<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ahmed K Emara</title><link>https://akemara.com/en/</link><atom:link href="https://akemara.com/en/index.xml" rel="self" type="application/rss+xml"/><description>Ahmed K Emara</description><generator>Akemara Kit (https://akemara.com)</generator><language>en</language><lastBuildDate>Sun, 19 May 2024 00:00:00 +0000</lastBuildDate><image><url>https://akemara.com/media/logo.svg</url><title>Ahmed K Emara</title><link>https://akemara.com/en/</link></image><item><title>REST API Design: The Complete Guide to Building APIs That Last</title><link>https://akemara.com/en/blog/rest-api-design-complete-guide/</link><pubDate>Fri, 17 Jul 2026 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/rest-api-design-complete-guide/</guid><description>&lt;p&gt;Designing a REST API is easy. Designing one you won&amp;rsquo;t quietly hate a year from now is the hard part.&lt;/p&gt;
&lt;p&gt;The moment another developer writes code against your endpoints, you&amp;rsquo;ve made them a promise. This URL works. This field is called &lt;code&gt;price&lt;/code&gt;. This call returns a &lt;code&gt;201&lt;/code&gt;. Change your mind later and you&amp;rsquo;re not refactoring your own code anymore. You&amp;rsquo;re breaking theirs, in production, probably on a Friday.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s why API design has so many &amp;ldquo;rules.&amp;rdquo; They aren&amp;rsquo;t bureaucracy. They&amp;rsquo;re the accumulated scar tissue of people who painted themselves into corners that were expensive to climb out of. The good part is that most of those decisions have a clear right answer, and I&amp;rsquo;ve collected them here in one place. Read it top to bottom, or jump to the section you need and come back later.&lt;/p&gt;
&lt;p&gt;The whole goal is an API that&amp;rsquo;s predictable. A developer who has never seen your docs should be able to guess how it works and be right most of the time.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="what-rest-actually-asks-of-you"&gt;What REST Actually Asks of You&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="The core constraints REST asks of an API"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/what-rest-actually-asks-of-you_hu_c0e95f8035168432.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/what-rest-actually-asks-of-you_hu_d5cb3f70bb69ae0c.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/what-rest-actually-asks-of-you_hu_afa0062e6a53ec8c.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/what-rest-actually-asks-of-you_hu_c0e95f8035168432.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/what-rest-actually-asks-of-you_hu_fbe2bb2d6165293b.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;REST is a style, not a library you install. A few of its constraints matter every single day:&lt;/p&gt;
&lt;p&gt;The client and the server stay independent. Your front end doesn&amp;rsquo;t care how the database is shaped, and the server doesn&amp;rsquo;t care whether the caller is a browser, a mobile app, or a cron job.&lt;/p&gt;
&lt;p&gt;Every request stands on its own. The server keeps no memory of you between calls, so each request carries whatever it needs to be understood. This sounds like a limitation until you try to run ten copies of your service behind a load balancer, at which point it&amp;rsquo;s the only thing that saves you.&lt;/p&gt;
&lt;p&gt;Responses say whether they can be cached, and resources live behind a small, standard set of HTTP methods. That last constraint is the one people break constantly, and most of this guide is really about respecting it.&lt;/p&gt;
&lt;p&gt;You don&amp;rsquo;t need the textbook definition. You need to apply these ideas, and it starts with naming.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="resource-naming-and-url-design"&gt;Resource Naming and URL Design&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Resource naming and URL design for REST APIs"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/resource-naming-and-url-design_hu_2b2ead5ae00f6e5a.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/resource-naming-and-url-design_hu_7284645e20937355.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/resource-naming-and-url-design_hu_f1c66ecada3571ca.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/resource-naming-and-url-design_hu_2b2ead5ae00f6e5a.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/resource-naming-and-url-design_hu_6374897027802789.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;A resource is a &lt;em&gt;thing&lt;/em&gt; your API exposes: a user, an order, a product. URLs name those things. So build them out of nouns, and let the HTTP method supply the verb.&lt;/p&gt;
&lt;h3 id="nouns-not-verbs"&gt;Nouns, not verbs&lt;/h3&gt;
&lt;p&gt;Put the action in the path and you end up duplicating the verb that HTTP already gives you, then drowning in hundreds of one-off endpoints.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;❌ GET /getAllProducts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;❌ POST /createProduct
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;❌ POST /products/5112/delete
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;✅ GET /products
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;✅ POST /products
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;✅ DELETE /products/5112
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="pluralize-your-collections"&gt;Pluralize your collections&lt;/h3&gt;
&lt;p&gt;Pick plural and stick with it. A plural name reads correctly whether you&amp;rsquo;re grabbing the whole list or one item out of it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /users # everyone
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /users/42 # one person
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST /users # add someone
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;❌ &lt;strong&gt;Don&amp;rsquo;t Do That :&lt;/strong&gt;
Name the list &lt;code&gt;/users&lt;/code&gt; but a single record &lt;code&gt;/user/42&lt;/code&gt; and you&amp;rsquo;ve just forced every client to remember which endpoints are special.&lt;/p&gt;
&lt;h3 id="nest-relationships-but-not-very-deep"&gt;Nest relationships, but not very deep&lt;/h3&gt;
&lt;p&gt;Show ownership by nesting. A customer&amp;rsquo;s orders sit under that customer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /customers/42/orders # customer&amp;#39;s orders
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /customers/42/orders/18 # one of them
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /customers/42/orders/18/items/3 # one of them
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Two or three levels are plenty. The moment you find yourself writing urls like this, stop immediately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;❌ &lt;code&gt;/customers/42/orders/18/items/3/supplier/9&lt;/code&gt;
or&lt;/li&gt;
&lt;li&gt;❌ &lt;code&gt;/companies/12/warehouses/4/aisles/2/shelves/45/products/908&lt;/code&gt;
URLs like that are miserable to build and break the moment anything moves. Promote the thing to a top-level resource and filter instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="keep-paths-boring"&gt;Keep paths boring&lt;/h3&gt;
&lt;p&gt;Lowercase everything, and use hyphens for multi-word names: &lt;code&gt;/shipping-addresses&lt;/code&gt;, never &lt;code&gt;/shippingAddresses&lt;/code&gt; or &lt;code&gt;/Shipping_Addresses&lt;/code&gt;. Skip trailing slashes. Skip file extensions like &lt;code&gt;.json&lt;/code&gt; (the content type lives in a header, not the URL). And settle on one casing style for your JSON fields, &lt;code&gt;snake_case&lt;/code&gt; or &lt;code&gt;camelCase&lt;/code&gt;, then never mix them.&lt;/p&gt;
&lt;p&gt;I know &amp;ldquo;be consistent&amp;rdquo; sounds like filler advice. It isn&amp;rsquo;t. Consistency is the feature that lets a developer guess the next endpoint correctly instead of opening your docs for every call.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="http-methods-let-the-verb-carry-the-meaning"&gt;HTTP Methods: Let the Verb Carry the Meaning&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="HTTP methods and the meaning each verb carries"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/http-methods-let-the-verb-carry-the-meaning_hu_3ae51af675b2ca4e.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/http-methods-let-the-verb-carry-the-meaning_hu_abab4286f07b0710.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/http-methods-let-the-verb-carry-the-meaning_hu_4f7df8c3645e444c.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/http-methods-let-the-verb-carry-the-meaning_hu_3ae51af675b2ca4e.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/http-methods-let-the-verb-carry-the-meaning_hu_8e3970bf9c810a31.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;The method says what you&amp;rsquo;re doing. Get this right and your URLs stay stable while the behavior stays obvious.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;What it&amp;rsquo;s for&lt;/th&gt;
&lt;th style="text-align: center"&gt;Safe?&lt;/th&gt;
&lt;th style="text-align: center"&gt;Idempotent?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read something&lt;/td&gt;
&lt;td style="text-align: center"&gt;Yes&lt;/td&gt;
&lt;td style="text-align: center"&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create something, or kick off an action you can&amp;rsquo;t safely repeat&lt;/td&gt;
&lt;td style="text-align: center"&gt;No&lt;/td&gt;
&lt;td style="text-align: center"&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Replace a resource completely&lt;/td&gt;
&lt;td style="text-align: center"&gt;No&lt;/td&gt;
&lt;td style="text-align: center"&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PATCH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Change part of a resource&lt;/td&gt;
&lt;td style="text-align: center"&gt;No&lt;/td&gt;
&lt;td style="text-align: center"&gt;Maybe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DELETE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove a resource&lt;/td&gt;
&lt;td style="text-align: center"&gt;No&lt;/td&gt;
&lt;td style="text-align: center"&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QUERY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run a complex read, with the query in the body, read more about it in this section
&lt;/td&gt;
&lt;td style="text-align: center"&gt;Yes&lt;/td&gt;
&lt;td style="text-align: center"&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;ldquo;Safe&amp;rdquo; means the call doesn&amp;rsquo;t change anything on the server. &amp;ldquo;Idempotent&amp;rdquo; means calling it ten times leaves things exactly as calling it once would.&lt;/p&gt;
&lt;p&gt;Two things fall out of this that you can&amp;rsquo;t fudge. First, a &lt;code&gt;GET&lt;/code&gt; must never change state, ever. A &amp;ldquo;GET that deletes&amp;rdquo; looks harmless until a browser prefetcher or a crawler wipes half your database for you. Second, &lt;code&gt;PUT&lt;/code&gt; and &lt;code&gt;PATCH&lt;/code&gt; are not interchangeable: send the whole object to &lt;code&gt;PUT&lt;/code&gt;, send only the changed fields to &lt;code&gt;PATCH&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You need to implement &lt;em&gt;&lt;strong&gt;&amp;ldquo;Idempotency&amp;rdquo;&lt;/strong&gt;&lt;/em&gt; for &lt;code&gt;POST&lt;/code&gt; too, but it doesn&amp;rsquo;t come from the HTTP spec. It&amp;rsquo;s a pattern you can add to your API so clients can safely retry a request that creates something without accidentally creating it twice. More on that later.
&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="query-the-read-that-outgrew-the-url"&gt;QUERY: The Read That Outgrew the URL&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="The QUERY method carries the query in the request body, safely"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/query-the-read-that-outgrew-the-url_hu_35191eab17ead3ab.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/query-the-read-that-outgrew-the-url_hu_2bd3e91554592e94.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/query-the-read-that-outgrew-the-url_hu_4b1d3d3df9ad37dd.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/query-the-read-that-outgrew-the-url_hu_35191eab17ead3ab.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/query-the-read-that-outgrew-the-url_hu_8c834cabf1720730.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Sooner or later every API grows a read that doesn&amp;rsquo;t fit in a URL. A search screen with twenty optional filters. A report builder. Anything where the query is a document, not three parameters.&lt;/p&gt;
&lt;p&gt;Until recently you had two workarounds, and both were compromises. You could keep stuffing the query string until it hit a limit you didn&amp;rsquo;t pick — the spec only asks systems to accept around 8,000 octets, plenty enforce less, and every filter now sits in server logs, browser history, and bookmarks. Or you could tunnel the search through &lt;code&gt;POST /products/search&lt;/code&gt;, which works but lies. POST is neither safe nor idempotent, so caches ignore the response and a client can&amp;rsquo;t retry a dropped request without wondering what it just did twice. You know it&amp;rsquo;s a read. HTTP doesn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;HTTP hadn&amp;rsquo;t added a general-purpose method since &lt;code&gt;PATCH&lt;/code&gt; in 2010, and in June 2026 it finally did.
— years in the making, and called &lt;code&gt;SEARCH&lt;/code&gt; in its early drafts — defines &lt;strong&gt;&lt;code&gt;QUERY&lt;/code&gt;&lt;/strong&gt;: safe and idempotent like &lt;code&gt;GET&lt;/code&gt;, carries a body like &lt;code&gt;POST&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;QUERY /products HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;Host: api.example.com
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;Content-Type: application/json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;Accept: application/json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;category&amp;#34;: &amp;#34;books&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;price&amp;#34;: { &amp;#34;min&amp;#34;: 10, &amp;#34;max&amp;#34;: 50 },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;in_stock&amp;#34;: true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;sort&amp;#34;: [&amp;#34;-created_at&amp;#34;, &amp;#34;name&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The results come back in a normal &lt;code&gt;200&lt;/code&gt;, same as any read. But because the method itself now says &amp;ldquo;this is a read,&amp;rdquo; everything in the middle can finally act like it. Clients and proxies can retry a QUERY after a connection failure without the
that POST needs. Responses are cacheable, with the request body folded into the cache key so two different queries never collide. And conditional requests work just like GET: send &lt;code&gt;If-None-Match&lt;/code&gt;, get a cheap &lt;code&gt;304&lt;/code&gt; back when nothing changed.&lt;/p&gt;
&lt;p&gt;A few rules ride along. &lt;code&gt;Content-Type&lt;/code&gt; on the request is mandatory; a server must reject a QUERY without one rather than sniff the body and guess. And the status codes behave the way you&amp;rsquo;d hope: &lt;code&gt;415&lt;/code&gt; when you don&amp;rsquo;t support that query format, &lt;code&gt;400&lt;/code&gt; when the body doesn&amp;rsquo;t match its declared type, &lt;code&gt;422&lt;/code&gt; when the query parses fine but can&amp;rsquo;t actually run.&lt;/p&gt;
&lt;p&gt;The spec also hands you two response headers worth knowing. &lt;code&gt;Location&lt;/code&gt; can point at a URL that re-runs the same query as a plain &lt;code&gt;GET&lt;/code&gt;, so clients can repeat it without resending the body. &lt;code&gt;Content-Location&lt;/code&gt; can point at a stored copy of this specific result. Both optional, both great for expensive queries.&lt;/p&gt;
&lt;p&gt;Discovery is pleasantly boring: a resource can advertise support with an &lt;code&gt;Accept-Query&lt;/code&gt; response header listing the formats it takes (&lt;code&gt;Accept-Query: application/json&lt;/code&gt;), you can look for &lt;code&gt;QUERY&lt;/code&gt; in the &lt;code&gt;Allow&lt;/code&gt; header of an &lt;code&gt;OPTIONS&lt;/code&gt; response, or you just send one and handle the &lt;code&gt;405&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;❌ &lt;strong&gt;Don&amp;rsquo;t Do That :&lt;/strong&gt;
Don&amp;rsquo;t reach for &lt;code&gt;GET&lt;/code&gt; with a request body instead. The spec assigns a GET body no meaning, and any proxy, cache, or server along the path is free to drop it. Elasticsearch gets away with this inside a cluster you control; a public API won&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;Should you ship QUERY today? Check your stack first. The RFC is brand new, so framework, gateway, CDN, and client library support is still filling in — and browser calls pay a CORS preflight, since QUERY isn&amp;rsquo;t on the safelist. Where your tooling isn&amp;rsquo;t ready, a clearly named &lt;code&gt;POST /search&lt;/code&gt; stays the honest fallback. And for simple filters, keep doing what the
says: even the RFC notes that short queries belong in the query string. QUERY is for the ones that outgrew it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="status-codes-tell-the-truth-about-what-happened"&gt;Status Codes: Tell the Truth About What Happened&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="HTTP status codes describing what actually happened"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/status-codes-tell-the-truth-about-what-happened_hu_2332184dd3d80a9.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/status-codes-tell-the-truth-about-what-happened_hu_9cec14b9917b997a.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/status-codes-tell-the-truth-about-what-happened_hu_cf258351480f2f99.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/status-codes-tell-the-truth-about-what-happened_hu_2332184dd3d80a9.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/status-codes-tell-the-truth-about-what-happened_hu_d275f8a07e71d3cf.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Return the code that actually describes the outcome. Learn a small set well rather than reaching for exotic ones nobody recognizes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Success (2xx)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;200 OK&lt;/code&gt; — the everyday success.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;201 Created&lt;/code&gt; — you made something. Add a &lt;code&gt;Location&lt;/code&gt; header pointing to it.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;202 Accepted&lt;/code&gt; — you took the request but you&amp;rsquo;re processing it later.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;204 No Content&lt;/code&gt; — it worked, there&amp;rsquo;s nothing to send back (great for &lt;code&gt;DELETE&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Redirects (3xx)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;301 Moved Permanently&lt;/code&gt; — it lives at a new URL now.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;304 Not Modified&lt;/code&gt; — the caller&amp;rsquo;s cached copy is still good (this is your friend; more on it under caching).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Client Side Error / Mistakes (4xx)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;400 Bad Request&lt;/code&gt; — the request itself is malformed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;401 Unauthorized&lt;/code&gt; — you don&amp;rsquo;t know who they are.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;403 Forbidden&lt;/code&gt; — you know who they are, they just can&amp;rsquo;t do this.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;404 Not Found&lt;/code&gt; — no such thing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;405 Method Not Allowed&lt;/code&gt; — right URL, wrong verb.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;409 Conflict&lt;/code&gt; — clashes with current state, like a duplicate.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;422 Unprocessable Entity&lt;/code&gt; — well-formed but it failed validation.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;429 Too Many Requests&lt;/code&gt; — they&amp;rsquo;re hitting you too hard.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Server Side Error / Mistakes (5xx)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;500 Internal Server Error&lt;/code&gt; — something blew up on your side.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;503 Service Unavailable&lt;/code&gt; — you&amp;rsquo;re down or overloaded right now.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&amp;rsquo;s one sin worth calling out by name: returning &lt;code&gt;200 OK&lt;/code&gt; with an error buried in the body. Clients trust the status line. If you lie there, every error handler downstream lies too.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="requests-and-responses"&gt;Requests and Responses&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Predictable JSON request and response shapes"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/requests-and-responses_hu_d0c0c919a14635f1.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/requests-and-responses_hu_2a8193a2de034037.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/requests-and-responses_hu_d14b242125be4ce6.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/requests-and-responses_hu_d0c0c919a14635f1.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/requests-and-responses_hu_74b3796178e3d6d1.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Use JSON. For request bodies, for responses, for basically everything except shoving a file across the wire. And actually set the header, &lt;code&gt;Content-Type: application/json&lt;/code&gt;. A JSON-shaped string without that header just makes clients guess and parse by hand.&lt;/p&gt;
&lt;p&gt;After a &lt;code&gt;POST&lt;/code&gt; or a &lt;code&gt;PATCH&lt;/code&gt;, send the resource back. The client just changed it; don&amp;rsquo;t make them fire a second request to see what it looks like now.&lt;/p&gt;
&lt;p&gt;Keep your response shapes predictable. For lists, wrap the data and hang the pagination info off the same object so it always lives in the same spot:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Wireless Mouse&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;price&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;24.99&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pagination&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;page&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;per_page&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;total&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;137&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;next&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/products?page=2&amp;amp;per_page=20&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="filtering-sorting-and-pagination"&gt;Filtering, Sorting, and Pagination&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Filtering, sorting, and pagination via query parameters"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/filtering-sorting-and-pagination_hu_c08ef98f60d39b07.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/filtering-sorting-and-pagination_hu_fe7bd93d3e6aee6e.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/filtering-sorting-and-pagination_hu_7bccb9a1c1ea7098.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/filtering-sorting-and-pagination_hu_c08ef98f60d39b07.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/filtering-sorting-and-pagination_hu_10aecbdab26d2065.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Collections grow. Plan for it from the first commit, and do it with query parameters. Never bake a limit into the path.&lt;/p&gt;
&lt;p&gt;Filter to narrow things down:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /products?category=books&amp;amp;status=active&amp;amp;min_price=10
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sort with a readable convention. A leading &lt;code&gt;-&lt;/code&gt; for descending is common and people get it instantly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /products?sort=-created_at,name
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Paginate anything that can return a big list. You&amp;rsquo;ve got two real options:&lt;/p&gt;
&lt;p&gt;Offset, or page-based, pagination is the simple one. &lt;code&gt;?page=3&amp;amp;per_page=20&lt;/code&gt;. It&amp;rsquo;s perfect for small and medium datasets and for any UI that shows page numbers.&lt;/p&gt;
&lt;p&gt;Cursor-based pagination hands back an opaque pointer to the next batch instead. &lt;code&gt;?limit=20&amp;amp;cursor=eyJpZCI6MTQ0fQ&lt;/code&gt;. It costs more to build, but it stays correct and fast even when rows are being inserted while someone pages through, which is exactly why high-traffic APIs like Stripe lean on it. If your data moves a lot or your tables are huge, this is the one.&lt;/p&gt;
&lt;p&gt;Whichever you pick, return some metadata: a total, or next and previous links, so the client isn&amp;rsquo;t left wondering whether there&amp;rsquo;s more.&lt;/p&gt;
&lt;p&gt;When the filters themselves outgrow the query string, that&amp;rsquo;s a job for
.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="error-handling"&gt;Error Handling&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="One consistent error envelope for REST APIs"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/error-handling_hu_7d9497aad873391e.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/error-handling_hu_deb1ae4d10d8f08c.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/error-handling_hu_eb511dc090030d1.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/error-handling_hu_7d9497aad873391e.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/error-handling_hu_f182997bdea9722d.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Errors are part of your interface. Clients have to read them in code, not just with their eyes, so use one consistent shape everywhere. There&amp;rsquo;s a standard for this,
, but a simple consistent envelope of your own is fine too:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;validation_failed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;The request contains invalid fields.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;details&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;field&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;issue&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;must be a valid email address&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;field&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;age&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;issue&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;must be greater than 0&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A good error gives the caller a stable machine code to branch on (so they check for &lt;code&gt;validation_failed&lt;/code&gt; instead of scanning your prose), a human message for whoever&amp;rsquo;s reading the logs, and field-level detail when validation fails. What it never includes is a stack trace, a SQL fragment, or an internal file path. Those help attackers and confuse everyone else.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="versioning"&gt;Versioning&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="API versioning strategies"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/versioning_hu_c46e0d67940e9abd.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/versioning_hu_9655f75a1ce5dd48.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/versioning_hu_72f55b15ae51245b.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/versioning_hu_c46e0d67940e9abd.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/versioning_hu_2719950a6d322884.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Public APIs change. Version from the very first release so you can grow without breaking the people already depending on you.&lt;/p&gt;
&lt;p&gt;The usual approaches:&lt;/p&gt;
&lt;p&gt;URI versioning puts it right in the path, &lt;code&gt;/v1/products&lt;/code&gt;. It&amp;rsquo;s the popular default because you can see it, route on it, and test it in a browser without thinking.&lt;/p&gt;
&lt;p&gt;Header versioning keeps URLs clean by tucking the version into a header like &lt;code&gt;Accept: application/vnd.myapi.v1+json&lt;/code&gt;. Tidier, but harder to eyeball and debug.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also a date-based flavor that some big APIs use, where you pin a version with something like &lt;code&gt;X-Api-Version: 2024-03-29&lt;/code&gt;. Stripe and GitHub both work this way, and it&amp;rsquo;s nice when you ship changes constantly.&lt;/p&gt;
&lt;p&gt;Pick one and use it everywhere. The rule underneath all of them: adding an optional field is safe and needs no new version, but removing a field, renaming one, or changing its type is a breaking change and belongs in a new major version.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="security"&gt;Security&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Security applied to every API endpoint"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/security_hu_871626d3ce0f8d6e.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/security_hu_99dfd0caca2f90ac.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/security_hu_6c88377092e9fe22.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/security_hu_871626d3ce0f8d6e.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/security_hu_519ded1459bc0b18.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Security isn&amp;rsquo;t a phase near the end of the project. It&amp;rsquo;s a property of every endpoint, so treat it that way.&lt;/p&gt;
&lt;p&gt;Serve everything over HTTPS and refuse plain HTTP outright. A token sent over HTTP is a token sent to anyone listening.&lt;/p&gt;
&lt;p&gt;For authentication, figure out who&amp;rsquo;s calling. API keys are simple and great for server-to-server traffic and identifying which app is talking to you. OAuth 2.0 and OpenID Connect are the standard when a user is granting access on their behalf. JWTs are self-contained tokens that ride along in a header and fit REST&amp;rsquo;s stateless nature nicely.&lt;/p&gt;
&lt;p&gt;Authentication is only half of it. Authorization is the other half: knowing &lt;em&gt;who&lt;/em&gt; someone is doesn&amp;rsquo;t mean they&amp;rsquo;re allowed to touch &lt;em&gt;this&lt;/em&gt; record. Check permissions on every request, and default to denying.&lt;/p&gt;
&lt;p&gt;Rate limit your endpoints so one runaway client can&amp;rsquo;t take you down. When someone trips the limit, return &lt;code&gt;429&lt;/code&gt; with a &lt;code&gt;Retry-After&lt;/code&gt; header so the well-behaved clients know to back off instead of hammering harder.&lt;/p&gt;
&lt;p&gt;Validate everything that comes in. Assume every request is hostile until it proves otherwise. And keep secrets out of URLs entirely, because tokens in a query string end up in server logs, browser history, and whatever analytics tool is watching. Headers exist for a reason.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="performance-and-caching"&gt;Performance and Caching&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Performance and caching for REST APIs"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/performance-and-caching_hu_40e5fc06eed168cd.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/performance-and-caching_hu_52fc2e93bf654a6e.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/performance-and-caching_hu_811075f7b75c139e.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/performance-and-caching_hu_40e5fc06eed168cd.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/performance-and-caching_hu_3bd8330a889e530d.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;A few cheap wins here go a long way:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Cache-Control&lt;/code&gt; headers tell clients and proxies how long a response stays fresh, which spares you a flood of pointless requests for data that barely changes.&lt;/p&gt;
&lt;p&gt;ETags pair with &lt;code&gt;If-None-Match&lt;/code&gt; so a client can ask &amp;ldquo;has this changed?&amp;rdquo; and get back a tiny &lt;code&gt;304 Not Modified&lt;/code&gt; when it hasn&amp;rsquo;t. Bandwidth saved on both ends, for almost no effort.&lt;/p&gt;
&lt;p&gt;Turn on compression (gzip or Brotli). JSON shrinks a lot and you pay basically nothing.&lt;/p&gt;
&lt;p&gt;And let clients ask for less. Sparse fieldsets like &lt;code&gt;?fields=id,name,price&lt;/code&gt; and opt-in expansion like &lt;code&gt;?expand=author&lt;/code&gt; cut payload size and round trips, and they quietly save you from a pile of N+1 query problems.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="idempotency-so-retries-dont-hurt"&gt;Idempotency, So Retries Don&amp;rsquo;t Hurt&lt;/h2&gt;
&lt;p&gt;Networks drop requests halfway through, and clients retry when they do. For anything that creates a record or moves money, a double-submit is a genuine bug, not a hypothetical one.&lt;/p&gt;
&lt;p&gt;The fix is an &lt;code&gt;Idempotency-Key&lt;/code&gt; header. The client sends a unique key with the request; if your server sees that same key twice, it returns the original result instead of doing the work again. Stripe popularized this pattern for exactly the obvious reason: nobody wants to charge a customer twice because their phone hiccupped. Borrow it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="documentation"&gt;Documentation&lt;/h2&gt;
&lt;p&gt;An API nobody can figure out might as well be broken. Describe yours with an
spec (you may still hear it called Swagger). One machine-readable file becomes your source of truth, and from it you can generate interactive docs, client SDKs in a dozen languages, and mock servers for testing.&lt;/p&gt;
&lt;p&gt;Document every endpoint, its parameters, and, please, its error responses, all with real examples. The APIs that feel effortless are the ones whose docs answer your next question before you&amp;rsquo;ve finished asking it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="a-word-on-hateoas"&gt;A Word on HATEOAS&lt;/h2&gt;
&lt;p&gt;Strict REST includes HATEOAS, where responses carry links telling the client what it can do next, so it can navigate without hardcoding URLs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pending&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;_links&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;self&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;href&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/orders/42&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;cancel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;href&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/orders/42/cancel&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;items&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;href&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/orders/42/items&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the real world most production APIs do this halfway or not at all, and they survive just fine. Treat it as a tool rather than a commandment. Add links where they genuinely help someone discover what&amp;rsquo;s possible, and don&amp;rsquo;t lose sleep over purity.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-checklist"&gt;The Checklist&lt;/h2&gt;
&lt;p&gt;Pin this somewhere and glance at it before you ship:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; URLs are nouns; the method is the verb.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Collections are plural and named consistently.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Relationships nest no more than a two or three levels.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Paths are lowercase and hyphenated, no trailing slash, no &lt;code&gt;.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; One casing style for JSON fields, everywhere.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Methods match their meaning (&lt;code&gt;GET&lt;/code&gt; never writes; &lt;code&gt;PUT&lt;/code&gt; replaces, &lt;code&gt;PATCH&lt;/code&gt; edits).&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Honest status codes, and never a &lt;code&gt;200&lt;/code&gt; wrapped around an error.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; JSON with the right &lt;code&gt;Content-Type&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Filtering, sorting, and pagination through query params, with metadata.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Complex reads use
where the stack supports it, never GET with a body.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; One error shape, with codes, messages, and field-level detail.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Versioned from day one; breaking changes only in a new major version.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; HTTPS only, real auth, real authorization.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Rate limiting with &lt;code&gt;429&lt;/code&gt; and &lt;code&gt;Retry-After&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Caching via &lt;code&gt;Cache-Control&lt;/code&gt; and ETags.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Idempotency keys on anything that creates or charges.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; OpenAPI docs with examples and error cases.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="faq"&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What&amp;rsquo;s the difference between PUT and PATCH?&lt;/strong&gt;
&lt;code&gt;PUT&lt;/code&gt; replaces the whole resource, so anything you leave out gets wiped or reset. &lt;code&gt;PATCH&lt;/code&gt; only touches the fields you send. Reach for &lt;code&gt;PUT&lt;/code&gt; when you&amp;rsquo;re swapping the entire thing and &lt;code&gt;PATCH&lt;/code&gt; when you&amp;rsquo;re nudging a couple of values.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Singular or plural resource names?&lt;/strong&gt;
Plural, consistently. &lt;code&gt;/users&lt;/code&gt; for the list and &lt;code&gt;/users/42&lt;/code&gt; for one of them reads naturally both ways and saves clients from memorizing exceptions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How should I version an API?&lt;/strong&gt;
Version it from the first release. URI versioning (&lt;code&gt;/v1/...&lt;/code&gt;) is the easiest to see and route, though header or date-based versioning works too. Whatever you choose, use it everywhere and only bump the version for breaking changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Offset or cursor pagination?&lt;/strong&gt;
Offset is simpler and totally fine for modest datasets and numbered-page UIs. Cursor pagination holds up when data is changing in real time or the tables are enormous, which is why the big APIs prefer it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is it still REST if I skip HATEOAS?&lt;/strong&gt;
Technically HATEOAS is part of REST, but most real APIs skip it or do it partially and still get called RESTful all day long. Add hypermedia where it actually helps; otherwise don&amp;rsquo;t sweat it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="last-thing"&gt;Last Thing&lt;/h2&gt;
&lt;p&gt;None of this is clever. Good API design is mostly the discipline to be boring and predictable in all the places a developer expects you to be boring and predictable. Get the fundamentals right, version from day one, and never make someone read your source code to figure out what an endpoint does.&lt;/p&gt;
&lt;p&gt;Do that, and your API just keeps working while everything around it churns. Which, honestly, is about the nicest thing anyone can say about one.&lt;/p&gt;</description></item><item><title>How I Turned a 100 ms Problem into a Microsecond Query with AI-Assisted Performance Engineering</title><link>https://akemara.com/en/blog/ai-assisted-performance-engineering/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/ai-assisted-performance-engineering/</guid><description>&lt;p&gt;&lt;em&gt;A real Hungarian-lottery problem, two complete solutions with the actual Go on the table, and the honest story of how AI helped collapse latency from a 100 ms budget to ~1.5 microseconds — not in one prompt, but one disciplined iteration at a time.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One of the most useful ways I have found to work with AI on a performance problem is &lt;em&gt;not&lt;/em&gt; to ask:&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;“Can you optimize this code?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;but instead to ask:&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;“What work can be moved out of the critical path?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That single question often rewrites the entire architecture. This article is about a problem that began with one ordinary line in a spec — &lt;strong&gt;maximum acceptable query latency: 100 ms&lt;/strong&gt; — and ended somewhere I did not expect.&lt;/p&gt;
&lt;p&gt;The job was to report winner counts for a lottery-like system with up to &lt;strong&gt;10 million players&lt;/strong&gt;. The final results landed like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;100 ms budget
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;12 ms optimized parallel scan (ordinary systems engineering)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1.5 µs (0.0015 ms) indexed O(1) query (AI-assisted redesign)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Put another way: the budget was 100 ms = 100,000,000 ns. The final query latency was about 1,500 ns — roughly &lt;strong&gt;66,000× under budget&lt;/strong&gt;. But the final number is not the interesting part. The interesting part is how AI can help decompose a problem into known building blocks that &lt;em&gt;snap together&lt;/em&gt; into something dramatically faster — and how much human steering that took.&lt;/p&gt;
&lt;p&gt;Before we go anywhere, it helps to fix the scale we are talking about, because the difference between a millisecond and a microsecond is the difference between “fast enough” and “effectively free.”&lt;/p&gt;
&lt;p&gt;The relationship between the units is worth committing to memory — performance work lives and dies on knowing which unit you are in:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;UNIT&lt;/span&gt; &lt;span class="n"&gt;SYMBOL&lt;/span&gt; &lt;span class="n"&gt;SECONDS&lt;/span&gt; &lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;NEXT&lt;/span&gt; &lt;span class="n"&gt;UNIT&lt;/span&gt; &lt;span class="n"&gt;EVERYDAY&lt;/span&gt; &lt;span class="n"&gt;FEEL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;─────────────────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;slow&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="nb"&gt;load&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;millisecond&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="mf"&gt;0.001&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;snappy&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;microsecond&lt;/span&gt; &lt;span class="err"&gt;µ&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="mf"&gt;0.000001&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="err"&gt;µ&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;few&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="n"&gt;reads&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;nanosecond&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="mf"&gt;0.000000001&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;µ&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="n"&gt;CPU&lt;/span&gt; &lt;span class="n"&gt;instruction&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;─────────────────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="err"&gt;µ&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt; &lt;span class="err"&gt;µ&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;indexed&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;227&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;
&lt;figure id="figure-the-whole-journey-on-one-scale-a-second-is-a-thousand-milliseconds-a-millisecond-is-a-thousand-microseconds-a-microsecond-is-a-thousand-nanoseconds-moving-from-12-ms-to-15-µs-is-not-a-small-win--it-is-crossing-nearly-four-orders-of-magnitude"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Time units on one scale: from seconds to milliseconds to microseconds to nanoseconds, marking the journey from a 100 ms budget to a 1.5 µs query"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/article-cover-2_hu_c1825071bd8a8cc4.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/article-cover-2_hu_a9da2cecc2191da.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/article-cover-2_hu_d1317e6debe1bb3a.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/article-cover-2_hu_c1825071bd8a8cc4.webp"
width="760"
height="372"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/article-cover-2_hu_771a8fc83770aca0.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
The whole journey on one scale. A second is a thousand milliseconds; a millisecond is a thousand microseconds; a microsecond is a thousand nanoseconds. Moving from 12 ms to 1.5 µs is not a small win — it is crossing nearly four orders of magnitude.
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="the-hungarian-lottery-problem"&gt;The Hungarian Lottery problem&lt;/h2&gt;
&lt;p&gt;The rules are simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each player chooses &lt;strong&gt;5 distinct numbers&lt;/strong&gt; from &lt;strong&gt;1 to 90&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Later, the lottery draws &lt;strong&gt;5 distinct winning numbers&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A player wins if they match exactly &lt;strong&gt;2, 3, 4, or 5&lt;/strong&gt; numbers.&lt;/li&gt;
&lt;li&gt;At peak load there can be around &lt;strong&gt;10 million players&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;After the draw, the system must immediately report the winner counts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The required output, after each draw, answers four questions: how many players matched exactly 2 numbers, exactly 3, exactly 4, and exactly 5?&lt;/p&gt;
&lt;p&gt;At first glance this looks trivial — it is a counting problem. But performance engineering starts the moment someone adds a latency requirement. With &lt;strong&gt;maximum response time after the draw: 100 ms&lt;/strong&gt; in the spec, the question quietly changes. We stop asking “can we count the winners?” and start asking the only question that matters at scale:&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;How much work must happen after the winning numbers are known?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That distinction leads to two very different implementations: a &lt;strong&gt;scan-based solution&lt;/strong&gt; optimized for simplicity, low memory, and cache efficiency; and an &lt;strong&gt;indexed solution&lt;/strong&gt; optimized for extremely low post-draw latency. Both are useful. Both are worth understanding. And the gap between them is where this story lives.&lt;/p&gt;
&lt;h2 id="understanding-the-shape-of-the-problem"&gt;Understanding the shape of the problem&lt;/h2&gt;
&lt;p&gt;Performance-heavy problems are rarely solved by one trick. Most of the time, the real work is understanding the &lt;em&gt;shape&lt;/em&gt; of the problem before writing any code. I try to answer a small set of questions first:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What is known ahead of time?&lt;/li&gt;
&lt;li&gt;What must be answered immediately?&lt;/li&gt;
&lt;li&gt;What is the true critical path?&lt;/li&gt;
&lt;li&gt;Where is the bottleneck — CPU, memory bandwidth, allocation, I/O, synchronization, or algorithmic complexity?&lt;/li&gt;
&lt;li&gt;What tradeoff are we willing to make between memory, preprocessing time, and query latency?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the lottery, the answers are clarifying. The tickets are &lt;em&gt;known before the draw&lt;/em&gt;; the winning numbers are &lt;em&gt;only known after&lt;/em&gt;. The critical path — the part a human is actually waiting on — is the post-draw report; everything else is preparation we can do on our own schedule. So the right question is not “how fast is the whole program?” but “how fast is the part users are waiting for?”&lt;/p&gt;
&lt;h2 id="thinking-like-an-ai-performance-agent"&gt;Thinking like an AI performance agent&lt;/h2&gt;
&lt;p&gt;When I work with AI on optimization problems, I try to hand it &lt;em&gt;constraints&lt;/em&gt; instead of implementation details. Something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Goal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Answer winner counts after the draw.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Maximum latency 100 ms.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Dataset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Up to 10 million tickets.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Allowed tradeoffs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;more preprocessing,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;more memory,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;lower query latency.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;What work can be moved before the draw?&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That framing naturally produces two architectures: &lt;strong&gt;scan everything after the draw&lt;/strong&gt;, or &lt;strong&gt;precompute enough that the draw becomes almost free&lt;/strong&gt;. The second option is where things get interesting — but I want to be honest up front about the order of events. I did &lt;em&gt;not&lt;/em&gt; arrive at the fast solution by typing one clever prompt. I arrived at a solid, ordinary solution first, by hand, and only then used AI to attack the part that ordinary engineering could not improve.&lt;/p&gt;
&lt;h2 id="the-key-idea-split-the-problem-into-phases"&gt;The key idea: split the problem into phases&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Diagram splitting the system into a preparation phase before the draw and a minimal query phase after the draw"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-key-idea-split-the-problem-into-phases_hu_ecb5a6ba4aa3ded9.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-key-idea-split-the-problem-into-phases_hu_5396a45ff4892110.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-key-idea-split-the-problem-into-phases_hu_b77e3c5202c6e549.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-key-idea-split-the-problem-into-phases_hu_ecb5a6ba4aa3ded9.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-key-idea-split-the-problem-into-phases_hu_46000d7691140d77.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Because the tickets are known before the draw and the winning numbers are not, the system splits naturally into two phases:&lt;/p&gt;
&lt;p&gt;For performance-heavy problems, this separation is often more important than the algorithm itself. A system that does more work &lt;em&gt;before&lt;/em&gt; the draw and very little &lt;em&gt;after&lt;/em&gt; can beat one that starts quickly but scans everything while the clock is running. This is one of the most important patterns in our field: &lt;strong&gt;move work from the critical path into a preparation phase.&lt;/strong&gt; Search engines do it with inverted indexes, databases with secondary indexes, compilers with intermediate representations, recommendation systems with precomputed features. The lottery is no different.&lt;/p&gt;
&lt;h2 id="high-level-architecture"&gt;High-level architecture&lt;/h2&gt;
&lt;p&gt;
&lt;figure id="figure-one-front-end-two-execution-paths-scan-mode-is-on-per-draw-indexed-mode-does-a-fixed-26-lookups-per-draw--o1-independent-of-the-number-of-players"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Architecture with one front end feeding two execution paths: an O(N) scan mode and an O(1) indexed mode"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/high-level-architecture_hu_d1bfa99d8b9f68b9.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/high-level-architecture_hu_5aafced10c5a7fc2.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/high-level-architecture_hu_2c4e4ca2d90ab016.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/high-level-architecture_hu_d1bfa99d8b9f68b9.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/high-level-architecture_hu_5b79eae148444e3f.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
One front end, two execution paths. Scan mode is O(N) per draw; indexed mode does a fixed 26 lookups per draw — O(1), independent of the number of players.
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;The final design supports two execution modes that share the same front end. Input is parsed in parallel into a compact packed representation, and from there the data feeds either a scan path or an indexed path:&lt;/p&gt;
&lt;p&gt;The scan mode met the latency target comfortably. The indexed mode essentially eliminated query latency altogether. We will build both — and, conveniently, both satisfy the same one-line Go signature, so the rest of the program is written against the interface and the algorithm is chosen at startup with a flag:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;type winnerCounter func(d1, d2, d3, d4, d5 int) (w2, w3, w4, w5 int)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// ./lottery -mode scan input.txt → O(N) per draw, lower memory
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// ./lottery -mode indexed input.txt → O(1) per draw, default
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That symmetry is not just tidy; it is what lets the slow path &lt;em&gt;verify&lt;/em&gt; the fast one later.&lt;/p&gt;
&lt;h2 id="solution-1--the-cache-friendly-scan-12-ms-and-no-ai-required"&gt;Solution 1 — the cache-friendly scan (12 ms, and no AI required)&lt;/h2&gt;
&lt;p&gt;I want to be clear: there is &lt;em&gt;no AI magic in this section&lt;/em&gt;. The 12 ms result came from ordinary, disciplined systems engineering — the kind any performance-minded engineer can do with a profiler and some patience. That matters, because it sets the bar the AI-assisted solution had to beat.&lt;/p&gt;
&lt;p&gt;The scan solution is the simplest correct approach. Load all tickets into memory, then for each draw, scan every ticket and count matches:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for each ticket:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; matches = count numbers that appear in the draw
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if matches == 2: winners2++
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if matches == 3: winners3++
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if matches == 4: winners4++
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if matches == 5: winners5++
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The complexity is &lt;strong&gt;O(N) per draw&lt;/strong&gt;. For 10 million players that &lt;em&gt;sounds&lt;/em&gt; expensive, but the constants are tiny — and data layout is where the win comes from.&lt;/p&gt;
&lt;h2 id="compact-representation"&gt;Compact representation&lt;/h2&gt;
&lt;p&gt;Instead of storing tickets as objects, structs, maps, or nested slices, store them as one flat byte array:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tickets []byte // player i occupies tickets[5*i : 5*i+5]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So the memory layout is just numbers, back to back, with nothing in between:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Player 0 Player 1 Player 2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌─────────────┐ ┌─────────────┐ ┌─────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ n n n n n │ │ n n n n n │ │ n n n n n │ ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└─────────────┘ └─────────────┘ └─────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Flat memory: [n0 n1 n2 n3 n4][n0 n1 n2 n3 n4][n0 n1 n2 n3 n4]...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With one byte per number, 10 million tickets pack into about &lt;code&gt;10,000,000 × 5 = 50 MB&lt;/code&gt;, and a modern CPU streams through 50 MB of contiguous memory surprisingly quickly. (The raw text file is closer to 145 MB — more on how we avoid copying &lt;em&gt;that&lt;/em&gt; later.) The advantages stack up: no per-ticket allocation, no pointer chasing, a compact footprint, sequential access, cache-friendly scanning, and trivial chunking across workers.&lt;/p&gt;
&lt;p&gt;“For performance-heavy code, &lt;strong&gt;data layout is part of the algorithm&lt;/strong&gt;. A theoretically simple algorithm with good locality routinely beats a cleverer algorithm implemented with poor locality.”&lt;/p&gt;
&lt;h2 id="the-hot-loop-a-91-byte-table-that-lives-in-l1"&gt;The hot loop: a 91-byte table that lives in L1&lt;/h2&gt;
&lt;p&gt;
&lt;figure id="figure-scan-mode-per-draw-mark-the-hit-table-fan-the-packed-tickets-out-across-workers-let-each-build-a-private-histogram-merge-and-print-no-locks-on-the-hot-path"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Scan mode flow: mark the 91-byte hit table, fan packed tickets across workers with private histograms, merge and print"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-hot-loop-a-91-byte-table-that-lives-in-l1_hu_9ab355ff2693c152.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-hot-loop-a-91-byte-table-that-lives-in-l1_hu_3ed1a918d421dc51.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-hot-loop-a-91-byte-table-that-lives-in-l1_hu_6d775bac6bef974e.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-hot-loop-a-91-byte-table-that-lives-in-l1_hu_9ab355ff2693c152.webp"
width="760"
height="507"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-hot-loop-a-91-byte-table-that-lives-in-l1_hu_63a9bdc1529bef40.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
Scan mode, per draw. Mark the hit table, fan the packed tickets out across workers, let each build a private histogram, merge, and print. No locks on the hot path.
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;For each draw, build a tiny membership table — one byte per possible number — and mark the five that were drawn. At 91 bytes it stays resident in L1 cache, so every lookup is essentially free. Counting a player then collapses to five indexed reads and an increment: no set intersection, no map lookup, no sorting, no allocation. Here is the actual hot loop, lifted straight from the source:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;isDrawn&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;maxNumber&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;91&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;resident&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;L1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// each worker walks its own slice of players into a private histogram:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chunkStart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chunkEnd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// m is 0..5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;histogram&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// buckets 2..5 are the answer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is the entire computation: a branch-free sum of five bytes and one array bump, ten million times — the kind of work a CPU chews through without complaint.&lt;/p&gt;
&lt;h2 id="parallel-scanning"&gt;Parallel scanning&lt;/h2&gt;
&lt;p&gt;
&lt;figure id="figure-map-then-reduce-over-packed-memory-workers-produce-local-histograms-in-parallel-a-single-merge-sums-them-histogram-buckets-25-are-the-answer-the-business-asked-for"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Map-then-reduce over packed memory: workers build local histograms in parallel and a single merge sums buckets 2 to 5"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/parallel-scanning_hu_d00a2d617ad0d51c.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/parallel-scanning_hu_6003d5193a4f565d.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/parallel-scanning_hu_e61f970a69122f05.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/parallel-scanning_hu_d00a2d617ad0d51c.webp"
width="760"
height="507"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/parallel-scanning_hu_7f0a11db0f4495b8.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
Map-then-reduce over packed memory. Workers produce local histograms in parallel; a single merge sums them. Histogram buckets 2–5 are the answer the business asked for.
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;The scan splits cleanly across CPU cores. Each worker processes a contiguous range of players and keeps a &lt;em&gt;private&lt;/em&gt; histogram, so there is no locking in the hot loop:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Packed tickets
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌───────────────────────────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ player 0 ............................. player N │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└───────────────────────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌────────────┐ ┌────────────┐ ┌────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Worker 0 │ │ Worker 1 │ │ Worker 2 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ hist[0..5] │ │ hist[0..5] │ │ hist[0..5] │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └───────────────┼───────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌──────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ Merge result │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The theoretical complexity stays &lt;strong&gt;O(N)&lt;/strong&gt;, but practical latency approaches &lt;strong&gt;O(N / p)&lt;/strong&gt; where &lt;em&gt;p&lt;/em&gt; is the number of cores. Better still, every worker walks a contiguous, predictable region of memory — exactly the access pattern hardware prefetchers love.&lt;/p&gt;
&lt;h2 id="result-12-ms"&gt;Result: 12 ms&lt;/h2&gt;
&lt;p&gt;On a deliberately constrained box — 4 logical CPUs, 4 GB RAM — the scan implementation handled 10 million players with an average draw latency of about &lt;strong&gt;12.2 ms&lt;/strong&gt;. Against a 100 ms budget, that is already &lt;strong&gt;~8× faster than required&lt;/strong&gt;. Mission accomplished. Or so it seemed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why keep scan mode at all?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is simple, memory-efficient, easy to debug, and easy to trust. It makes a perfect correctness baseline, shines when memory is tight or draws are rare, and — as we will see — can &lt;em&gt;verify&lt;/em&gt; the fast solution. Before building a complex optimization, build the simplest version that is fast enough to measure: sometimes it is already good enough, and always it tells you where the real bottleneck is.&lt;/p&gt;
&lt;h2 id="asking-ai-a-better-question"&gt;Asking AI a better question&lt;/h2&gt;
&lt;p&gt;With 12 ms in hand, the lazy follow-up would have been “Can this be faster?” AI is happy to answer that — tighter loops, SIMD, chunk-size tuning — and you claw back a few percent. Instead I asked a structurally different question:&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;Can the query become independent of the number of players?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That changes everything. A few percent is optimization; independence from N is a different runtime model. The answer turned out to be yes — and the resulting architecture is not new mathematics but a composition of several classical ideas. Composing them correctly, in code, at 1.5 µs, was not a one-shot prompt.&lt;/p&gt;
&lt;h2 id="the-ai-collaboration-was-iterative-not-one-prompt"&gt;The AI collaboration was iterative, not one prompt&lt;/h2&gt;
&lt;p&gt;This is the part most “I used AI to 1000× my code” stories quietly skip. The reframing question opened the door, but walking through it took several rounds of &lt;em&gt;precise&lt;/em&gt; instruction, architectural constraints, and — above all — measurement and verification at every step. Here is the honest iteration log.&lt;/p&gt;
&lt;h2 id="iteration-1--the-first-design-was-correct-and-slow"&gt;Iteration 1 — the first design was correct and slow&lt;/h2&gt;
&lt;p&gt;Asked to make the query independent of N, the model proposed precomputing combination frequencies and answering via lookups. Good instinct — but its first implementation keyed everything on hash maps: &lt;code&gt;map[pair]int&lt;/code&gt;, &lt;code&gt;map[triple]int&lt;/code&gt;, and so on. Correct, yet heavy: hash computation on the hot path, bucket overhead, pointer chasing, GC pressure, unpredictable latency. I gave it a hard constraint: &lt;strong&gt;no hashing on the query path, no per-item allocation.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="iteration-2--dense-arrays-demand-a-bijection"&gt;Iteration 2 — dense arrays demand a bijection&lt;/h2&gt;
&lt;p&gt;Removing maps means storing counts in flat arrays, which forces a new sub-problem: every combination needs a deterministic integer &lt;em&gt;index&lt;/em&gt;. I pinned it precisely — “map each sorted k-combination to a unique index in &lt;code&gt;[0, C(90,k))&lt;/code&gt;, allocation-free” — and the model reached for the &lt;strong&gt;combinatorial number system&lt;/strong&gt; (combinadics). Two details were non-negotiable: convert numbers to zero-based first, and &lt;strong&gt;precompute the binomial coefficients&lt;/strong&gt; with Pascal&amp;rsquo;s recurrence so &lt;code&gt;C(n,k)&lt;/code&gt; is never recomputed in a loop.&lt;/p&gt;
&lt;h2 id="iteration-3--the-inflated-counts-bug-caught-by-the-baseline"&gt;Iteration 3 — the inflated-counts bug (caught by the baseline)&lt;/h2&gt;
&lt;p&gt;The first end-to-end indexed run produced numbers that were obviously too large — the single most important moment in the project, and I only caught it because the scan baseline gave me the invariant &lt;code&gt;scan(draw) == indexed(draw)&lt;/code&gt;, which failed. The cause is a classic trap: &lt;em&gt;subset totals are not exact-match counts&lt;/em&gt;. A 5-match ticket is also counted inside every pair, triple, and quadruple total. I asked the model to derive the correction rather than patch it, pinning the contribution table (a 4-match ticket contributes 6 pairs, 4 triples, 1 quadruple; a 5-match ticket 10/10/5/1). That produced the &lt;strong&gt;exactly-k inclusion–exclusion&lt;/strong&gt; back-substitution, and the invariant held again.&lt;/p&gt;
&lt;h2 id="iteration-4--make-preprocessing-fast-too"&gt;Iteration 4 — make preprocessing fast, too&lt;/h2&gt;
&lt;p&gt;Constant-time queries are worthless if the index takes forever to build, and the naive preprocessing both allocated per combination and pinned four cores to the same hot cache lines. Closing that gap — allocation-free increments, parallel parsing over memory-mapped input, and a per-table choice between private accumulation and atomics (the subject of a whole section below) — is where a lot of the back-and-forth actually went.&lt;/p&gt;
&lt;h2 id="iteration-5--correctness-edge-cases-and-validation-policy"&gt;Iteration 5 — correctness, edge cases, and validation policy&lt;/h2&gt;
&lt;p&gt;Finally I drove a test pass: the tiny hand-checkable dataset, duplicate tickets, malformed lines, and pathological numeric tokens that overflow a hand-written parser. We made validation policy explicit (strict vs. lenient) rather than letting bad input silently corrupt the output.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The real division of labor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The AI supplied the classical building blocks and a lot of code. &lt;strong&gt;I supplied the spec, the invariants, the measurements, and the correctness gate.&lt;/strong&gt; “Make it faster” gets you nowhere; “make the query O(1), dense, allocation-free, with exact-match semantics, and verify it against the baseline” gets you to 1.5 µs. The leverage is in the constraints, not the prompt count.&lt;/p&gt;
&lt;h2 id="solution-2--indexed-counting-the-15-µs-solution"&gt;Solution 2 — indexed counting (the 1.5 µs solution)&lt;/h2&gt;
&lt;p&gt;
&lt;figure id="figure-precompute-then-answer-with-lookups-the-expensive-ticket-to-subset-work-happens-before-the-draw-after-the-draw-four-table-reads-worth-of-subsets-become-t2t5-and-inclusionexclusion-turns-those-into-exact-counts"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Indexed counting pipeline: precompute subset tables from tickets before the draw, then answer with 26 lookups and inclusion–exclusion after it"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/solution-2-indexed-counting-the-1-5-s-solution_hu_cfee16989513b13e.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/solution-2-indexed-counting-the-1-5-s-solution_hu_f192777ed8b92619.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/solution-2-indexed-counting-the-1-5-s-solution_hu_aad1bd5a23ef5a2d.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/solution-2-indexed-counting-the-1-5-s-solution_hu_cfee16989513b13e.webp"
width="760"
height="570"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/solution-2-indexed-counting-the-1-5-s-solution_hu_fae3c5a99dafdb9e.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
Precompute, then answer with lookups. The expensive ticket-to-subset work happens before the draw. After the draw, four table reads&amp;rsquo; worth of subsets become T2–T5, and inclusion–exclusion turns those into exact counts.
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;The indexed solution starts from a different question: can we move &lt;em&gt;almost all&lt;/em&gt; of the work before the draw? Each ticket has five numbers, and from those five we can generate every smaller combination:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(5,2) = 10 pairs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(5,3) = 10 triples
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(5,4) = 5 quadruples
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(5,5) = 1 quintuple
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For every ticket, during preprocessing, we increment a frequency counter for each of those subsets. For the ticket &lt;code&gt;1 2 3 4 5&lt;/code&gt;, the ten pairs &lt;code&gt;(1,2) (1,3) (1,4) (1,5) (2,3) (2,4) (2,5) (3,4) (3,5) (4,5)&lt;/code&gt; each get bumped — and likewise for its triples, quadruples, and the full quintuple.&lt;/p&gt;
&lt;p&gt;After preprocessing, when the draw arrives, we do not touch the player list at all. We generate the &lt;em&gt;draw’s&lt;/em&gt; own 10 pairs, 10 triples, 5 quadruples, and 1 quintuple, and look each of them up in the precomputed tables. That is just &lt;strong&gt;26 lookups&lt;/strong&gt;, so the per-draw complexity is &lt;strong&gt;O(1)&lt;/strong&gt; — it no longer depends on the number of players.&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;Scan mode asks: “which players match this draw?” Indexed mode asks: “how many tickets already contain each subset of this draw?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="dense-arrays-instead-of-hash-maps"&gt;Dense arrays instead of hash maps&lt;/h2&gt;
&lt;p&gt;A straightforward implementation might reach for hash maps. But here the universe is small and fixed — numbers are always 1 to 90 — so the number of possible combinations is finite, predictable, and small enough to store densely:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(90,2) = 4,005
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(90,3) = 117,480
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(90,4) = 2,555,190
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(90,5) = 43,949,268
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;With 32-bit counters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;count2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;005&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;× 4 B ≈ 16 KB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;count3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;117&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;480&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;× 4 B ≈ 470 KB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;count4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;555&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;190&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;× 4 B ≈ 10 MB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;count5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;43&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;949&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;268&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;× 4 B ≈ 176 MB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;───────────────────────────────────&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;total index memory ≈ 186 MB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is a reasonable price for constant-time queries, and dense arrays avoid hashing, bucket overhead, pointer chasing, allocation, and GC pressure. The only remaining puzzle is mapping each combination to a unique array index.&lt;/p&gt;
&lt;h2 id="ranking-combinations-with-combinadics"&gt;Ranking combinations with combinadics&lt;/h2&gt;
&lt;p&gt;Each combination needs a deterministic rank. Convert numbers to zero-based first (&lt;code&gt;1..90 → 0..89&lt;/code&gt;), then for a sorted combination &lt;code&gt;c0 &amp;lt; c1 &amp;lt; … &amp;lt; c(k-1)&lt;/code&gt; compute:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rank = C(c0,1) + C(c1,2) + ... + C(c(k-1),k)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;For a 5-number combination:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rank = C(c0,1) + C(c1,2) + C(c2,3) + C(c3,4) + C(c4,5)Example — combination 1 2 3 4 5, zero-based 0 1 2 3 4:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rank = C(0,1) + C(1,2) + C(2,3) + C(3,4) + C(4,5) → a unique index into count5[]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The rank becomes the array index, and you write &lt;code&gt;count5[rank]++&lt;/code&gt;. No maps, no serialized keys, no per-combination heap allocation — just direct counter increments. The binomial coefficients are themselves precomputed once with Pascal&amp;rsquo;s rule, so ranking never does arithmetic heavier than a few array reads and adds:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;binomial&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;uint64&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initBinomialTable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="c1"&gt;// rank of a sorted, zero-based quintuple c0&amp;lt;c1&amp;lt;c2&amp;lt;c3&amp;lt;c4:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;rank&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// lands in [0, C(90,5))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For a fixed, small combination space, this is exactly the transformation that turns a general-purpose solution into a high-performance one.&lt;/p&gt;
&lt;h2 id="the-subtle-part-exact-match-counts"&gt;The subtle part: exact match counts&lt;/h2&gt;
&lt;p&gt;The indexed approach has one important trap — the bug that bit me in Iteration 3. The raw subset counts are &lt;em&gt;not&lt;/em&gt; the final answer. A ticket matching all five drawn numbers contains 10 pairs, 10 triples, 5 quadruples, and 1 quintuple, so naively summing pair, triple, and quadruple totals counts it many times over. A four-match ticket similarly contributes 6 pairs, 4 triples, 1 quadruple.&lt;/p&gt;
&lt;p&gt;The precomputed tables answer “how many tickets &lt;em&gt;contain&lt;/em&gt; this pair/triple/quadruple?” But the business wants “how many tickets have &lt;em&gt;exactly&lt;/em&gt; 2, 3, 4, or 5 matches?” Those are different questions, and conflating them is the most common bug in combination-indexed solutions.&lt;/p&gt;
&lt;h2 id="from-subset-totals-to-exact-counts"&gt;From subset totals to exact counts&lt;/h2&gt;
&lt;p&gt;
&lt;figure id="figure-why-the-correction-is-exactly-these-coefficients-higher-matches-are-folded-into-every-lower-subset-total-inclusionexclusion-peels-them-back-off-from-the-most-specific-count-m5-down-to-the-least-m2"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Diagram of the inclusion–exclusion correction peeling higher-match contributions off each subset total, from m5 down to m2"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/from-subset-totals-to-exact-counts_hu_1e9d5afad9572cd0.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/from-subset-totals-to-exact-counts_hu_bde308187c5f6ba3.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/from-subset-totals-to-exact-counts_hu_b5983705433853f5.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/from-subset-totals-to-exact-counts_hu_1e9d5afad9572cd0.webp"
width="760"
height="608"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/from-subset-totals-to-exact-counts_hu_fc01840f29a10529.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
Why the correction is exactly these coefficients. Higher matches are folded into every lower subset total. Inclusion–exclusion peels them back off, from the most specific count (m5) down to the least (m2).
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Let &lt;code&gt;T2, T3, T4, T5&lt;/code&gt; be the subset totals for the draw, and &lt;code&gt;m2, m3, m4, m5&lt;/code&gt; the exact match counts we want. Counting how each exact category contributes to each total:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exactly 2 matches: C(2,2)=1 pair
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exactly 3 matches: C(3,2)=3 pairs, C(3,3)=1 triple
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exactly 4 matches: C(4,2)=6 pairs, C(4,3)=4 triples, C(4,4)=1 quad
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exactly 5 matches: C(5,2)=10 pairs, C(5,3)=10 triples, C(5,4)=5 quads, C(5,5)=1 quint
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;So the totals expand as:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; T5 = m5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; T4 = m4 + 5·m5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; T3 = m3 + 4·m4 + 10·m5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; T2 = m2 + 3·m3 + 6·m4 + 10·m5And we solve backward:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; m5 = T5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; m4 = T4 - 5·m5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; m3 = T3 - 4·m4 - 10·m5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; m2 = T2 - 3·m3 - 6·m4 - 10·m5
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the core correction step. Without it, the indexed solution reports inflated counts — a fast wrong answer, which is just a more expensive bug.&lt;/p&gt;
&lt;h2 id="why-the-query-is-constant-time"&gt;Why the query is constant time&lt;/h2&gt;
&lt;p&gt;
&lt;figure id="figure-the-per-draw-query-end-to-end-sort-and-validate-generate-ranks-do-26-lookups-correct-with-inclusionexclusion-print-the-number-of-operations-never-grows-with-the-player-count"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="End-to-end per-draw query: sort and validate, generate ranks, 26 lookups, inclusion–exclusion correction, print — constant work regardless of players"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/why-the-query-is-constant-time_hu_a7060ed37fcff872.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/why-the-query-is-constant-time_hu_a0ba60c589af1bff.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/why-the-query-is-constant-time_hu_5fdd10b87b2d9dd2.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/why-the-query-is-constant-time_hu_a7060ed37fcff872.webp"
width="760"
height="507"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/why-the-query-is-constant-time_hu_cd6d74cb481eda96.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
The per-draw query, end to end. Sort and validate, generate ranks, do 26 lookups, correct with inclusion–exclusion, print. The number of operations never grows with the player count.
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Put it together and the post-draw path is fixed work: generate 10 pairs, 10 triples, 5 quadruples, 1 quintuple; do 26 dense-array lookups to get T2–T5; apply the inclusion–exclusion correction; print. Whether there are 10,000 players, 10 million, or 100 million, the draw performs the same amount of work. In code, the entire correction is four lines — and the subset totals accumulate into &lt;code&gt;int64&lt;/code&gt; even though each stored count is a &lt;code&gt;uint32&lt;/code&gt;, because a sum of subset totals can run larger than any single count:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// total2..total5 are int64 sums of the 26 looked-up counts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;winners5 = int(total5)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;winners4 = int(total4) - 5*winners5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;winners3 = int(total3) - 4*winners4 - 10*winners5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;winners2 = int(total2) - 3*winners3 - 6*winners4 - 10*winners5
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="building-the-index-without-melting-the-cache"&gt;Building the index without melting the cache&lt;/h2&gt;
&lt;p&gt;Here is the part the math never warns you about. On paper, preprocessing is trivial: for each player, bump 26 counters — ten million players, 260 million increments, done. So you write the obvious parallel loop, split the players across four cores, let each core increment the shared tables… and your beautiful O(1) query now sits behind a build that &lt;em&gt;crawls&lt;/em&gt;, because four cores are elbowing each other for the same handful of cache lines.&lt;/p&gt;
&lt;p&gt;The culprit is contention, and it is wildly uneven across the four tables. The pair table has only 4,005 slots but absorbs roughly 100 million increments, so a few cache lines run white-hot — atomic adds there would serialize the entire build on them. The quintuple table is the opposite: 44 million slots sharing only ~10 million increments, so collisions are rare. One strategy cannot be right for both, and the real code splits accordingly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// SMALL + HOT (pairs: 4,005 slots, ~100M hits; triples: 117,480 slots):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// each worker counts into a PRIVATE copy, merged by index shard afterward.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;local.pairs[binomial[a][1]+binomial[b][2]]++
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// LARGE + SPARSE (quads: 2.5M slots; quintuples: 44M slots):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// atomic adds straight into the shared table — collisions are rare, and
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// cloning 176 MB per worker would be absurd.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;atomic.AddUint32(&amp;amp;quintupleCounts[rank], 1)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So the two small tables get per-worker private copies (zero contention while counting, then a quick shard-by-index-range merge), while the two big tables take atomic increments directly into shared memory (no 10 MB-and-176 MB-per-core duplication, and no merge pass at all). The payoff: a build whose peak memory stays roughly flat as cores are added, doing genuinely O(N) work with an O(N/p) span — no rescanning the dataset once per worker.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The lesson hiding in the build&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The algorithm said “increment 26 counters.” The hardware said “watch four cores fight over one cache line.” Choosing private-accumulate versus atomic per table, by size and collision rate, is exactly the kind of decision the AI and I traded notes on — and exactly the kind that never survives into the tidy final write-up. Concurrency correctness is cheap to state and expensive to earn.&lt;/p&gt;
&lt;h2 id="why-the-indexed-solution-is-not-new-mathematics"&gt;Why the indexed solution is not new mathematics&lt;/h2&gt;
&lt;p&gt;It is worth emphasizing, partly out of honesty and partly because it is the most reusable lesson here, that the indexed solution invents nothing. It is a composition of &lt;strong&gt;four classical results&lt;/strong&gt;, none original to it — and they span more than a century.&lt;/p&gt;
&lt;h2 id="1--the-addressing-layer--the-combinatorial-number-system-combinadics"&gt;1 · The addressing layer — the combinatorial number system (combinadics)&lt;/h2&gt;
&lt;p&gt;Representing each sorted k-combination as &lt;code&gt;C(c₁,1) + C(c₂,2) + … + C(c_k,k)&lt;/code&gt; creates a bijection onto &lt;code&gt;{0, …, C(n,k)−1}&lt;/code&gt;. The idea was observed by &lt;strong&gt;D. H. Lehmer (1964)&lt;/strong&gt;, later named the &lt;em&gt;combinatorial number system&lt;/em&gt; by &lt;strong&gt;Donald Knuth&lt;/strong&gt; in &lt;em&gt;The Art of Computer Programming, Volume 4A&lt;/em&gt; (2011), who traces the underlying concept back to &lt;strong&gt;Ernesto Pascal (1887)&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="2--the-arithmetic--binomial-coefficients"&gt;2 · The arithmetic — binomial coefficients&lt;/h2&gt;
&lt;p&gt;Underneath the ranking is nothing more exotic than &lt;code&gt;C(n,k) = C(n−1,k−1) + C(n−1,k)&lt;/code&gt;, commonly associated with &lt;strong&gt;Blaise Pascal (1654)&lt;/strong&gt; though known much earlier in India, Persia, and China. Precomputing this triangle is what makes ranking allocation-free and fast.&lt;/p&gt;
&lt;h2 id="3--the-counting-layer--itemset-support-counting"&gt;3 · The counting layer — itemset support counting&lt;/h2&gt;
&lt;p&gt;Incrementing a counter for every subset contained in every record is essentially &lt;em&gt;itemset support counting&lt;/em&gt;, the core idea behind the &lt;strong&gt;Apriori&lt;/strong&gt; algorithm introduced by &lt;strong&gt;Rakesh Agrawal and Ramakrishnan Srikant (1994)&lt;/strong&gt;. Our “tickets” are transactions; our “combinations” are itemsets.&lt;/p&gt;
&lt;h2 id="4--the-recovery-layer--inclusionexclusion-exactly-k"&gt;4 · The recovery layer — inclusion–exclusion (exactly-k)&lt;/h2&gt;
&lt;p&gt;Recovering exact winner counts from subset totals uses the exactly-k form of inclusion–exclusion:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;m_j = Σ_{k≥j} (−1)^{k−j} · C(k,j) · T_k
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is associated with &lt;strong&gt;Charles Jordan&lt;/strong&gt;, later generalized through the &lt;strong&gt;Schuette–Nesbitt formula&lt;/strong&gt;, all resting on the classical inclusion–exclusion principle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The actual contribution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The solution’s only original move is &lt;em&gt;noticing these four pieces snap together&lt;/em&gt; in a way that makes query cost independent of dataset size. That is engineering, not new mathematics — and recognizing such compositions is precisely where an AI collaborator earns its keep, provided you supply the constraints that force the pieces into place.&lt;/p&gt;
&lt;h2 id="loading-and-parsing-efficiently"&gt;Loading and parsing efficiently&lt;/h2&gt;
&lt;p&gt;For a 10-million-ticket dataset, parsing can dominate startup time. The input is ASCII text, one ticket per line (&lt;code&gt;4 16 22 35 78&lt;/code&gt;, &lt;code&gt;1 2 3 4 5&lt;/code&gt;, …). A naive parser using high-level string splitting and per-line integer parsing is fine for small files but creates real overhead at scale: temporary strings, repeated allocation, expensive conversions, GC pressure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Memory-map the file.&lt;/strong&gt; Rather than copying the ~145 MB into a heap-owned slice the way &lt;code&gt;os.ReadFile&lt;/code&gt; would, the file is &lt;code&gt;mmap&lt;/code&gt;&amp;rsquo;d read-only: the OS faults pages in straight from its cache, in parallel, as the workers touch them — less heap pressure and free cooperation with the page cache (with a plain-read fallback when mapping is unavailable). Because parsing is the dominant cost, wall-clock load time then scales down roughly linearly with core count.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Split work by line boundaries.&lt;/strong&gt; Parsing can run in parallel, but chunks must never split a ticket line. So the file is divided into byte ranges and each boundary is nudged forward to the next newline, guaranteeing every worker parses complete lines and writes to its own output buffer with no shared writes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;File bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌──────────────────────────────────────────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ line line line line line line line line line line line line │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└──────────────────────────────────────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌────────────┐ ┌────────────┐ ┌────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ Worker 0 │ │ Worker 1 │ │ Worker 2 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ whole lines │ │ whole lines │ │ whole lines │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └────────────┘ └────────────┘ └────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each worker emits its own packed buffer; a prefix sum over the per-chunk sizes hands every chunk a disjoint offset in the final slice, so the buffers are stitched together with a parallel copy and no worker ever overlaps another.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then make the inner loop mean.&lt;/strong&gt; Two small tricks keep the byte-level parse tight. A single unsigned subtraction decides whether a byte is a digit (if &lt;code&gt;ch-'0'&lt;/code&gt; wraps above 9, it is not), and the running value is clamped &lt;em&gt;while&lt;/em&gt; it accumulates, so an absurdly long token can never overflow an &lt;code&gt;int&lt;/code&gt; and wrap back into the valid range — it is read in full, then rejected as out of range:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;if ch-&amp;#39;0&amp;#39; &amp;gt; 9 { // unsigned wrap-around: &amp;#34;is this a digit?&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; valid = false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;if value &amp;lt;= maxNumber { // clamp WHILE accumulating: no overflow, ever
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; value = value*10 + int(digit)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;} // 99999999999999999999 is read, then rejected
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The output buffer is sized to a provably safe upper bound — a valid ticket consumes at least nine input bytes and emits five — so it appends without ever risking an overrun.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Validate while parsing.&lt;/strong&gt; A valid ticket has exactly five distinct numbers in 1..90 — cheap to check. But validation &lt;em&gt;policy&lt;/em&gt; is a product decision, not a performance one. &lt;em&gt;Strict mode&lt;/em&gt; rejects the whole input if any line is malformed (right when every line is a real transaction); &lt;em&gt;lenient mode&lt;/em&gt; skips bad lines and reports how many (useful for best-effort ingestion). Make the choice explicit — silent data loss is not an optimization.&lt;/p&gt;
&lt;h2 id="benchmarks--interpretation"&gt;Benchmarks &amp;amp; interpretation&lt;/h2&gt;
&lt;p&gt;I benchmarked both implementations on a deliberately constrained environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;CPU&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;logical CPUs used by the Go runtime&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;RAM&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Go&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;go1.23.2 linux/amd64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Setting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GOMAXPROCS=4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Draws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;| 10 20 30 40 50 | 5 15 25 35 45&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;time-to-READY, per-draw latency, max RSS (/usr/bin/time -v)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A word on how the program runs, since it shapes what these numbers mean. It loads the file, prints &lt;code&gt;READY&lt;/code&gt;, then reads draws from stdin and answers each on stdout, flushing per draw so the consumer gets every result immediately. Diagnostics go to stderr so the answer stream stays clean. Crucially, the per-draw timing brackets &lt;em&gt;only the computation&lt;/em&gt; — not the line parsing or the printing — so a draw latency reflects the calculation itself, independent of I/O.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1 million players:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MODE TIME→READY DRAW1 DRAW2 DRAW3 AVG DRAW MAX RSS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;──────────────────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Scan 23.69 ms 0.884 ms 0.882 ms 0.915 ms 0.894 ms 40.5 MiB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Indexed 177.77 ms 1.472 µs 0.650 µs 0.628 µs 0.917 µs 202.9 MiB
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At 1M players, scan mode is already fast enough — but indexed mode answers roughly &lt;strong&gt;three orders of magnitude faster per draw&lt;/strong&gt;, in exchange for more time before READY and more memory.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;10 million players:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MODE TIME→READY DRAW1 DRAW2 DRAW3 AVG DRAW MAX RSS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;──────────────────────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Scan 210.64 ms 7.978 ms 15.017 ms 13.687 ms 12.227 ms 255.9 MiB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Indexed 1.220 s 2.649 µs 1.044 µs 0.791 µs 1.495 µs 301.5 MiB
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At 10M players, scan mode still performs well because it is streaming compact contiguous memory across four cores — an average of about &lt;strong&gt;12.2 ms&lt;/strong&gt; is very respectable. But indexed mode is in a different class, answering in about &lt;strong&gt;1.5 µs&lt;/strong&gt; after the index is built. The difference is simply: scan touches &lt;em&gt;every ticket&lt;/em&gt; after the draw; indexed touches &lt;em&gt;26 counters&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="putting-the-gap-on-a-bar"&gt;Putting the gap on a bar&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10 million players · average draw latency
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Scan mode 12.227 ms ██████████████████████████████████████████████████
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Indexed mode 0.0015 ms ▏ratio ≈ 12.227 ms / 0.001495 ms ≈ 8,178×
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So indexed mode was roughly &lt;strong&gt;8,000× faster per draw&lt;/strong&gt; here. That does not make scan mode bad — it means the two modes optimize different parts of the system.&lt;/p&gt;
&lt;h2 id="startup-vs-query-latency-the-break-even"&gt;Startup vs. query latency (the break-even)&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;million players&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;before READY ~211 ms | per draw ~12 ms&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Indexed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;before READY ~1.22 s | per draw ~1.5 µs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;extra startup cost of indexed = 1.220 s − 0.211 s ≈ 1.009 s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;per-draw saving = 12.227 ms − 0.0015 ms ≈ 12.2255 ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;break-even = 1.009 s / 12.2255 ms ≈ 83 draws
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you only care about total runtime on this machine and dataset, indexed mode pays back its extra preprocessing after about &lt;strong&gt;83 draws&lt;/strong&gt;. But total runtime is often the wrong metric: if the requirement is &lt;em&gt;minimum latency after the draw&lt;/em&gt;, indexed mode is justified even for a single draw, because it moves work out of the critical path.&lt;/p&gt;
&lt;p&gt;On memory: measured max RSS was ~256 MiB (scan) and ~302 MiB (indexed). The theoretical tables are ~186 MB plus ~50 MB of packed tickets; measured RSS also includes runtime overhead and mapped pages, so it will not match exactly. One small but deliberate touch: once the indexed tables are built, the program drops the ~50 MB ticket buffer (&lt;code&gt;packedTickets = nil&lt;/code&gt;) and forces a GC, since indexed draws read only the tables — keeping steady-state memory closer to the tables themselves. Both modes fit comfortably in 4 GB.&lt;/p&gt;
&lt;h2 id="comparing-the-two-approaches"&gt;Comparing the two approaches&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DIMENSION&lt;/span&gt; &lt;span class="n"&gt;SCAN&lt;/span&gt; &lt;span class="n"&gt;MODE&lt;/span&gt; &lt;span class="n"&gt;INDEXED&lt;/span&gt; &lt;span class="n"&gt;MODE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;────────────────────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Preprocessing&lt;/span&gt; &lt;span class="n"&gt;minimal&lt;/span&gt; &lt;span class="n"&gt;heavier&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Per&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt; &lt;span class="n"&gt;complexity&lt;/span&gt; &lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Memory&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="n"&gt;lower&lt;/span&gt; &lt;span class="n"&gt;higher&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Implementation&lt;/span&gt; &lt;span class="n"&gt;complexity&lt;/span&gt; &lt;span class="n"&gt;lower&lt;/span&gt; &lt;span class="n"&gt;higher&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Scaling&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt; &lt;span class="n"&gt;constant&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Debuggability&lt;/span&gt; &lt;span class="n"&gt;very&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="n"&gt;requires&lt;/span&gt; &lt;span class="n"&gt;stronger&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Best&lt;/span&gt; &lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baseline&lt;/span&gt; &lt;span class="n"&gt;lowest&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt; &lt;span class="n"&gt;latency&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Choose &lt;strong&gt;scan mode&lt;/strong&gt; when memory is constrained, the dataset is modest, draws are rare, or you want a correctness baseline. Choose &lt;strong&gt;indexed mode&lt;/strong&gt; when post-draw latency is critical, there may be many queries, the player count may grow, and enough memory is available. Indexed mode is not “better” in every dimension — only for the one goal of minimizing critical-path latency. That is the engineering tradeoff.&lt;/p&gt;
&lt;h2 id="performance-lessons-from-this-problem"&gt;Performance lessons from this problem&lt;/h2&gt;
&lt;p&gt;The Hungarian lottery problem is small, but it demonstrates several principles that apply to many performance-heavy systems.&lt;/p&gt;
&lt;h2 id="1-optimize-the-critical-path"&gt;1. Optimize the critical path&lt;/h2&gt;
&lt;p&gt;The critical path is the work that must happen while someone is waiting.&lt;/p&gt;
&lt;p&gt;Here, that is the post-draw report.&lt;/p&gt;
&lt;p&gt;Indexing is valuable because it moves work from the critical path into the preparation phase.&lt;/p&gt;
&lt;p&gt;This pattern appears everywhere:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;search engines build inverted indexes before queries&lt;/li&gt;
&lt;li&gt;databases maintain indexes before reads&lt;/li&gt;
&lt;li&gt;compilers build intermediate representations before optimization&lt;/li&gt;
&lt;li&gt;recommendation systems precompute features before serving&lt;/li&gt;
&lt;li&gt;analytics systems aggregate data before dashboard queries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Precomputation is not an implementation detail.&lt;/p&gt;
&lt;p&gt;It is a latency strategy.&lt;/p&gt;
&lt;h2 id="2-data-layout-matters"&gt;2. Data layout matters&lt;/h2&gt;
&lt;p&gt;The scan solution is fast because the data layout is simple and compact.&lt;/p&gt;
&lt;p&gt;A flat byte array is not glamorous, but it works well.&lt;/p&gt;
&lt;p&gt;Many performance problems are not solved by more abstraction. They are solved by fewer cache misses.&lt;/p&gt;
&lt;p&gt;Before optimizing arithmetic, look at memory access patterns.&lt;/p&gt;
&lt;h2 id="3-avoid-allocations-in-hot-paths"&gt;3. Avoid allocations in hot paths&lt;/h2&gt;
&lt;p&gt;The draw path should not allocate per ticket.&lt;/p&gt;
&lt;p&gt;The indexed query path should not allocate per combination.&lt;/p&gt;
&lt;p&gt;The parser should avoid temporary strings where possible.&lt;/p&gt;
&lt;p&gt;Allocation is not always expensive, but repeated allocation in hot paths creates secondary costs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;allocator overhead&lt;/li&gt;
&lt;li&gt;garbage collection&lt;/li&gt;
&lt;li&gt;memory fragmentation&lt;/li&gt;
&lt;li&gt;reduced locality&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="4-measure-phases-separately"&gt;4. Measure phases separately&lt;/h2&gt;
&lt;p&gt;A single “total runtime” number can be misleading.&lt;/p&gt;
&lt;p&gt;For this problem, scan mode may start faster, while indexed mode answers faster.&lt;/p&gt;
&lt;p&gt;Neither number alone tells the full story.&lt;/p&gt;
&lt;p&gt;Performance-heavy systems usually have multiple important latencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cold start&lt;/li&gt;
&lt;li&gt;warm query&lt;/li&gt;
&lt;li&gt;p50 latency&lt;/li&gt;
&lt;li&gt;p99 latency&lt;/li&gt;
&lt;li&gt;peak memory&lt;/li&gt;
&lt;li&gt;throughput&lt;/li&gt;
&lt;li&gt;tail behavior under concurrency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Measure what matters.&lt;/p&gt;
&lt;h2 id="5-keep-a-simple-implementation-around"&gt;5. Keep a simple implementation around&lt;/h2&gt;
&lt;p&gt;The scan implementation is useful even when indexed mode is the default.&lt;/p&gt;
&lt;p&gt;It helps validate correctness.&lt;/p&gt;
&lt;p&gt;It provides a fallback.&lt;/p&gt;
&lt;p&gt;It gives future engineers a simpler mental model.&lt;/p&gt;
&lt;p&gt;In high-performance systems, complexity tends to accumulate. A clear baseline keeps the project grounded.&lt;/p&gt;
&lt;h2 id="6-exact-semantics-matter"&gt;6. Exact semantics matter&lt;/h2&gt;
&lt;p&gt;The indexed solution’s biggest risk is not performance.&lt;/p&gt;
&lt;p&gt;It is semantics.&lt;/p&gt;
&lt;p&gt;Subset counts are not exact match counts.&lt;/p&gt;
&lt;p&gt;That difference is easy to miss, and it completely changes the answer.&lt;/p&gt;
&lt;p&gt;Performance work must never blur the meaning of the result.&lt;/p&gt;
&lt;p&gt;A fast wrong answer is just a more expensive bug.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;This problem starts as a lottery-counting exercise, but it quickly becomes a performance engineering problem.&lt;/p&gt;
&lt;p&gt;The core lesson is not specific to lotteries.&lt;/p&gt;
&lt;p&gt;The broader lesson is:&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;&lt;em&gt;Move work out of the critical path when the problem allows it.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The scan solution shows how far careful data layout and a simple CPU-friendly loop can go.&lt;/p&gt;
&lt;p&gt;The indexed solution shows how preprocessing can change the runtime model completely.&lt;/p&gt;
&lt;p&gt;On a 4 CPU / 4 GB RAM environment, scan mode handled 10 million players with an average draw latency of about &lt;strong&gt;12 ms&lt;/strong&gt;. That is already very fast.&lt;/p&gt;
&lt;p&gt;Indexed mode handled the same 10 million players with an average draw latency of about &lt;strong&gt;1.5 microseconds&lt;/strong&gt; after preprocessing.&lt;/p&gt;
&lt;h2 id="the-bottom-line"&gt;The Bottom Line&lt;/h2&gt;
&lt;p&gt;Ultimately, software engineering rarely hands us a single “correct” path only trade-offs. Both solutions are incredibly valuable; they simply optimize for entirely different dimensions of performance.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sometimes, the right answer is minimalist:&lt;/strong&gt; a flat array paired with a tight, cache-friendly loop for raw speed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sometimes, the right answer is structural:&lt;/strong&gt; a precomputed index that slashes lookup times as complexity scales.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most resilient architectures don’t treat these patterns as rivals. They recognize that the best system often doesn’t force a choice between the two, it harmoniously leverages both.&lt;/p&gt;</description></item><item><title>Open Banking in MENA: Lessons from Production</title><link>https://akemara.com/en/talks/open-banking-mena/</link><pubDate>Sun, 15 Mar 2026 00:00:00 +0000</pubDate><guid>https://akemara.com/en/talks/open-banking-mena/</guid><description>&lt;p&gt;A practical look at building open-banking integrations against real MENA
regulatory regimes: consent flows, strong customer authentication, API
reliability, and the operational realities of running them in production.&lt;/p&gt;</description></item><item><title>Scaling Payments Without Losing a Cent</title><link>https://akemara.com/en/talks/scaling-payments/</link><pubDate>Thu, 20 Nov 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/talks/scaling-payments/</guid><description>&lt;p&gt;How to keep money correct while scaling: idempotency keys, double-entry
ledgers, reconciliation, and the failure modes that bite payment systems under
load — with patterns that hold up in production.&lt;/p&gt;</description></item><item><title>Goodbye Random Inserts: UUIDv7 vs ULID vs UUIDv4 — Unique Identifiers, Performance, and Database Impact</title><link>https://akemara.com/en/blog/uuidv7-ulid-uuidv4/</link><pubDate>Wed, 08 Oct 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/uuidv7-ulid-uuidv4/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Unique identifiers are what makes data models work in modern apps. Choosing the right format for identifiers can affect performance and scalability, whether they are primary keys in a database or IDs in a distributed system. &lt;strong&gt;UUIDs (Universally Unique Identifiers)&lt;/strong&gt; have been popular for a long time because they are unique all over the world without a central coordinator. &lt;strong&gt;UUID version 4 (UUIDv4),&lt;/strong&gt; a randomly generated 128-bit ID, is one of the most popular UUIDs because it is simple and has a very low chance of colliding with another ID. But pure randomness has certain drawbacks in database environments, especially when it comes to indexing and performance. Newer schemes &lt;strong&gt;like ULID (Universally Unique Lexicographically Sortable Identifier)&lt;/strong&gt; and &lt;strong&gt;UUID version 7 (UUIDv7)&lt;/strong&gt; include time elements in the ID, making it possible to sort identifiers lexicographically (by time) and fix the problems with UUIDv4. In this article, we’ll look at the differences between UUIDv4, ULID, and UUIDv7. We’ll also talk about the problems with UUIDv4 in databases, how ULID fixes those problems, and how ULID compares to the new UUIDv7. We will also talk about how each one can be used and how it affects databases (with a focus on MySQL/MariaDB and PostgreSQL), including performance, sorting behavior, entropy, and other practical issues.&lt;/p&gt;
&lt;h2 id="uuidv4-pure-randomness-and-its-challenges"&gt;UUIDv4: Pure Randomness and Its Challenges&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;UUIDv4&lt;/strong&gt; is a 128-bit ID that has 122 bits of randomness. The other bits are used to mark the version and variant. A normal UUIDv4 has 32 hex characters (128 bits) in the 8–4–4–4–12 pattern, like 7f750c37–4d0b-4f0b-b922–37be0b9138a7. The fact that it only uses random bytes and has a very low risk of collisions (almost zero for most applications, given 2¹²² possibilities) are two of its best features. UUIDv4 is great for distributed systems because it makes sure that IDs are unique. Any node can make IDs on its own, and the chance that two nodes will make the same ID is very low. It’s also good when you need things to be unpredictable (like session tokens or security-sensitive IDs) because the randomness makes it impossible to guess IDs in order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt; occurs when UUIDv4 acts as a primary key or index within a database. UUIDv4s are pretty much random, so new inserts don’t go in any natural order. Most relational databases use a B-Tree index for primary keys. When you insert random values, each new value can end up in a random spot in the index. This causes index fragmentation, which means that data pages often split and stay half-empty, and records get spread out across the disk. Using a random UUID as the primary key in MySQL’s InnoDB engine (and MariaDB’s engine, which works in the same way) can make performance drop a lot because it clusters the table by the primary key. InnoDB expects primary keys to be in order and optimizes by filling pages to about 94% full. When random inserts are used, pages may only fill to about 50% full before splitting. This means that a table with random UUID primary keys can grow to almost twice the size of the same data with sequential keys because there are so many pages that are only partially filled. A real-world example from Percona showed that a table with 1 billion rows and UUIDv4 as a CHAR(36) primary key was almost 1 TB in size. However, after being reorganized with an ordered key, it shrank to about 450 GB, or half the size. The random inserts had caused “extreme fragmentation,” which meant that the index pages for the UUID table were only about 50% full, while the index pages for an auto-increment or ordered UUID key were about 90% full.&lt;/p&gt;
&lt;p&gt;UUIDv4’s randomness has significant impacts on performance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Index Fragmentation and Page Splits:&lt;/strong&gt; When you add random items, the B-tree has to split pages in the middle instead of just adding them to the end. A lot of page splits leave a lot of half-empty pages and a “overly large and sparsely packed index.” This slows down insertion throughput and uses up disk space. MySQL with a random UUID primary key will have more index maintenance overhead in systems with a lot of data, which will slow down writes. One explanation says that when values go up, a new page can be added at the end. But when values are random, a split makes two half-full pages that stay half-full for a long time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cache Locality:&lt;/strong&gt; Accessing records that were recently added is less cache-friendly because records with UUIDv4 keys are spread out across the index. The database buffer/cache can’t keep “recent” inserts together because “recent” is everywhere. With sequential inserts, on the other hand, the most recently added data is stored in a small number of contiguous pages that are hot in cache. With UUIDv4, the working set is spread out, which causes cache misses. Because of this, the cache doesn’t work as well. The database might have to load a lot of different pages to work with recent data. When you use a sequential key, MySQL and PostgreSQL will have fewer cache hits and more disk I/O for normal access patterns.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disk I/O:&lt;/strong&gt; Because UUIDv4 is random, the storage engine can’t just add new data; it has to keep finding and rearranging pages. This means that workloads that require a lot of inserts will have more disk I/O. InnoDB will do a lot of writes and splits in the middle of the index. For applications that write a lot, a sequential key is much better for I/O. By avoiding the random insertion pattern, you can often get much faster insert rates. Researchers have found that time-ordered IDs like UUIDv7 or ULID “significantly reduce the disk I/O overhead” for inserts compared to UUIDv4.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;More Storage and Index Overhead:&lt;/strong&gt; UUIDs are big (16 bytes), which makes them more fragmented. They take up even more space (36 bytes plus some overhead) when stored as text (a 36-character string). An INT auto-increment in MySQL is 4 bytes, but a UUID stored in binary is 16 bytes (4 times larger), and a UUID stored as a string is 36 bytes (9 times larger than INT). The primary key value is also in all secondary indexes, so if the primary key is bigger, the secondary indexes will be bigger too. All UUID variants (v4, v7, ULID, etc.) are 128 bits, but it’s best to store them in a small 16-byte binary format to keep them from getting too big. MySQL’s UUID_TO_BIN() with the swap flag, for instance, can store v1/v2 in an ordered way in 16 bytes. There is no inherent order to exploit for v4, but binary(16) still takes up less space than char(36).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall &lt;strong&gt;UUIDv4&lt;/strong&gt; is great for uniqueness and easy to make, but it doesn’t have any sortable elements, which makes it “inefficient in situations where sorting is crucial for optimal performance, such as database keys.” Many teams have found that using UUIDv4 as the main key can make inserts slower and the index bigger over time. PostgreSQL’s experience is a little better than MySQL’s because PostgreSQL doesn’t cluster the table by PK by default (the table heap is separate). This means that random PKs mainly affect the index pages and not the order of the physical rows. Still, PostgreSQL B-tree indexes will have the same problems with page splits and half-full pages when the data is random. In short, the problem with UUIDv4 is that it is completely random, which means the database is not very local.&lt;/p&gt;
&lt;h2 id="ulid-sortable-identifiers-with-embedded-timestamp"&gt;ULID: Sortable Identifiers with Embedded Timestamp&lt;/h2&gt;
&lt;p&gt;Alizain Feerasta came up with &lt;strong&gt;ULID (Universally Unique Lexicographically Sortable Identifier)&lt;/strong&gt; in 2016 to fix the problems with random UUIDs. A ULID is also a 128-bit identifier, but its structure is very different from the one used in UUIDv4. The first &lt;strong&gt;48 bits are a timestamp&lt;/strong&gt; (milliseconds since the Unix epoch), and the last &lt;strong&gt;80 bits are random&lt;/strong&gt;. This design means that ULIDs still have globally unique randomness, but they also have an ordering dimension. This means that if you create ULIDs over time, their lexical order (when compared as strings or bytes) shows when they were created. To put it another way, ULIDs can be sorted by time with millisecond accuracy. A standard ULID is not shown in hex, but in Crockford’s Base32 encoding, which makes a string with 26 characters (for example, 01H9AZ98MT1XY7TK3XN9B0AJP3). This base32 doesn’t have any characters that are hard to understand (like I, L, O, or U) and doesn’t care about case, which makes ULIDs easier for people to read, copy, or put in URLs. The 26-character ULID string is easier to read and takes up less space than the 36-character UUID string (with hyphens) or the 32 hex characters (without hyphens).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How ULID solves the problems with UUIDv4:&lt;/strong&gt; ULIDs make sure that IDs generated in chronological order will sort in chronological order by putting time into the ID. For databases, this has immediate benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Append-like Insertion Pattern:&lt;/strong&gt; When ULIDs are used as primary keys, new inserts usually have larger (later) timestamps, which means they are sorted in a larger order. This means that new rows are usually added to the “end” of the index, though they may be out of order if the clock times are slightly different. The way the index works is much more like an auto-increment or sequential key. This cuts down on page splits and fragmentation a lot. Using a time-ordered key like ULID or UUIDv7 in MySQL/InnoDB “results in fewer page splits and an overall improvement in storage efficiency.” The data stays grouped by when it was made, which is useful for many applications, such as event logs. Inserts become more predictable, and you can often handle them by adding new pages at the end instead of splitting up old ones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better Cache Locality:&lt;/strong&gt; ULIDs make it so that recently added IDs are close together in value, so the most recently added rows are in a small part of the index. The database can keep the end of the index (the most recent pages) in memory, which makes it more likely that recent inserts and queries will hit the cache. UUIDv4 put new rows all over the place, but ULID keeps them in order by time. This makes caching work better. For example, a query for the most recent N records can just scan a range of the index without having to jump around.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Natural Time Sorting:&lt;/strong&gt; ULIDs let you sort by the ID itself to get chronological order. This means that in some cases, you may not need a separate timestamp column for sorting. If your primary key is a ULID, for instance, an ORDER BY id DESC gives you the newest entries first because the highest ULID corresponds to the most recent timestamp. This can make application logic or indexing easier, but keep in mind that if you need full date-time information or queries by date, you probably still want to keep a normal timestamp column for convenience. But the most important thing to remember is that ULID encodes time, which can be very useful for logs or time-series data where getting them back in order is important.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monotonic within a millisecond:&lt;/strong&gt; If more than one ULID is created in the same millisecond, it becomes a problem. The timestamp bits would be the same for both. When the timestamp doesn’t change, the ULID specification says to use a monotonic increment for the random part. This means that if you call the ULID generator more than once in less than a millisecond, it will see that the timestamp hasn’t changed and just add 1 to the random part for each new ID. This makes sure that ULIDs with the same timestamps stay in order (the one generated second will have a slightly bigger random part, making it lexicographically bigger by 1). For instance, if two ULIDs came out at the same ms, you might see 01BX5ZZKBKACTAV9WEVGEMMVS0 followed by 01BX5ZZKBKACTAV9WEVGEMMVS1. The last character went from 0 to 1. This one-step process makes sure that there are no duplicate ULIDs in a single timestamp and keeps the order. In real systems, the chance of needing more than 2⁸⁰ ULIDs in one millisecond is almost zero (that’s 1.2e24, which is a lot more than millions of IDs per second), so overflow isn’t a problem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collision and entropy:&lt;/strong&gt; ULIDs have 80 bits of randomness, which is a little less than the 122 bits of randomness in UUIDv4, but still very large. The 80-bit random part gives 1.2×10²⁴ options per millisecond, even if the timestamp part repeats. For any possible use, the chance of a collision is still almost zero (to put it in perspective, generating 2⁴⁰ ≈ 1 trillion ULIDs gives a collision chance of about 1 in 2⁴⁰, which is very small). One study found that ULIDs have “negligible collision probabilities even at high generation rates,” and that they are actually less likely to collide than a UUIDv7 with 74 random bits. In short, ULIDs are still unique all over the world, just like UUIDv4.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to Read:&lt;/strong&gt; People often say that ULIDs are easier for people to read. The Crockford Base32 alphabet doesn’t use characters that look alike, so the 26-character string (for example, 01GZ9TGXKJQR6Z1YT1BP7FY4WX) is easier to work with than a 36-character UUID with mixed case hex and dashes. ULIDs don’t care about case, which can help you avoid mistakes when you handle them by hand (you don’t have to keep case like you do with hex). They are also safe to use in URLs because they only have letters and numbers. These properties can be helpful if you need to show or log identifiers that someone might read or type. But there is a trade-off: you can either store ULIDs as text (26 characters) or as binary (16 bytes). Comparing strings is a little slower than comparing raw bytes, and the text form is bigger (26 bytes vs. 16 bytes). Some databases, like Postgres, have a UUID type for 16-byte values but not a native ULID type. You can store ULIDs in a CHAR(26) or as binary (BYTEA(16) after changing Base32 to bytes). You can get both the raw 16-byte binary and the encoded string from many ULID libraries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary, ULIDs enable globally unique IDs easier to use and faster to find in databases that deal with time-based data. In many systems that need the benefits of UUIDs without the performance hit of completely random keys, they have become a de facto standard. The bad news is that ULID is not (yet) an official standard from the IETF or ISO, and not all languages or databases support it. Most of the time, you’ll use a library to make ULIDs. The good news is that there are implementations in a lot of languages, like Java, Go, Python, and JavaScript. If you need to, you can also easily implement the spec yourself. Also, keep in mind that ULIDs, like any other time-based ID, show the timestamp. If someone sees a ULID, they can decode the first 10 characters to get the exact time it was made. In a lot of cases, this isn’t a problem (and it can even be helpful), but if you don’t want to give away the age or order of records (for security or privacy reasons), a time-based scheme might not be the best choice. We’ll talk about this more in the section on comparisons.&lt;/p&gt;
&lt;h2 id="uuidv7-the-new-time-ordered-uuid-standard"&gt;UUIDv7: The New Time-Ordered UUID Standard&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;UUIDv7&lt;/strong&gt; is a new member of the UUID family. It was standardised in RFC 9562 (May 2024), which obsoletes RFC 4122, as a time-ordered UUID variant. It borrowed a lot of inspiration from how well ULID did. UUIDv7 formalises the idea of a timestamp plus random bits within the UUID standard. A UUIDv7 is like a ULID in that it has 128 bits and a 48-bit Unix epoch timestamp (in milliseconds) in the most important bits. The rest of the bits are random, but you can add a monotonic counter to make sure there are no duplicates in the same millisecond. UUIDv7 gives 48 bits to the timestamp, 4 bits to the version number (which will be 0111 for version 7), and the rest (128−48−4 = 76 bits) to a mix of random and optional counter bits. After setting aside some bits for the version and the UUID “variant” indicator, there are about 74 bits of pure entropy left over. The variant is the two most important bits of a byte that show an RFC 4122 UUID. For version 7, those bits are set to the standard 10xx pattern. This gives UUIDv7 a little less randomness than ULID’s 80 bits, but it still has a huge space. UUIDv7 can make about 2⁷⁴ unique IDs every millisecond, just like ULID. If you need more than that, the spec says to use a counter and roll over the timestamp if needed. But in reality, 2⁷⁴ (1.8e22) per ms will never be reached. There are almost no collisions; one study found that even at very high rates, the risk of a collision with UUIDv7 is extremely low (and only slightly higher than with ULID).&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;main advantage of UUIDv7&lt;/strong&gt; is that it gives you a UUID that is the same for everyone and is in order by time. You get the benefits of ULID (sequential by time, better database performance) but in a way that works with UUID tools and types that are already out there. The standard way to write a UUIDv7 is as a 36-character hex string with hyphens. For example, 0199c376-ce80–7b08–81fd-5524d4a20ed1 could be a UUIDv7. The 7 in the third group shows that it is version 7. The first part, 0199c376ce80, is a timestamp (0x0199c376ce80 in hex). Sorting &lt;strong&gt;UUIDv7 lexicographically (or comparing as raw bytes)&lt;/strong&gt; is the same as putting them in chronological order because the timestamp is stored in the high bits. If you put UUIDv7 values into a UUID column in PostgreSQL or a BINARY(16) column in MySQL, their natural byte order is time-ordered. This means that an ascending index on that column will automatically time-order your rows. This is the same property that ULID has, but ULID uses a different way of writing it down.&lt;/p&gt;
&lt;p&gt;From a &lt;strong&gt;database point of view&lt;/strong&gt;, UUIDv7 works a lot like ULID:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ordered Inserts:&lt;/strong&gt; New UUIDv7s usually have a timestamp component that gets increasing over time (unless the clock is skewed or the UUIDs are generated out of order), so inserts usually go to the “end” of the index. This gives you a low fragmentation and a high page fill factor, just like ULID. In terms of how well it uses index pages, MySQL’s InnoDB will treat a UUIDv7 primary key almost as well as an AUTO_INCREMENT. To avoid problems with page splitting, the PlanetScale team strongly suggests using “time-based UUIDs such as version 6 or 7.” PostgreSQL also benefits from the better locality, which means that it needs less index rebalancing over time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance:&lt;/strong&gt; In practice, many have found that UUIDv7 and ULID offer better insert and query performance than UUIDv4. Benchmarks often show that inserts happen faster and use less disc space. For example, one study found that using time-ordered IDs (ULID or v7) can speed up generation and lower network overhead in distributed systems. In their tests, the improvements almost doubled the speed of ID generation and cut index storage and traffic by a large amount. You may have different experiences, but what is happening in general is clear: the database “will thank you” for using a sorted UUID.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compatibility:&lt;/strong&gt; UUIDv7 follows the UUID standard, so it can be used anywhere a “UUID” is needed. PostgreSQL now has a UUID data type that can store UUIDv7 without any changes (it’s just a 16-byte value). Support for v7 is being added to the UUID libraries of many languages. As of 2023–2024, there are libraries in Java, Go, Rust, Python, and other languages that can make UUIDv7. Even if native support isn’t great yet, you can make a UUIDv7 by taking the output of a ULID library and formatting it as a UUID. You don’t need a special column type or encoding to store UUIDv7, which is nice because it’s still a UUID. You would save it as a BINARY(16) in MySQL, or you could use UNHEX() on the text version. You can cast from a string to a UUID in Postgres or use extension functions to make v7. There is talk of adding uuid_generate_v7() to the pgcrypto extension or something like that. The UUIDv7 spec is now published as RFC 9562 (May 2024), which obsoletes RFC 4122 — so official support across platforms is arriving quickly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How it is different from ULID:&lt;/strong&gt; UUIDv7 and ULID are similar, but there are some small differences between them:&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standardization:&lt;/strong&gt; UUIDv7 is an official standard (RFC 9562), but ULID is not officially standardized; instead, it is a de facto standard based on how it is used. This means that UUIDv7 has an official specification that all implementations must follow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Representation:&lt;/strong&gt; UUIDv7 uses the common UUID hex format, which is usually shown with hyphens. By default, ULID uses Base32 text. The choice can affect how easy it is to use. ULIDs are shorter and easier for people to read, but UUIDv7 might work better with your tools if they expect hex UUID strings. There is no technical advantage to one encoding over the other, except for length and how easy it is to read. (26-character base32 string vs. 36-character hex string).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage in DB:&lt;/strong&gt; Both are 128-bit when stored in a DB. When you store ULIDs as text, they take up 26 bytes, while UUIDv7 takes up 36 bytes. But the best way to store both is as 16-byte binary files. You can’t directly use a ULID string with PostgreSQL’s UUID type because it’s not in the UUID format. However, you could convert ULID Base32 to hex. UUIDv7 lets you use the UUID type right away. You would use BINARY(16) for both in MySQL. So, UUIDv7 might be a little easier to use with UUID columns that are already there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Randomness:&lt;/strong&gt; ULID has 80 random bits, while UUIDv7 has about 74 random bits, depending on how it is set up with counters. In real life, both give you a lot of uniqueness. The random part of ULID is always 80 bits, but UUIDv7 lets you use some of those bits as a counter if you need to make a lot of IDs in the same millisecond. If an implementation uses, for example, a 12-bit counter and 62 bits of random, then in theory, ULID’s full 80-bit random might have a lower chance of colliding. But, as was said, 62 random bits is more than enough. UUIDv7 implementations that work correctly use a secure PRNG for the random part, which makes them strong. One study found that ULID’s risk of collision was 98% lower than that of UUIDv7 when using a certain scheme with fewer random bits for v7. However, both were practically zero for real-world volumes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Information Exposure:&lt;/strong&gt; Both of them encode the timestamp down to the millisecond. So both show when the ID was made, which could give away the approximate system clock and the order of events. If you need IDs that don’t show the order in which they were created (for example, to keep users from figuring out how many records were made between two IDs or to keep the timestamp of an action hidden), then neither ULID nor UUIDv7 will work. You would go back to UUIDv4 or another scheme that is completely random. This is something to think about when creating identifiers for the public. An internal database primary key might not need to be kept secret, but an external API ID might.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary, UUIDv7 is the best of both worlds for many applications. It has the speed and ordering benefits of ULIDs as well as the ease of use and compatibility of the UUID format. One source says, “with today’s tools, UUIDv7 is the default choice for internal primary keys.” ULID should only be used when human-readability or specific legacy reasons require it, and UUIDv4 should only be used when you can’t expose time info. Now, let’s look at these plans side by side in different ways.&lt;/p&gt;
&lt;h2 id="use-cases-and-recommendations"&gt;Use Cases and Recommendations&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;When to use UUIDv4:&lt;/strong&gt; If you don’t want the ID to have any information (like timestamps or order), and you want it to be unpredictable, UUIDv4 is still a good choice. It’s also everywhere and doesn’t need any extra libraries or special care. Use UUIDv4 for things like security tokens, API keys, or external IDs when you don’t want the creation time to be known. If you don’t want users to be able to guess how many orders your system has by looking at an order ID, a random UUIDv4 is a good choice. Also, in very rare cases of very distributed generation where even a timestamp could cause problems, v4’s randomness is easy to understand. Just know that there is a performance cost in databases. For large internal databases, v4 is usually not the best choice for the reasons given. Using UUIDv4 with auto-increment (for example, having an internal auto-increment key and using the UUID as an external reference) might help with some problems, but it makes things more complicated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use ULID:&lt;/strong&gt; ULID is a great choice for apps that need IDs in a certain order and are easy for people to read. ULIDs make it easier to read logs and URLs if you are logging events or have a system where IDs might show up in logs or URLs. They are also useful on the front end or in places where users can see them because users can tell which of two ULIDs is newer (they sort alphabetically by time). ULIDs are great for event sourcing, logging, messaging systems, and any other place where you want to keep IDs in the order they happened and maybe even use the ID to quickly figure out when an event happened. ULIDs are used to make k-sortable keys in many NoSQL databases (like some key-value stores) and messaging systems. This makes time-range queries faster. One thing to keep in mind is that ULIDs depend on timestamps, so make sure that all systems’ clocks are synchronised to some extent. If a machine’s clock is way off, its ULIDs will show that (even though timestamps that are out of order will eventually sort themselves out by absolute time, you might get some out of order if one server’s clock is a little slow). If you need to work with IDs by hand from time to time, like copying one from one place to another, ULIDs are helpful because they don’t have hyphens and are shorter, which makes mistakes less likely. ULID is a proven alternative if your environment doesn’t yet easily support UUIDv7. Just make sure to use a good library to handle the monotonic increment correctly. Also, if you don’t need to read them directly in the DB, think about storing them as binary for efficiency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use UUIDv7:&lt;/strong&gt; UUIDv7 is a great default for most new systems, especially internal microservices and databases. It works with the UUID standard and gives you almost the same benefits as ULID when it comes to sorting and indexing. If you work in an environment that is quickly adopting new standards or if you want to make sure your IDs will work in the future, UUIDv7 is the best choice. For example, if you’re using PostgreSQL and want to use the native UUID type because it’s easier, you can make UUIDv7 in your app and save it without any problems. UUIDv7 is great for primary keys in databases where you want to avoid the problems that come with auto-increments, like hitting sequence limits or sequence contention, but you still want the speed of sequential inserts. They are also useful in distributed systems where you need timestamps in IDs to help with debugging or sorting events around the world. One important thing to think about is that if the timestamp being embedded is thought to be sensitive (for example, it could show how users behave in some situations), then treat the IDs as if they are sensitive too. But for internal use, like database keys and internal messaging, that’s usually not a problem.&lt;/p&gt;</description></item><item><title>Zero Trust vs. Traditional VPNs: The Future of Perimeterless Secure Access</title><link>https://akemara.com/en/blog/zero-trust-vs-vpn/</link><pubDate>Fri, 26 Sep 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/zero-trust-vs-vpn/</guid><description>&lt;h2 id="1-introduction-reimagining-the-security-perimeter-in-a-world-without-boundaries"&gt;1. Introduction: Reimagining the Security Perimeter in a World Without Boundaries&lt;/h2&gt;
&lt;p&gt;Enterprise security was based on a medieval fortress for decades, and it was easy to understand. The organization’s most important things, like its data, applications, and servers, were all safely stored in a fortified castle with a strong, well-defined network perimeter around it. This “castle-and-moat” security model was based on a simple idea: anything inside the perimeter was safe, and anything outside was not. The main security problem was to keep the gates safe and patrol the walls. But the world of technology has changed so much that this old model is now dangerously out of date.&lt;/p&gt;
&lt;h2 id="why-does-the-old-model-not-work-anymore"&gt;Why Does the Old Model Not Work Anymore?&lt;/h2&gt;
&lt;p&gt;The idea of a corporate perimeter that can be defended has completely fallen apart. Moving to cloud infrastructure, using Software-as-a-Service (SaaS) apps, and making hybrid and remote work more common have all made corporate data and access points spread out all over the world. Employees, partners, and contractors now need to be able to get to company resources from a lot of different devices and networks that aren’t trusted. This phenomenon makes the line between “inside” and “outside” disappear. The castle-and-moat approach doesn’t work anymore because there is no longer a moat to protect. In this situation, using a perimeter-based strategy is like defending a castle with no walls, which makes the crown jewels vulnerable to threats from anywhere, even from inside.&lt;/p&gt;
&lt;h2 id="introducing-zero-trust-the-always-verify-never-trust-way-of-thinking"&gt;Introducing Zero Trust: The “Always Verify, Never Trust” Way of Thinking&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Illustration of Zero Trust checks — identity check, device posture, authentication, and access policy — applied between devices and resources"
srcset="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/introducing-zero-trust-the-always-verify-never-trust-way-of-thinking_hu_307ee5a362c7528d.webp 320w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/introducing-zero-trust-the-always-verify-never-trust-way-of-thinking_hu_182f3cf65137ae30.webp 480w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/introducing-zero-trust-the-always-verify-never-trust-way-of-thinking_hu_15539b96df6858f5.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/introducing-zero-trust-the-always-verify-never-trust-way-of-thinking_hu_307ee5a362c7528d.webp"
width="760"
height="363"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/introducing-zero-trust-the-always-verify-never-trust-way-of-thinking_hu_c6fcd1d434e60dd3.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Zero Trust is an entirely new way of thinking about security that has come about because of this new reality. The National Institute of Standards and Technology (NIST) says that Zero Trust is a security model that does away with implicit trust and requires constant verification for every access request, regardless of where it comes from. It follows the simple but strong rule: “never trust, always verify.” This is not just one product or technology; it is a complete strategic plan for cybersecurity that assumes the network has already been hacked. Zero Trust doesn’t trust a user or device just because it’s “on the corporate network.” Instead, it checks every connection attempt against a wide range of signals, such as user identity, device health, location, and application sensitivity. Access is only granted for the specific resource requested. The main difference between the old and new models is that the old one was based on location (trust is given if a user is “inside” the network), and the new one is based on identity and context (trust is earned for each request, no matter where the user is).&lt;/p&gt;
&lt;h2 id="the-vpns-legacy-a-tool-that-was-needed-in-the-past-decades"&gt;The VPN’s Legacy: A Tool That Was Needed in the past decades&lt;/h2&gt;
&lt;p&gt;The Virtual Private Network (VPN) was the main way for remote employees to access company resources for years. It was the main tool used to extend the company’s moat to remote workers. A VPN made it possible for a remote user’s device to connect to the corporate network by creating a secure, encrypted tunnel over the public internet. This provided them with access to internal resources as if they were sitting at a desk in the office. The VPN was very useful in its time, but it is now a product of the perimeter-based era. Its architecture is fundamentally misaligned with the security and performance demands of the modern, distributed enterprise, and its continued use as a primary access strategy introduces significant and unacceptable risks. Switching from VPN to Zero Trust isn’t just a technological upgrade; it’s a necessary change in thinking and design because the corporate network perimeter is no longer there.&lt;/p&gt;
&lt;h2 id="2-the-architecture-of-trust-a-close-look-at-traditional-vpns"&gt;2. The Architecture of Trust: A Close Look at Traditional VPNs&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Diagram of a traditional VPN: devices connect through a single trusted tunnel into the whole private corporate network"
srcset="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/2-the-architecture-of-trust-a-close-look-at-traditional-vpns_hu_93107a5a0a3da377.webp 320w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/2-the-architecture-of-trust-a-close-look-at-traditional-vpns_hu_3e598a13150bb3f5.webp 480w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/2-the-architecture-of-trust-a-close-look-at-traditional-vpns_hu_f745b0792744e203.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/2-the-architecture-of-trust-a-close-look-at-traditional-vpns_hu_93107a5a0a3da377.webp"
width="760"
height="363"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/2-the-architecture-of-trust-a-close-look-at-traditional-vpns_hu_87a366ac78e8b560.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;To comprehend the inadequacy of the traditional VPN, it is crucial to examine its foundational architecture and the trust model it implements. The VPN worked well for its original purpose, but its design has flaws that modern enemies take advantage of.&lt;/p&gt;
&lt;h2 id="how-vpns-work-the-castle-and-moat-philosophy-and-encrypted-tunnels"&gt;How VPNs Work: The Castle-and-Moat Philosophy and Encrypted Tunnels&lt;/h2&gt;
&lt;p&gt;A VPN makes a secure, private connection over a public, untrusted network like the internet. This is done by a process called tunneling, in which data packets are put inside other packets and encrypted before being sent. A person who is not in the same location as the VPN client software on their device starts the connection. This client sets up an encrypted tunnel to a VPN concentrator or server, which is a special device that sits at the edge of the corporate network.&lt;/p&gt;
&lt;p&gt;The VPN concentrator decrypts the traffic and sends it to the internal corporate network after the user logs in, usually with a username and password, but sometimes with multi-factor authentication. The internal network sees the remote user’s device as being directly connected because it has been given an IP address from the internal network’s address space. 9 This architecture effectively extends the trusted corporate perimeter to the remote endpoint, allowing the user to access files, printers, and internal applications as if they were physically present in the office.&lt;/p&gt;
&lt;h2 id="recognised-strengths-the-part-vpns-played"&gt;Recognised Strengths: The Part VPNs Played&lt;/h2&gt;
&lt;p&gt;This model worked well for a long time. VPNs made it easier and more standardised to work safely from home. Protocols like IPsec protected data in transit by encrypting it so that no one could listen in on it on public Wi-Fi or other unsafe networks. The VPN was a good and logical choice for businesses with a centralised IT infrastructure and a workforce that mostly worked in offices, with only a few users needing to access the network from outside the office.&lt;/p&gt;
&lt;h2 id="the-inherent-flaws-putting-the-modern-business-at-unacceptable-risk"&gt;The Inherent Flaws: Putting the Modern Business at Unacceptable Risk&lt;/h2&gt;
&lt;p&gt;The same design that made VPNs useful in a world where security was based on the perimeter is what makes them dangerous today. The main problem with a VPN is not its encryption but how it builds trust. It mixes up authentication and wide authorization in a dangerous way. Once a user is verified, they are implicitly trusted and given broad access, which opens up a lot of security holes.&lt;/p&gt;
&lt;h2 id="the-keys-to-the-kingdom-problem-too-much-access"&gt;The “Keys to the Kingdom” Problem: Too Much Access&lt;/h2&gt;
&lt;p&gt;The primary problem with a VPN is that it provides people access to the whole network. When the user successfully logs in, they are not connected to just one app; they are put on the network. This provides them access to a tremendous number of resources, much more than they need for their job or task. This “all-or-nothing” approach goes against the Principle of Least Privilege (PoLP) and makes the attack surface enormous and flat. An authenticated user might be able to scan the network, locate services, and try to connect to systems that they don’t have a legitimate reason to access.&lt;/p&gt;
&lt;h2 id="the-threat-of-lateral-movement"&gt;The Threat of Lateral Movement&lt;/h2&gt;
&lt;p&gt;This extra access makes lateral movement possible, which is a key part of most big data breaches. If an attacker gets a user’s VPN credentials through phishing or malware, they can log in to the VPN and get a foothold in the trusted corporate network. Once they get in, they can move around freely, looking through file shares, databases, and other places where they can get more access until they find and steal valuable data. The VPN architecture makes sure that the “blast radius” of a single hacked account is enormous, possibly covering the whole enterprise network.&lt;/p&gt;
&lt;h2 id="user-experience-and-performance"&gt;User Experience and Performance&lt;/h2&gt;
&lt;p&gt;There are also big problems with performance and user experience because of the way VPNs are built. To check for security, traditional VPNs send all user traffic, including traffic going to cloud services like Microsoft or Salesforce, through a central corporate data center. This process adds a lot of latency, which makes applications run more slowly and annoys users. This bad user experience isn’t just annoying; it’s also a direct cause of security risk. When employees think the VPN is slowing things down, they often look for ways around it, like turning off the VPN to get to cloud apps directly or using their own devices and tools that aren’t approved. This “shadow IT” activity takes company data out of the view and control of security teams, making new attack surfaces that aren’t managed.&lt;/p&gt;
&lt;h2 id="the-scalability-limit"&gt;The Scalability Limit&lt;/h2&gt;
&lt;p&gt;Old VPN infrastructure is made up of physical or virtual appliances that can only hold so much data. As an organization’s remote workforce grows, these VPN concentrators slow down the network. Scaling this infrastructure is a difficult and expensive process that often involves buying more hardware, making the network more complicated, and adding a lot of administrative work. This model doesn’t work well with the modern workforce, which is flexible and constantly changing.&lt;/p&gt;
&lt;h2 id="poor-cloudsaas-fitness"&gt;Poor Cloud/SaaS Fitness&lt;/h2&gt;
&lt;p&gt;The main purpose of VPNs was to connect a remote user to a single data center on the same site. They are not built to work in a world where data and apps are spread out over many public and private clouds. It is not efficient to force cloud-bound traffic through an on-premises VPN gateway. This slows down performance and makes the network architecture more complicated than it needs to be.&lt;/p&gt;
&lt;h2 id="3-the-architecture-of-verification-getting-to-know-zero-trust-network-access-ztna"&gt;3. The Architecture of Verification: Getting to Know Zero Trust Network Access (ZTNA)&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Illustration of ZTNA architecture where each access request is verified per application instead of granting network-wide access"
srcset="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/3-the-architecture-of-verification-getting-to-know-zero-trust-network-access-ztna_hu_f3ad5ec3b907f81e.webp 320w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/3-the-architecture-of-verification-getting-to-know-zero-trust-network-access-ztna_hu_7285ead09f1b9458.webp 480w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/3-the-architecture-of-verification-getting-to-know-zero-trust-network-access-ztna_hu_5f33590bd28edd76.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/3-the-architecture-of-verification-getting-to-know-zero-trust-network-access-ztna_hu_f3ad5ec3b907f81e.webp"
width="760"
height="363"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/3-the-architecture-of-verification-getting-to-know-zero-trust-network-access-ztna_hu_4041d0e7fa6f6902.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Zero Trust Network Access (ZTNA) is the modern, architectural successor to the VPN. This is not just a small improvement; it is a complete change in the way things work. It was built from the ground up to allow safe access in a world without boundaries. ZTNA is a way to put the Zero Trust idea into action to keep applications and data safe.&lt;/p&gt;
&lt;h2 id="core-principles-of-ztna-a-new-way-to-control-access"&gt;Core Principles of ZTNA: A New Way to Control Access&lt;/h2&gt;
&lt;p&gt;The security and performance benefits of ZTNA come from a set of core principles that are very different from those of the VPN’s old model. The main new thing about ZTNA’s architecture is that it separates application access from network access. The goal of a VPN is to let people connect to the network, while the goal of a ZTNA solution is to let individuals access applications securely. This separation makes things much more detailed and safe.&lt;/p&gt;
&lt;h2 id="identity-as-the-new-border"&gt;Identity as the New Border&lt;/h2&gt;
&lt;p&gt;Identity is the new security perimeter in a ZTNA model. Decisions about who can access something are no longer based on where a user or device is on the network. Instead, every request for access is checked and approved based on the user’s verified identity, the health of their device, and other signals in the context. Every connection is treated as if it comes from a network that is not trusted and is hostile, even if the user is in a corporate office.&lt;/p&gt;
&lt;h2 id="the-principle-of-least-privilege-polp-in-action"&gt;The Principle of Least Privilege (PoLP) in Action&lt;/h2&gt;
&lt;p&gt;By default, ZTNA follows the principle of least privilege. When a user is provided access, they are not put on the network. Instead, the ZTNA solution sets up a secure, encrypted, one-to-one link between the user’s device and the app or resource they have permission to use. The user can’t see or access any of the other applications or resources, which greatly reduces the attack surface.&lt;/p&gt;
&lt;h2 id="adaptive-trust-and-continuous-verification"&gt;Adaptive Trust and Continuous Verification&lt;/h2&gt;
&lt;p&gt;The VPN only checks your identity once, but ZTNA works on the idea of “never trust, always verify.” It checks the credentials of users and devices for every access request that comes in during a session. This constant checking makes ZTNA a flexible security model. It can take in a steady stream of contextual signals, like user behavior, time of day, geographic location, and device posture, to make access decisions that change based on risk. ZTNA can automatically revoke access or require step-up authentication in real time if a user’s risk profile changes during a session (for example, if malware is found on their device). This procedure is very different from a VPN’s binary, static access model, where a user is either “in” or “out.”&lt;/p&gt;
&lt;h2 id="device-posture-and-health-as-key-signals"&gt;Device Posture and Health as Key Signals&lt;/h2&gt;
&lt;p&gt;ZTNA puts the security posture of the endpoint device right into the access policy. Before allowing a connection, the ZTNA solution can check that the device meets certain security standards, such as having an up-to-date operating system, disk encryption turned on, or an active endpoint detection and response (EDR) agent running. You can block access from devices that don’t obey the rules or have been hacked, which stops them from putting corporate resources at risk.&lt;/p&gt;
&lt;h2 id="strategic-benefits-how-ztna-is-built-for-todays-businesses"&gt;Strategic Benefits: How ZTNA is Built for Today’s Businesses&lt;/h2&gt;
&lt;p&gt;These core principles fix the problems with traditional VPNs and give you many strategic advantages.&lt;/p&gt;
&lt;h2 id="less-area-for-attacks"&gt;Less Area for Attacks&lt;/h2&gt;
&lt;p&gt;ZTNA greatly reduces the attack surface of an enterprise by making internal applications invisible to the public internet and only letting users access certain, approved resources. This idea, which is often called “darkening” the IT infrastructure, means that attackers can’t scan for and find weak applications because the firewall doesn’t have any open ports for incoming traffic.&lt;/p&gt;
&lt;h2 id="getting-rid-of-lateral-movement"&gt;Getting rid of lateral movement&lt;/h2&gt;
&lt;p&gt;Because ZTNA only gives access to applications and not the network, the risk of lateral movement is greatly reduced. Micro-segmentation is built into the architecture. If an attacker gets into a user’s account, the damage is limited to the few apps that that user was given access to. The attacker can’t move to other servers, databases, or network segments because they are never on the underlying network.&lt;/p&gt;
&lt;h2 id="better-performance-and-user-experience"&gt;Better performance and user experience&lt;/h2&gt;
&lt;p&gt;ZTNA makes it possible to connect directly to apps and the cloud in a safe way. This gets rid of the practice of backhauling traffic through a central data center, which slows down performance. Users get faster, lower-latency connections to their apps, whether they are hosted on-site or in the cloud. For the end user, the experience is often seamless and clear. The ZTNA client runs in the background and automatically makes secure connections when needed, which gets rid of the problems that come with manual VPN clients.&lt;/p&gt;
&lt;h2 id="flexibility-and-scalability"&gt;Flexibility and Scalability&lt;/h2&gt;
&lt;p&gt;ZTNA is inherently flexible and scalable because it is mostly built in the cloud. It can easily handle a workforce that is growing, shrinking, or spread out over a wide area without needing expensive and complicated hardware installations. Centralized, policy-based controls make it easy to add new users and applications quickly, which is what modern businesses need to be flexible.&lt;/p&gt;
&lt;h2 id="4-ztna-in-action-a-look-at-the-best-vendor-architectures"&gt;4. ZTNA in Action: A Look at the Best Vendor Architectures&lt;/h2&gt;
&lt;p&gt;To transition from theoretical concepts to practical implementation, it is beneficial to analyze how prominent vendors have executed ZTNA. These real-world architectures show how the main ideas behind Zero Trust can be turned into strong, enterprise-level solutions. The ways that companies like Cloudflare and Microsoft do things show different architectural philosophies, but they all have the same goal: to replace the old VPN model with a more secure, identity-based framework.&lt;/p&gt;
&lt;h2 id="cloudflare-zero-trust-protecting-the-business-on-a-global-edge-network"&gt;Cloudflare Zero Trust: Protecting the Business on a Global Edge Network&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Illustration of Cloudflare Zero Trust enforcing security policies on a global edge network between users and applications"
srcset="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/cloudflare-zero-trust-protecting-the-business-on-a-global-edge-network_hu_1e76847884905f6a.webp 320w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/cloudflare-zero-trust-protecting-the-business-on-a-global-edge-network_hu_fd83c8af9fa534ef.webp 480w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/cloudflare-zero-trust-protecting-the-business-on-a-global-edge-network_hu_de542420da012ae5.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/cloudflare-zero-trust-protecting-the-business-on-a-global-edge-network_hu_1e76847884905f6a.webp"
width="760"
height="501"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/cloudflare-zero-trust-protecting-the-business-on-a-global-edge-network_hu_3717daa3468fdfaa.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Cloudflare’s ZTNA solution is based on its huge global edge network, which covers hundreds of cities around the world. This network-based method aims to improve security and performance by getting policy enforcement as close to the end user as possible.&lt;/p&gt;
&lt;h2 id="a-look-at-architecture"&gt;A Look at Architecture&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Cloudflare Access architecture: users authenticate via identity providers, device posture is checked, and a JWT grants access to SaaS and internal apps"
srcset="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/a-look-at-architecture_hu_bd8a6703a141abf2.webp 320w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/a-look-at-architecture_hu_3fed207073d8ff8a.webp 480w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/a-look-at-architecture_hu_a90ed95c1caab12b.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/a-look-at-architecture_hu_bd8a6703a141abf2.webp"
width="760"
height="281"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/a-look-at-architecture_hu_61498d63cc571290.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;There are two main parts to the Cloudflare architecture. First, the WARP client is put on the devices that the end user uses, like laptops and mobile phones. This client makes a secure, encrypted tunnel from the device to the nearest Cloudflare network location, which is called a Point of Presence (PoP). This tunnel sends all of the device’s traffic to Cloudflare’s security services, which can check it and enforce access rules. Second, cloudflared is a lightweight connector daemon that is installed in the private network for private applications that are hosted in on-premises data centers or private clouds. This connector makes a secure connection to the Cloudflare edge that only goes out. This is an important part of the architecture because it means that no inbound ports need to be opened on the corporate firewall, which keeps the private infrastructure hidden from the public internet.&lt;/p&gt;
&lt;h2 id="important-features-and-method"&gt;Important Features and Method&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Illustration of Cloudflare Zero Trust key features: identity-based access, device posture checks, and application-layer control"
srcset="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/important-features-and-method_hu_9b8dce6a15d7bce1.webp 320w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/important-features-and-method_hu_71cf0c5900247e5a.webp 480w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/important-features-and-method_hu_a5d5d487726e8a99.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/important-features-and-method_hu_9b8dce6a15d7bce1.webp"
width="760"
height="541"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/important-features-and-method_hu_c0b1cc67f1d50b15.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Identity-Based Access:&lt;/strong&gt; Cloudflare Access works perfectly with all of the most popular Identity Providers (IdPs), like Microsoft Entra ID, Okta, and Ping Identity. This lets businesses use their current identity source of truth to create detailed access policies based on things like a simple user’s identity, group membership, and other IdP attributes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device Posture Checks:&lt;/strong&gt; The WARP client checks the security of the endpoint all the time. It can look for things like the OS version, disc encryption, and the status of the firewall. It also works with popular Endpoint Detection and Response (EDR) and Mobile Device Management (MDM) platforms like CrowdStrike and SentinelOne to use their risk signals to make access decisions. This makes sure that only healthy, compliant devices can connect to sensitive resources.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full Application-Layer Control:&lt;/strong&gt; The platform gives users the least amount of access to a lot of resources, such as internal web apps, SaaS apps, and non-web protocols like SSH and RDP. You can give access to web apps without a client (through a secure proxy) or to any TCP/UDP traffic through the WARP client.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The ZTNA is a key part of the Cloudflare One platform, which is a full Security Service Edge (SSE) suite. This means that ZTNA works with a Secure Web Gateway (SWG) to filter internet traffic, a Cloud Access Security Broker (CASB) to protect SaaS apps, and Remote Browser Isolation (RBI) to stop web-based threats. This converged method gives all traffic, from any user to any destination, a single security stack.&lt;/p&gt;
&lt;h2 id="microsoft-global-secure-access-gsa-identity-centric-security-for-the-microsoft-ecosystem"&gt;Microsoft Global Secure Access (GSA): Identity-Centric Security for the Microsoft Ecosystem&lt;/h2&gt;
&lt;p&gt;Microsoft’s identity platform, Microsoft Entra ID, is at the heart of its ZTNA strategy. Microsoft’s SSE solution is called Global Secure Access (GSA). It is meant to give organizations that are heavily invested in the Microsoft ecosystem a seamless and deeply integrated security experience.&lt;/p&gt;
&lt;h2 id="an-overview-of-architecture"&gt;An Overview of Architecture&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Microsoft Global Secure Access architecture: the GSA client reaches Microsoft 365 via Entra Internet Access and private apps via Entra Private Access"
srcset="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/an-overview-of-architecture_hu_9dda3a08631262b2.webp 320w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/an-overview-of-architecture_hu_42c64f523b2bc3de.webp 480w, https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/an-overview-of-architecture_hu_9fc70fbffea428d5.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/an-overview-of-architecture_hu_9dda3a08631262b2.webp"
width="760"
height="300"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/zero-trust-vs-vpn/images/webp/an-overview-of-architecture_hu_4c6cf6bc41e72847.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Microsoft Entra Internet Access and Microsoft Entra Private Access are the two main services that make up GSA. Internet Access is a secure web gateway that knows who you are and protects your connections to Microsoft 365, other SaaS apps, and the public internet. Private Access is the ZTNA part that will take the place of traditional VPNs for getting to private apps on-premises and elsewhere. GSA uses a client installed on endpoints to capture and route traffic through Microsoft’s global network for policy enforcement, just like Cloudflare does.&lt;/p&gt;
&lt;h2 id="main-features-and-method"&gt;Main Features and Method&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deep Entra ID and Conditional Access Integration:&lt;/strong&gt; The best thing about GSA is that it works with Microsoft Entra ID right out of the box. It uses Microsoft’s Conditional Access as its policy engine and Entra ID as its main identity provider. This lets businesses make access policies that are rich in detail and aware of the context and that work the same way for all resources. For instance, a single Conditional Access policy can require both MFA and a compliant device to access both a SaaS app like Salesforce and an older app that is still on-premises.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adaptive Access Policies:&lt;/strong&gt; The GSA can use the huge amount of signal intelligence from the whole Microsoft security stack. It can use Entra ID Protection to get information about user and session risk, Microsoft Intune to check the compliance status of devices, and Microsoft Defender for Endpoint to get threat alerts to make access decisions that change in real time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Updating access to old applications:&lt;/strong&gt; One of the main uses for GSA is to supply legacy apps that don’t natively support protocols like SAML or OIDC safe, modern authentication. Organizations can use Entra Private Access to protect these apps from the internet while still being able to enforce strong controls like MFA and Conditional Access. This means they don’t have to change the old apps themselves.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unified Management and Operations:&lt;/strong&gt; The Microsoft Entra admin center is where you set up and manage the whole GSA solution. This gives IT teams that are already in charge of the Microsoft and Azure environments a single, familiar console for managing identity, access, and now network security policies. This feature makes administration much easier.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These examples of vendors show that the architectural philosophies can be different, even though the end goal is the same: secure, identity-driven access. Cloudflare is the best because it has a powerful global network and a platform that works with any vendor. It focuses on performance and security at the edge. Microsoft is in the lead with its identity platform, which gives organizations that are committed to its ecosystem a deeply integrated experience. This feature shows that switching to ZTNA is not just a matter of comparing features but also a strategic choice about which architectural philosophy fits best with an organization’s current infrastructure and future plans. Furthermore, the fact that both vendors are marketing ZTNA as part of a larger SSE suite shows that the market is moving away from just replacing VPNs and towards a fully converged, cloud-delivered security model called Secure Access Service Edge (SASE).&lt;/p&gt;
&lt;h2 id="5-making-the-business-case-the-strategic-need-for-change"&gt;5. Making the Business Case: The Strategic Need for Change&lt;/h2&gt;
&lt;p&gt;Moving from a well-established technology like VPN to a new one like Zero Trust requires more than just a technical reason; it needs a strong business case that makes sense to top management. There are many reasons for adopting ZTNA that go beyond the IT department. These reasons include lowering risk, making operations more efficient, increasing employee productivity, and making the business more flexible. The return on investment (ROI) is not only the prevention of expensive breaches but also real gains in productivity and savings in operations.&lt;/p&gt;
&lt;h2 id="safety-and-lowering-risks"&gt;Safety and lowering risks&lt;/h2&gt;
&lt;p&gt;This is the strongest and most direct case for ZTNA. ZTNA fixes the biggest security problems with the VPN model by completely changing how access is granted.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reduced Attack Surface:&lt;/strong&gt; ZTNA hides applications from unauthorized users, which greatly reduces the attack surface that attackers can see and use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced the effects of a breach:&lt;/strong&gt; stopping lateral movement is a key risk reduction factor. In a ZTNA environment, an attacker cannot access the network just because a user account or device has been hacked. The breach’s possible damage, or “blast radius,” is limited to the few applications that the compromised identity could access.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Studies have shown that organizations that use Zero Trust save millions of dollars for each data breach compared to those that don’t, and some say that security incidents drop by more than 90% after they start using it.&lt;/p&gt;
&lt;h2 id="how-productive-and-happy-employees-are"&gt;How productive and happy employees are&lt;/h2&gt;
&lt;p&gt;Business leaders will find it very appealing to invest in security that also boosts productivity. Slow speeds, unreliable connections, and a clunky user experience make traditional VPNs frustrating for employees.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Frictionless Access:&lt;/strong&gt; ZTNA gives you access that is always on and works in the background without any problems. This gets rid of a major problem for employees, letting them quickly and reliably connect to the tools they need from anywhere.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better Performance:&lt;/strong&gt; ZTNA greatly improves application performance by getting rid of the backhauling bottleneck and allowing direct-to-cloud connections. This saves time and makes employees more productive. The result makes the security team a facilitator of productivity instead of a blocker.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="digital-transformation-and-business-agility"&gt;Digital Transformation and Business Agility&lt;/h2&gt;
&lt;p&gt;In today’s competitive world, it’s essential for businesses to be flexible. Zero Trust doesn’t stop change; it makes it happen.&lt;/p&gt;
&lt;p&gt;ZTNA is made for hybrid and multi-cloud environments, so it speeds up cloud adoption by giving you the secure, granular, and fast access model you need to move applications and workloads to the cloud with confidence.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Modern Ways of Working:&lt;/strong&gt; A strong ZTNA framework is the key to a safe and useful “work-from-anywhere” plan. It lets businesses give all of their employees safe and reliable access, regardless of where they are or what device they are using, without putting security at risk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Secure Collaboration:&lt;/strong&gt; ZTNA makes it easier to give contractors, partners, and suppliers safe, limited access to your network. Instead of giving them full VPN access, companies can give them limited, temporary access to only the systems they need, which lowers the risk of third parties.&lt;/p&gt;
&lt;h2 id="compliance-and-regulatory-alignment"&gt;Compliance and Regulatory Alignment&lt;/h2&gt;
&lt;p&gt;Modern businesses have a hard time keeping up with the many and constantly changing rules about data protection.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Demonstrable Control:&lt;/strong&gt; ZTNA’s ability to enforce granular, identity-based access controls and provide detailed, application-level audit logs makes it much easier to show that you are following rules like the General Data Protection Regulation (GDPR), the Health Insurance Portability and Accountability Act (HIPAA), and the Payment Card Industry Data Security Standard (PCI-DSS).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easier Audits:&lt;/strong&gt; ZTNA platforms make it simple to see and manage policies from one place, which can cut down on the time and money needed for security and compliance audits.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="getting-through-the-transition-dealing-with-the-challenges"&gt;Getting through the transition: dealing with the challenges&lt;/h2&gt;
&lt;p&gt;The business case is strong, but for the transition to be successful, everyone needs to be aware of the possible challenges. Most of the time, the biggest challenges with adopting Zero Trust are organizational and procedural, not technical.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Migration Strategy and Hybrid Environments:&lt;/strong&gt; It’s not often possible to switch from VPN to ZTNA all at once. Most businesses will need a phased migration plan, which means that ZTNA and VPNs will work together for a while. Moving high-risk user groups (like privileged administrators or third parties) or specific high-value applications to ZTNA first is a common way to start. Over time, the scope can be slowly expanded.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Cultural Shift:&lt;/strong&gt; Putting Zero Trust into action is a big change in culture. It necessitates a transition for the entire organization, especially the IT and security teams, from a deeply entrenched perimeter-based mentality of “trust by default” to a paradigm of “explicit verification.” To make sure everyone understands the “why” behind the new model, there needs to be strong executive support, clear communication, and training.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Legacy Systems and Technical Debt:&lt;/strong&gt; It can be challenging to add ZTNA to old apps that weren’t made to work with modern authentication standards. This problem often needs application connectors and identity proxies that can connect old protocols to the new, identity-based ZTNA framework.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A successful Zero Trust program is not just about putting a product in place. It is also a strategic initiative that needs careful planning, cooperation across departments, and a commitment to improving the organization’s security culture.&lt;/p&gt;
&lt;h2 id="6-conclusion-looking-forward-to-a-safe-flexible-and-productive-future"&gt;6. Conclusion: Looking forward to a safe, flexible, and productive future&lt;/h2&gt;
&lt;p&gt;The time of security based on the perimeter is over. The old-fashioned VPN, which was a reliable technology in the past, can’t keep up with the modern digital business’s needs for security, performance, and flexibility. Its basic model, which allows wide network access based on a single point-in-time authentication, makes the attack surface too big and puts organizations at a high risk of lateral movement by enemies. The performance problems and negative user experience it causes not only slow down work but also make people more likely to take risks.&lt;/p&gt;
&lt;h2 id="summary-why-ztna-is-the-logical-next-step-for-vpns"&gt;Summary: Why ZTNA Is the Logical Next Step for VPNs&lt;/h2&gt;
&lt;p&gt;Zero Trust Network Access (ZTNA) is the clear and logical next step after the VPN. It’s not a small upgrade; it’s an entirely new architecture that works better in a world where cloud computing, SaaS apps, and a distributed workforce are the norm. ZTNA offers a much stronger and more effective security model by moving the security perimeter from the network to identity. The VPN’s biggest problems are directly addressed by its core principles: continuous verification, least-privilege access, and built-in micro-segmentation. The end result is a security framework that makes the attack surface much smaller, stops lateral movement, boosts employee productivity by providing a smooth and fast user experience, and gives the flexibility needed to keep up with digital transformation.&lt;/p&gt;
&lt;h2 id="looking-ahead-trends-in-enterprise-adoption-and-the-future-of-sase"&gt;Looking Ahead: Trends in Enterprise Adoption and the Future of SASE&lt;/h2&gt;
&lt;p&gt;The change from VPN to ZTNA is not just a small trend; it is a major industry movement backed up by market data and analyst forecasts. According to research from companies like Gartner, ZTNA will serve a large majority of new remote access deployments instead of VPN services. In fact, many businesses are expected to stop using traditional VPNs altogether in the next few years. According to current adoption statistics, more than 80% of organizations have already adopted or are in the process of adopting a Zero Trust model, with ZTNA being a key part of that plan. In the end, the switch to ZTNA should be considered the first and most important step on a longer path to a Secure Access Service Edge (SASE) architecture. SASE is a single, unified, cloud-based platform that brings together networking and security services like ZTNA, Secure Web Gateway, CASB, and SD-WAN. Organizations are not only fixing their immediate secure access problems by starting the transition from VPN to ZTNA now; they are also laying the groundwork for a more comprehensive, flexible, and effective security architecture that is built for the future. Enterprise leaders no longer have to ask if they should move beyond the VPN; they need to ask how quickly they can embrace the Zero Trust future.&lt;/p&gt;</description></item><item><title>A Production-Grade Guide to Golang Database Connection Management with MySQL/MariaDB</title><link>https://akemara.com/en/blog/golang-database-connection-management/</link><pubDate>Sat, 20 Sep 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/golang-database-connection-management/</guid><description>&lt;h2 id="the-databasesql-abstraction-its-a-pool-not-a-connection"&gt;The database/sql Abstraction: It’s a Pool, Not a Connection&lt;/h2&gt;
&lt;p&gt;A foundational understanding of Go’s &lt;code&gt;database/sql&lt;/code&gt; package is crucial for building robust, high-performance applications. The most common and consequential misunderstanding is to treat the &lt;code&gt;sql.DB&lt;/code&gt; object as a one-to-one representation of a database connection. It is not. Instead, it is a sophisticated, concurrent-safe manager for a pool of underlying database connections. Establishing this correct mental model is the first step toward mastering database interactions in Go.&lt;/p&gt;
&lt;h2 id="deconstructing-sqldb-the-concurrent-pool-manager"&gt;Deconstructing sql.DB: The Concurrent Pool Manager&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;sql.DB&lt;/code&gt; object, returned by &lt;code&gt;sql.Open&lt;/code&gt;, represents a handle to the database, but more specifically, it manages a pool of active and idle connections. This handle is explicitly designed to be safe for concurrent use by multiple goroutines, which is a cornerstone of modern Go application architecture. The package’s design abstracts away the complexity of connection management; it automatically creates new connections as needed to handle parallel database operations and returns them to the pool when they are no longer required.&lt;/p&gt;
&lt;p&gt;This design implies that a single &lt;code&gt;sql.DB&lt;/code&gt;instance should be created once when the application initializes. This instance should then be shared, typically as a package-level variable or passed through dependency injection, across all parts of the application that require database access. The anti-pattern of calling &lt;code&gt;sql.Open&lt;/code&gt; and &lt;code&gt;db.Close&lt;/code&gt; for each request or function call is inefficient and defeats the entire purpose of connection pooling, as it incurs the high overhead of establishing a new TCP connection and performing a database handshake for every operation.&lt;/p&gt;
&lt;h3 id="code-example-singleton-sqldb-initialization"&gt;Code Example: Singleton sql.DB Initialization&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;database/sql&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/go-sql-driver/mysql&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// db is a package-level variable to hold the database connection pool.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// sql.Open just validates its arguments and prepares the handle.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// It does not create any connections yet.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;mysql&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;user:password@tcp(127.0.0.1:3306)/dbname&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;could not connect to database: %v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// It&amp;#39;s a good practice to set connection pool parameters.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// These will be discussed in detail in Section 3.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetConnMaxLifetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Minute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetMaxOpenConns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetMaxIdleConns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Ping the database to verify the connection is alive.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ping&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;database is not responding: %v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// The application can now use the &amp;#39;db&amp;#39; variable to perform queries.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// The db handle should be closed when the application is shutting down.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// defer db.Close() // In a real app, this would be in a shutdown hook.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-lifecycle-of-a-connection-within-the-pool"&gt;The Lifecycle of a Connection within the Pool&lt;/h2&gt;
&lt;p&gt;When a goroutine executes a query, the pool manager:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Checks for an available idle connection.&lt;/li&gt;
&lt;li&gt;Hands it to the goroutine if found.&lt;/li&gt;
&lt;li&gt;If none are idle and under the limit, creates a new one.&lt;/li&gt;
&lt;li&gt;If the max is reached, blocks until a connection is free.&lt;/li&gt;
&lt;li&gt;Returns the connection to the idle pool after use.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This mechanism of recycling connections dramatically improves performance.&lt;/p&gt;
&lt;h2 id="how-concurrency-works-the-databasesql-promise"&gt;How Concurrency Works: The database/sql Promise&lt;/h2&gt;
&lt;p&gt;The abstraction provided by &lt;code&gt;database/sql&lt;/code&gt; has a profound implication for concurrency: there is no guarantee of connection affinity. Executing two consecutive statements on the same &lt;code&gt;sql.DB&lt;/code&gt; handle may result in those statements running on two different underlying database connections.&lt;/p&gt;
&lt;p&gt;This behavior can be a source of confusion for developers accustomed to a session-based model. For instance, a common mistake is to issue a session-specific command like LOCK TABLES mytable WRITE followed by an INSERT INTO mytable…. If the INSERT statement is executed on a different connection from the one that acquired the lock, it will block indefinitely, waiting for a lock it can never acquire.&lt;/p&gt;
&lt;p&gt;This is not a flaw in the design but a deliberate choice to maximize concurrency and throughput. The package assumes that most database operations are stateless and can be executed by any available connection. For sequences of operations that require a consistent session state, the package provides an explicit mechanism: transactions (&lt;code&gt;sql.Tx&lt;/code&gt;). All operations performed on a transaction object are guaranteed to execute on the same single, underlying connection.&lt;/p&gt;
&lt;p&gt;This design forces a paradigm shift. Developers must move away from thinking in terms of a persistent “session” or “my connection” and instead think in terms of atomic, stateless operations serviced by an anonymous pool of workers. The only time a specific connection’s state should be considered is within the well-defined boundaries of a transaction or a manually managed &lt;code&gt;sql.Conn&lt;/code&gt;. Understanding this distinction is fundamental to preventing a wide range of subtle and difficult-to-debug logical errors in concurrent Go applications.&lt;/p&gt;
&lt;h2 id="establishing-and-configuring-the-mysqlmariadb-connection"&gt;Establishing and Configuring the MySQL/MariaDB Connection&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Connection do&amp;rsquo;s and don&amp;rsquo;ts checklist: call db.Ping after sql.Open, never call sql.Open per query, defer db.Close in main, and never ignore errors"
srcset="https://akemara.com/en/blog/golang-database-connection-management/images/webp/establishing-and-configuring-the-mysql-mariadb-connection_hu_2de0e58e44cb60e9.webp 320w, https://akemara.com/en/blog/golang-database-connection-management/images/webp/establishing-and-configuring-the-mysql-mariadb-connection_hu_9565dfb5e3ea329f.webp 480w, https://akemara.com/en/blog/golang-database-connection-management/images/webp/establishing-and-configuring-the-mysql-mariadb-connection_hu_652d7e7d462b63c4.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/golang-database-connection-management/images/webp/establishing-and-configuring-the-mysql-mariadb-connection_hu_2de0e58e44cb60e9.webp"
width="760"
height="349"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/golang-database-connection-management/images/webp/establishing-and-configuring-the-mysql-mariadb-connection_hu_b65a1e4e8d0a437b.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Successfully connecting a Go application to a MySQL or MariaDB database involves three key steps: selecting a reliable driver, correctly formatting the connection string (Data Source Name or DSN), and verifying the connection. Getting these details right at the outset is essential for building a stable and maintainable application.&lt;/p&gt;
&lt;h2 id="choosing-the-right-driver"&gt;Choosing the Right Driver&lt;/h2&gt;
&lt;p&gt;Go’s database/sql package is an interface; it requires a specific database driver to communicate with a database backend. For MySQL and MariaDB, the community has produced several drivers, but github.com/go-sql-driver/mysql stands out as the de facto standard. It is a pure Go implementation that adheres to the&lt;/p&gt;
&lt;p&gt;database/sql/driver interface, meaning it has no C-library dependencies, which simplifies deployment. Its popularity, active maintenance, and comprehensive feature set make it the recommended choice for most projects.&lt;/p&gt;
&lt;p&gt;To use the driver, it must be imported into the application. The import is done for its side effects — specifically, the driver’s &lt;code&gt;init()&lt;/code&gt; function registers itself with the database/sql package under the name “mysql”. This is why a blank identifier (&lt;code&gt;_&lt;/code&gt;) is used for the import.&lt;/p&gt;
&lt;h2 id="the-data-source-name-dsn-a-deep-dive"&gt;The Data Source Name (DSN): A Deep Dive&lt;/h2&gt;
&lt;p&gt;The DSN is a string that contains all the information the driver needs to connect to the database. The go-sql-driver/mysql uses a standard format&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[username[:password]@][protocol[(address)]]/dbname[?param1=value1&amp;amp;...&amp;amp;paramN=valueN]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A typical DSN for a local MySQL server might look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;myuser:mypassword@tcp(127.0.0.1:3306)/mydatabase?parseTime=true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While the basic components are straightforward, the optional parameters at the end of the DSN are critical for correct application behavior. A misconfiguration here can lead to subtle bugs, particularly with character encoding and timestamp handling.&lt;/p&gt;
&lt;h3 id="important-parameters"&gt;Important Parameters:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;parseTime=true&lt;/code&gt; → converts DATE/DATETIME to &lt;code&gt;time.Time&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loc=Local&lt;/code&gt; → use system time zone&lt;/li&gt;
&lt;li&gt;&lt;code&gt;charset=utf8mb4&lt;/code&gt; → ensures Unicode support&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timeout=5s&lt;/code&gt;, &lt;code&gt;readTimeout=30s&lt;/code&gt;, &lt;code&gt;writeTimeout=30s&lt;/code&gt; → for resilience&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="best-practice-programmatic-dsn-construction"&gt;Best Practice: Programmatic DSN Construction&lt;/h2&gt;
&lt;p&gt;Manually constructing the DSN string by concatenating strings is error-prone and can introduce security vulnerabilities, especially if passwords or other parameters contain special characters that require URL escaping. The go-sql-driver/mysql provides a much safer and more readable alternative: the &lt;code&gt;mysql.Config&lt;/code&gt; struct and its &lt;code&gt;FormatDSN()&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;This approach provides compile-time checking of field names and handles all necessary escaping automatically, making the code more robust and maintainable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Code Example: Building a DSN with mysql.Config&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;database/sql&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/go-sql-driver/mysql&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;connectToDB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Create a new config struct.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;myuser&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Passwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;my$ecr&amp;amp;tP@ssw0rd&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Special characters are handled correctly.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Net&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tcp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;127.0.0.1:3306&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DBName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;mydatabase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AllowNativePasswords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Recommended for modern MySQL versions.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ParseTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Collation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;utf8mb4_unicode_ci&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Loc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Use the system&amp;#39;s local time zone.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ReadTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;WriteTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Use the FormatDSN method to get the connection string.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dsn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FormatDSN&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;mysql&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="verifying-the-connection-the-importance-of-dbping"&gt;Verifying the Connection: The Importance of db.Ping()&lt;/h2&gt;
&lt;p&gt;A critical detail about &lt;strong&gt;sql.Open&lt;/strong&gt; is that it does not immediately establish a connection to the database or validate the DSN parameters (other than basic parsing). It simply prepares the sql.DB handle for future use. This lazy initialization means that configuration errors — such as an incorrect password, a wrong hostname, or a firewall blocking the port — will not be detected until the first actual query is executed.&lt;/p&gt;
&lt;p&gt;This can lead to a situation where an application starts up and appears healthy, only to fail later when it first tries to access the database. To prevent this and ensure a “fail-fast” startup, it is a crucial best practice to call &lt;strong&gt;db.Ping()&lt;/strong&gt; or &lt;strong&gt;db.PingContext()&lt;/strong&gt; immediately after sql.Open. This method explicitly verifies that a connection to the database can be established and that the server is responsive, providing immediate feedback on the validity of the connection configuration.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;connectToDB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Failed to configure database connection: %v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Ping the database to ensure the connection is valid.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ping&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Failed to connect to database: %v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Successfully connected to the database!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="mastering-connection-pool-configuration"&gt;Mastering Connection Pool Configuration&lt;/h2&gt;
&lt;p&gt;The default configuration of the database/sql connection pool is often inadequate for production workloads. Proper tuning is essential for achieving high performance, stability, and resilience. Go provides four key functions to control the pool’s behavior: SetMaxOpenConns, SetMaxIdleConns, SetConnMaxLifetime, and SetConnMaxIdleTime. These settings are not independent; they form an interconnected system that must be configured holistically based on the application’s workload and the surrounding infrastructure.&lt;/p&gt;
&lt;h2 id="setmaxopenconnsn-your-first-line-of-defense"&gt;SetMaxOpenConns(n): Your First Line of Defense&lt;/h2&gt;
&lt;p&gt;This is the most critical setting for pool management. It imposes a hard limit on the total number of connections (both in-use and idle) that the pool can open to the database. By default, this limit is zero, which means unlimited.&lt;/p&gt;
&lt;p&gt;An unlimited number of open connections is dangerous. Under high load, a Go application can spawn thousands of goroutines, each attempting to perform a database query. Without a limit, the application could try to open thousands of connections, quickly overwhelming the database server and exceeding its configured max_connections limit. This leads to “Error 1040: Too many connections” errors, causing cascading failures not only in the offending application but also for any other service connected to that database.&lt;/p&gt;
&lt;p&gt;Setting SetMaxOpenConns acts as a crucial backpressure mechanism. When the limit is reached, any new goroutine requesting a connection will wait (block) until a connection is returned to the pool, effectively throttling the application’s database access to a sustainable rate. The value should be determined based on the database server’s capacity, the number of application instances connecting to it, and the application’s concurrency needs.&lt;/p&gt;
&lt;h2 id="setmaxidleconnsn-balancing-readiness-and-resources"&gt;SetMaxIdleConns(n): Balancing Readiness and Resources&lt;/h2&gt;
&lt;p&gt;This function controls the maximum number of connections that are kept open in the idle pool, waiting to be used. The default value is 2.&lt;/p&gt;
&lt;p&gt;The trade-off here is between performance and resource consumption.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A higher value&lt;/strong&gt; means that during a sudden burst of traffic, connections are readily available, and requests do not have to wait for new ones to be established. This reduces latency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A lower value&lt;/strong&gt; conserves resources on both the application server (memory, file descriptors) and the database server (memory, process slots).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When a connection is needed and the pool has fewer than MaxIdleConns idle connections, it may create new ones even if existing connections are about to be freed. Conversely, when a connection is returned to the pool and there are already MaxIdleConns idle connections, the returned connection is closed and discarded. This constant opening and closing is known as connection churn. To minimize this churn, a common and effective strategy for high-throughput services is to set SetMaxIdleConns to the same value as SetMaxOpenConns. This ensures that once the pool is warmed up, connections are reused as much as possible, reducing the latency associated with creating new ones.&lt;/p&gt;
&lt;h2 id="setconnmaxlifetimed-defeating-firewalls-and-stale-connections"&gt;SetConnMaxLifetime(d): Defeating Firewalls and Stale Connections&lt;/h2&gt;
&lt;p&gt;This function sets the maximum amount of time a connection can be reused since it was created. After this duration, the connection will be marked as expired. When it is next requested from the idle pool, it will be closed, and a new connection will be created to take its place. This closure happens lazily, not in the background.&lt;/p&gt;
&lt;p&gt;This setting is vital for production reliability, especially in environments with stateful firewalls, NAT gateways, or load balancers. These network devices often have idle TCP connection timeouts. If a connection in the Go application’s pool remains idle for longer than this timeout, the network device may silently drop the connection without notifying the application or the database. The pool, unaware that the connection is dead, will hand it out for a subsequent query, which will then fail with a network error like bad connection or connection reset by peer.&lt;/p&gt;
&lt;p&gt;By setting SetConnMaxLifetime to a value shorter than the network idle timeout (e.g., 5 minutes if the firewall timeout is 10 minutes), the application proactively recycles connections before they can become stale, dramatically improving the application’s resilience to network infrastructure behavior.&lt;/p&gt;
&lt;h2 id="setconnmaxidletimed-cleaning-up-after-the-party"&gt;SetConnMaxIdleTime(d): Cleaning Up After the Party&lt;/h2&gt;
&lt;p&gt;Introduced in Go 1.15, this function sets the maximum amount of time a connection can remain idle in the pool before being closed. It complements the other settings by providing a mechanism for the pool to shrink during periods of low activity.&lt;/p&gt;
&lt;p&gt;Consider an application that is configured with a high SetMaxIdleConns to handle peak traffic bursts. During these bursts, the pool will grow to contain many idle connections. In the past, these connections would remain open until they hit their ConnMaxLifetime. With SetConnMaxIdleTime, the application can be configured to close connections that have been idle for, say, 5 minutes. This allows the pool to be elastic, scaling down its resource usage gracefully after a period of high load, which is more efficient for both the application and the database server.&lt;/p&gt;
&lt;h2 id="a-holistic-tuning-strategy"&gt;A Holistic Tuning Strategy&lt;/h2&gt;
&lt;p&gt;These four parameters are not configured in isolation. Their values are interdependent and must be chosen based on a holistic understanding of the application’s workload and its operating environment. There is no universal “best” configuration.&lt;/p&gt;
&lt;p&gt;The process of determining the right values involves considering the constraints and goals of the system. SetMaxOpenConns is a hard ceiling dictated by the database’s capacity and the number of application replicas. SetMaxIdleConns is a performance lever, trading resource usage for lower latency. SetConnMaxLifetime is a reliability feature, dictated by external network infrastructure. Finally, SetConnMaxIdleTime provides elasticity, allowing the pool to adapt to changing load.&lt;/p&gt;
&lt;p&gt;The following table provides reasoned starting points for different application archetypes, illustrating the thought process behind choosing a configuration.&lt;/p&gt;
&lt;h2 id="high-traffic-api-server"&gt;High-Traffic API Server&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SetMaxOpenConns: (DB Max Conns / Instances) * 0.8&lt;/li&gt;
&lt;li&gt;SetMaxIdleConns: Equal to MaxOpenConns&lt;/li&gt;
&lt;li&gt;SetConnMaxLifetime: &amp;lt; Firewall/LB Timeout (e.g., 5m)&lt;/li&gt;
&lt;li&gt;SetConnMaxIdleTime: 5m&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rationale: Prioritizes low latency by keeping a full pool of warm connections, avoiding churn. Proactively recycles connections for reliability and cleans up excess idle connections during lulls in traffic. The 80% factor provides a safety margin.&lt;/p&gt;
&lt;h2 id="batch-processing-worker"&gt;Batch Processing Worker&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SetMaxOpenConns: NumCPU * 2&lt;/li&gt;
&lt;li&gt;SetMaxIdleConns: NumCPU * 2&lt;/li&gt;
&lt;li&gt;SetConnMaxLifetime: &amp;lt; Firewall/LB Timeout (e.g., 5m)&lt;/li&gt;
&lt;li&gt;SetConnMaxIdleTime: 1m&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rationale: Limits database concurrency to the available processing power of the worker. The workload is typically steady rather than bursty, so the idle pool can be the same size and connections can be cleaned up more aggressively if the worker becomes idle.&lt;/p&gt;
&lt;h2 id="low-traffic-internal-tool"&gt;Low-Traffic Internal Tool&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SetMaxOpenConns: 10&lt;/li&gt;
&lt;li&gt;SetMaxIdleConns: 2 (Default)&lt;/li&gt;
&lt;li&gt;SetConnMaxLifetime: 1h&lt;/li&gt;
&lt;li&gt;SetConnMaxIdleTime: 10m&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rationale: A low resource footprint is the primary goal. Connection latency is less critical. A longer connection lifetime is generally acceptable in a stable internal network environment.&lt;/p&gt;
&lt;h2 id="executing-queries-the-developers-day-to-day"&gt;Executing Queries: The Developer’s Day-to-Day&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Lifecycle of a query in four steps: get a connection with QueryContext, iterate rows with Next, always defer rows.Close, then check rows.Err"
srcset="https://akemara.com/en/blog/golang-database-connection-management/images/webp/executing-queries-the-developers-day-to-day_hu_ad8187d36be685e6.webp 320w, https://akemara.com/en/blog/golang-database-connection-management/images/webp/executing-queries-the-developers-day-to-day_hu_66f2e23e62ee7cbd.webp 480w, https://akemara.com/en/blog/golang-database-connection-management/images/webp/executing-queries-the-developers-day-to-day_hu_32e09630e741696c.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/golang-database-connection-management/images/webp/executing-queries-the-developers-day-to-day_hu_ad8187d36be685e6.webp"
width="760"
height="230"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/golang-database-connection-management/images/webp/executing-queries-the-developers-day-to-day_hu_e2e46f48aa5c615d.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Once the connection pool is established and configured, developers interact with the database through a core set of methods for executing queries. Adhering to best practices at this stage is critical for application correctness, security, and preventing resource leaks that can cripple a service under load.&lt;/p&gt;
&lt;h2 id="write-operations-exec-and-execcontext"&gt;Write Operations: Exec() and ExecContext()&lt;/h2&gt;
&lt;p&gt;For SQL statements that do not return rows, such as INSERT, UPDATE, and DELETE, the database/sql package provides the Exec() and ExecContext() methods. The Context variants are preferred as they allow for request cancellation and timeouts. These methods return two values: an sql.Result interface and an error.&lt;/p&gt;
&lt;p&gt;A crucial security practice is to never format query parameters directly into the SQL string using functions like fmt.Sprintf. This creates a SQL injection vulnerability. Instead, use parameter placeholders (&lt;code&gt;?&lt;/code&gt; for go-sql-driver/mysql) in the query string and pass the corresponding values as additional arguments to the Exec method. The driver will safely handle parameterization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Code Examples: Safe Write Operations&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// AddUser inserts a new user into the database.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;AddUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;INSERT INTO users (email, name) VALUES (?,?)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LastInsertId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// UpdateUserEmail changes the email for a given user ID.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;UpdateUserEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newEmail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;UPDATE users SET email =? WHERE id =?&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RowsAffected&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// DeleteUser removes a user from the database.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;DeleteUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DELETE FROM users WHERE id =?&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RowsAffected&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="retrieving-results-lastinsertid-and-rowsaffected"&gt;Retrieving Results: LastInsertId() and RowsAffected()&lt;/h2&gt;
&lt;p&gt;The sql.Result interface returned by Exec provides methods to get feedback on the operation’s outcome.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LastInsertId(): After an INSERT into a table with an AUTO_INCREMENT primary key, this method returns the ID of the newly created row. This is essential for workflows where you need to reference the new record immediately.&lt;/li&gt;
&lt;li&gt;RowsAffected(): For UPDATE or DELETE statements, this method returns the number of rows that were changed by the operation. This can be useful for verifying that the operation had the expected effect.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The code examples in the previous section demonstrate the idiomatic use of these methods.&lt;/p&gt;
&lt;h2 id="read-operations-queryrow-vs-query"&gt;Read Operations: QueryRow() vs. Query()&lt;/h2&gt;
&lt;p&gt;The package provides two primary methods for executing SELECT statements.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;db.QueryRow() (and QueryRowContext) is used when a query is expected to return &lt;strong&gt;at most one row&lt;/strong&gt;. It is ideal for fetching a record by its unique primary key. This method returns a *sql.Row object. A key characteristic is that QueryRow itself does not return an error. Any error that occurs during the query is deferred until the Scan() method is called on the returned *sql.Row. If the query returns no rows, Scan() will return the special error sql.ErrNoRows, which should be explicitly checked for.&lt;/li&gt;
&lt;li&gt;db.Query() (and QueryContext) is used when a query may return &lt;strong&gt;zero or more rows&lt;/strong&gt;. It returns two values: a *sql.Rows object and an error. The error should be checked immediately. The *sql.Rows object is then used to iterate over the result set.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Code Example: QueryRow to Fetch a Single User&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GetUserByID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueryRowContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;SELECT id, email, name FROM users WHERE id =?&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ErrNoRows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// It&amp;#39;s common to return nil, nil to indicate not found.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// A different error occurred.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-golden-rule-defer-rowsclose"&gt;The Golden Rule: defer rows.Close()&lt;/h2&gt;
&lt;p&gt;This is one of the most critical and often overlooked best practices when working with database/sql. When db.Query() is called, it checks out a connection from the pool. That connection remains in use, unavailable to other goroutines, until the associated *sql.Rows object is closed.&lt;/p&gt;
&lt;p&gt;*&lt;em&gt;Failure to close &lt;em&gt;sql.Rows results in a connection leak.&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The underlying connection is never returned to the pool. Under load, an application with a connection leak will quickly exhaust all available connections in its pool (up to MaxOpenConns). Once the pool is exhausted, all subsequent goroutines attempting to query the database will block indefinitely, effectively causing the application to hang.&lt;/p&gt;
&lt;p&gt;The most robust and idiomatic way to ensure *sql.Rows is always closed is to use a defer statement immediately after the db.Query() call. The defer guarantees that rows.Close() will be called when the function exits, regardless of whether it returns normally or due to an error or panic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Code Example: Query with defer rows.Close()&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueryContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;SELECT id, name FROM users&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Not closing rows leads to connection leaks.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GetUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueryContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;SELECT id, email, name FROM users ORDER BY name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// The Golden Rule: Defer Close to prevent connection leaks.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// rows.Close() will be called by the defer statement.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// After the loop, check for errors that may have occurred during iteration.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While iterating through all the rows with rows.Next() until it returns false will also implicitly close the *sql.Rows, relying on this is brittle. If the loop terminates early due to an error during Scan, the rows will not be closed without a defer. Therefore, defer rows.Close() should be considered mandatory.&lt;/p&gt;
&lt;h2 id="advanced-patterns-for-robust-applications"&gt;Advanced Patterns for Robust Applications&lt;/h2&gt;
&lt;p&gt;Beyond basic CRUD operations, production-grade applications require more sophisticated database interaction patterns to ensure data integrity, performance, and reliability. These include atomic operations using transactions, performance optimizations with prepared statements, and strategies for ensuring a service is ready to handle traffic immediately upon startup.&lt;/p&gt;
&lt;h2 id="atomic-operations-with-transactions-sqltx"&gt;Atomic Operations with Transactions (sql.Tx)&lt;/h2&gt;
&lt;p&gt;A database transaction is a sequence of operations performed as a single logical unit of work. The core promise of a transaction is atomicity: either all operations within it succeed and are committed to the database, or none of them are, and the database is left in its original state. This is essential for maintaining data integrity in multi-step processes.&lt;/p&gt;
&lt;p&gt;Go’s database/sql package provides first-class support for transactions via the *sql.Tx object, which is obtained by calling db.Begin() or db.BeginTx(). All operations performed on the *sql.Tx object are guaranteed to execute on the same, single underlying database connection.&lt;/p&gt;
&lt;p&gt;The idiomatic pattern for handling transactions in Go is designed for robustness:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Begin the transaction using tx, err := db.BeginTx(ctx, &amp;amp;sql.TxOptions{…}).&lt;/li&gt;
&lt;li&gt;Immediately defer tx.Rollback(). This acts as a safety net. If any subsequent operation fails and the function returns early, the Rollback is guaranteed to be called, preventing a partial update and releasing the transaction’s resources.&lt;/li&gt;
&lt;li&gt;Perform all database operations using the methods on the tx object (e.g., tx.ExecContext, tx.QueryRowContext). Check for an error after every call and return immediately on failure.&lt;/li&gt;
&lt;li&gt;If all operations succeed, the final step is to explicitly call tx.Commit(). If Commit is successful, the transaction is complete. The deferred Rollback call will then execute but will be a no-op on an already committed transaction and will return sql.ErrTxDone, which is typically ignored.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Code Example: Atomic Bank Transfer&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// TransferFunds atomically moves an amount from one account to another.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;TransferFunds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fromAccountID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;toAccountID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Defer a rollback in case of error.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Rollback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 1. Check if the &amp;#39;from&amp;#39; account has sufficient balance.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueryRowContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;SELECT balance FROM accounts WHERE id =?&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fromAccountID&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;insufficient funds&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 2. Debit the &amp;#39;from&amp;#39; account.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;UPDATE accounts SET balance = balance -? WHERE id =?&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fromAccountID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 3. Credit the &amp;#39;to&amp;#39; account.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;UPDATE accounts SET balance = balance +? WHERE id =?&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;toAccountID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 4. If all operations succeeded, commit the transaction.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="performance-and-security-with-prepared-statements-sqlstmt"&gt;Performance and Security with Prepared Statements (sql.Stmt)&lt;/h2&gt;
&lt;p&gt;A prepared statement is a pre-compiled SQL query that can be executed multiple times with different parameters. They offer two primary benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: By separating the SQL query logic from the data parameters, prepared statements provide robust protection against SQL injection attacks. The database engine treats the parameters as data only, not as executable code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: For queries that are executed frequently, prepared statements can be more efficient. The database parses and creates an execution plan for the query once during the “prepare” phase. Subsequent executions only require sending the parameters, reducing the parsing overhead on the database server.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In Go, a prepared statement is represented by *sql.Stmt and is created using db.Prepare().&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interaction with the Connection Pool&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The behavior of prepared statements is closely tied to the connection pool abstraction. When a statement is prepared using db.Prepare(), it is prepared on a specific connection from the pool. The returned *sql.Stmt object remembers which connection it was prepared on. When stmt.Exec() is called, the library attempts to use that original connection. However, if that connection is busy serving another request, database/sql will transparently grab another available connection from the pool and re-prepare the statement on that new connection before executing it. This automatic re-preparation ensures high availability but can lead to “statement churn” under high concurrency, where the same statement is prepared many times across different connections.&lt;/p&gt;
&lt;p&gt;In contrast, a statement prepared from a transaction (tx.Prepare()) is strictly bound to that transaction’s single connection and will not be re-prepared.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Code Example: Bulk Inserts with a Prepared Statement&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;BulkAddProducts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Prepare the statement once.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PrepareContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;INSERT INTO products(name, price) VALUES(?,?)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Execute the statement multiple times in a loop.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// It&amp;#39;s often better to continue and collect errors, &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// but for simplicity, we return on the first error.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="connection-pre-warming-and-health-checks"&gt;Connection Pre-warming and Health Checks&lt;/h2&gt;
&lt;p&gt;Useful in high-load environments to avoid startup latency.&lt;/p&gt;
&lt;p&gt;When a Go application starts, its connection pool is “cold” — it contains no open connections. The first few incoming requests will experience higher latency as they must wait for new database connections to be established on demand. For services that need to be ready to serve traffic at full speed immediately upon startup, it is a good practice to “pre-warm” the connection pool. This involves proactively creating a number of connections and placing them in the idle pool during the application’s initialization phase.&lt;/p&gt;
&lt;p&gt;A common strategy for pre-warming is to concurrently call db.Ping() a number of times up to the configured MaxIdleConns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Code Example: Pre-warming the Connection Pool&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;warmUpPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WaitGroup&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Ping the database to force a connection to be opened.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PingContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;failed to ping database during warmup: %v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// In main() after setting pool config:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// stats := db.Stats()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// warmUpPool(context.Background(), db, stats.MaxIdleConns)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Additionally, a standard practice for microservices running in orchestrated environments like Kubernetes is to expose a health check endpoint (e.g., /healthz). This endpoint allows the orchestrator to know if the service instance is healthy and ready to receive traffic. A crucial part of this health check is verifying connectivity to downstream dependencies, including the database. A simple and effective database health check is to call db.PingContext() with a short timeout.&lt;/p&gt;
&lt;h2 id="troubleshooting-common-connection-issues"&gt;Troubleshooting Common Connection Issues&lt;/h2&gt;
&lt;p&gt;Even with a well-configured application, issues related to database connectivity can arise in production. Understanding the common symptoms, their underlying causes, and the corresponding solutions is essential for maintaining a reliable service.&lt;/p&gt;
&lt;h2 id="the-symptom-error-1040-too-many-connections"&gt;The Symptom: “Error 1040: Too many connections”&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cause&lt;/strong&gt;: This error comes directly from the MySQL server and indicates that the server’s max_connections limit has been reached. In the context of a Go application, this is almost always caused by the application fleet collectively attempting to open more connections than the server is configured to allow. The primary culprit is an unconfigured or overly generous db.SetMaxOpenConns() setting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Solution&lt;/strong&gt;: The solution is to enforce a sensible limit on the application side. Calculate a safe SetMaxOpenConns value for a single application instance (e.g., (database_max_connections / number_of_instances) * 0.8 to leave a safety margin) and apply this setting consistently across all instances.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-symptom-application-hangs-or-deadlocks"&gt;The Symptom: Application Hangs or Deadlocks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cause&lt;/strong&gt;: The application becomes unresponsive, and requests time out. This often happens when all available connections in the pool are in use, and new goroutines are blocking indefinitely, waiting for a connection to become free. There are two primary causes:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Connection Leaks&lt;/strong&gt;: This is the most common cause. A connection is checked out of the pool to service a db.Query call, but the resulting *sql.Rows is never closed. The connection is never returned to the pool.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Undersized Pool&lt;/strong&gt;: The configured SetMaxOpenConns value is too low for the application’s sustained level of concurrency, leading to legitimate contention for a scarce resource.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Solution&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Audit for Leaks&lt;/strong&gt;: Meticulously review the codebase for any call to db.Query or tx.Query that is not immediately followed by defer rows.Close(). This is the number one suspect.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor Pool Statistics&lt;/strong&gt;: Use db.Stats() to expose the pool’s metrics (e.g., OpenConnections, InUse, Idle, WaitCount). If InUse is consistently equal to MaxOpenConns and WaitCount is climbing, the pool is saturated. This data can confirm a leak or an undersized pool.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tune SetMaxOpenConns&lt;/strong&gt;: If no leaks can be found, and the database server has spare capacity, cautiously increase the SetMaxOpenConns limit.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-symptom-intermittent-bad-connection-or-connection-reset-by-peer-errors"&gt;The Symptom: Intermittent bad connection or connection reset by peer Errors&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cause&lt;/strong&gt;: These errors typically occur after a period of inactivity. They are a classic sign that a stateful network device (like a firewall, NAT gateway, or load balancer) located between the application and the database has silently terminated an idle TCP connection. The Go connection pool, unaware of this external event, attempts to reuse the now-defunct connection, resulting in a network error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Proactively recycle connections before they can be terminated by the network infrastructure. Set db.SetConnMaxLifetime() to a duration that is safely shorter than the network device’s idle timeout. For example, if a firewall has a 10-minute timeout, setting SetConnMaxLifetime(5 * time.Minute) is a robust solution.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-symptom-lock-tables-or-other-session-specific-commands-misbehave"&gt;The Symptom: LOCK TABLES or other session-specific commands misbehave&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cause&lt;/strong&gt;: The developer is operating under the incorrect assumption that consecutive calls on a *sql.DB object will execute on the same database connection. As discussed in Section 1, the database/sql pool manager makes no such guarantee and may use different connections for each statement to maximize concurrency. A command that sets session state (like acquiring a lock or setting a session variable) on one connection will not affect a subsequent command that runs on a different connection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Solution&lt;/strong&gt;: When a sequence of operations requires a consistent, stateful session, it must be wrapped in a transaction. Begin a transaction with db.BeginTx(). All operations executed on the resulting *sql.Tx object are guaranteed to run on the same underlying connection, preserving session state for the duration of the transaction.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Effective database connection management in Go is not an afterthought but a core component of building scalable and resilient applications. The database/sql package provides a powerful, high-level abstraction that, when understood and configured correctly, handles the complexities of concurrency and resource pooling with efficiency.&lt;/p&gt;
&lt;p&gt;The key principles for production-grade database management can be synthesized into three main areas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Adopt the Correct Mental Model&lt;/strong&gt;: The sql.DB object is a concurrent-safe connection pool manager, not a single connection. Operations are, by default, stateless and can execute on any available connection. This understanding is fundamental to avoiding logical errors related to session state and correctly utilizing the package’s concurrency features.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Holistic and Proactive Pool Tuning&lt;/strong&gt;: The default pool settings are insufficient for most production workloads. A deliberate and holistic strategy is required, treating the four main tuning parameters — SetMaxOpenConns, SetMaxIdleConns, SetConnMaxLifetime, and SetConnMaxIdleTime — as an interconnected system. The configuration must account for application workload, database capacity, and the behavior of the surrounding network infrastructure to achieve a balance of performance, stability, and resource efficiency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vigilant Resource Management&lt;/strong&gt;: The developer’s primary responsibility in day-to-day coding is the meticulous management of connection lifecycles. The most critical practice is to always close *sql.Rows with defer rows.Close() to prevent connection leaks, which are a common cause of application failure under load. Similarly, using transactions correctly with the defer tx.Rollback() pattern ensures data integrity during atomic operations.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By internalizing these principles and applying the patterns and configurations detailed in this guide, Go developers can build applications that interact with MySQL/MariaDB databases in a manner that is not only correct and secure but also highly performant and resilient in demanding production environments.&lt;/p&gt;</description></item><item><title>Money20/20 Riyadh 2025: Fintech’s Global Stage Comes to Saudi Arabia</title><link>https://akemara.com/en/talks/money2020-riyadh-2025/</link><pubDate>Sun, 14 Sep 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/talks/money2020-riyadh-2025/</guid><description>&lt;p&gt;Money20/20 — the world’s leading platform for the global “money” ecosystem — is poised to make its Middle East debut in Riyadh, Saudi Arabia, in 2025. Best known as a catalyst for industry-defining announcements, high-profile networking, and thought leadership in fintech, Money20/20 has grown from a single conference into a cornerstone of the financial technology world. This upcoming Riyadh edition arrives at a pivotal moment for Saudi Arabia’s burgeoning fintech sector, aligning with the Kingdom’s Vision 2030 ambitions to become a global financial hub. &lt;strong&gt;The stage is set for a landmark gathering where policy meets innovation, capital meets opportunity, and the future of finance takes shape in the heart of the Middle East.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="what-is-money2020"&gt;What is Money20/20?&lt;/h2&gt;
&lt;p&gt;Money20/20 is a global fintech and financial services conference series essentially the &lt;em&gt;fintech industry’s biggest stage&lt;/em&gt;. Established in 2012 by entrepreneurs Anil Aggarwal and Jonathan Weiner, it began as a flagship event in Las Vegas and quickly became a must-attend gathering for the entire “money ecosystem,” from big banks to disruptive startups. Over the past decade, Money20/20 has expanded worldwide, with annual editions in the United States, Europe, and Asia, and now the Middle East.&lt;/p&gt;
&lt;p&gt;This is no ordinary trade show. Money20/20 has built a reputation as a conference &lt;em&gt;“where deals are done, partnerships are forged, and the agenda for the financial services industry is set”&lt;/em&gt;. Each event convenes influential leaders across banking, payments, fintech startups, Big Tech, regulators, and investors — all in one place — creating a high-energy forum for collaboration and competition. Major product launches and industry announcements often unfold on its stages, and hallway conversations can spark the next big venture. The vibe is more festival than forum, a deliberate break from the stereotypical “snoozefest” business conference model. As the organizers put it, Money20/20 has &lt;em&gt;“solidified its position as the leading global stage where stories unfold and the future is shaped… where the payments, banking, fintech and financial services community unites to create new and disruptive ways to move, manage, spend and borrow money”&lt;/em&gt;. In short, it’s the place you go if you want to know or &lt;strong&gt;decide&lt;/strong&gt; what’s next in money.&lt;/p&gt;
&lt;h2 id="money2020-riyadh-2025-a-fintech-milestone-for-saudi-arabia"&gt;Money20/20 Riyadh 2025: A Fintech Milestone for Saudi Arabia&lt;/h2&gt;
&lt;p&gt;All eyes in the fintech world are now turning to Saudi Arabia, which will host &lt;strong&gt;Money20/20 Middle East&lt;/strong&gt; in September 2025 — marking the conference’s first-ever foray into the region. Over three days (September 15–17, 2025), the brand-new Riyadh Exhibition and Convention Center (RECC) in Malham will welcome what is being billed as the largest fintech gathering the Middle East has ever seen. Organizers expect more than &lt;strong&gt;45,000 attendees&lt;/strong&gt;, including 600 investors and 350+ speakers, representing banks, fintech firms, tech giants, regulators, and startups from around the globe. To put that in perspective, this attendance figure rivals Money20/20’s flagship Las Vegas event and underscores the intense interest in the Middle East’s emerging fintech market.&lt;/p&gt;
&lt;p&gt;Importantly, Money20/20 Riyadh has the full backing of Saudi authorities and industry stakeholders. It is officially hosted by the Kingdom’s Financial Sector Development Program (FSDP) — a government initiative under Vision 2030 — alongside the Saudi Central Bank (SAMA), Capital Market Authority, and Insurance Authority. Co-organizing the event are Fintech Saudi (the national fintech development initiative) and Tahaluf, a Saudi events venture formed in partnership with Informa and the Saudi government. This public-private collaboration signals how strategic the conference is for Saudi Arabia: it’s not just another industry meetup, but a statement about the country’s ambitions. &lt;em&gt;“Hosting Money20/20 Middle East in Saudi Arabia is a powerful reflection of the Kingdom’s role in shaping global fintech,”&lt;/em&gt; says Annabelle Mander of Tahaluf. &lt;em&gt;“We’re proud to create a platform that brings together local pioneers and international leaders to reimagine the financial future, right here in the region.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For Saudi Arabia, landing Money20/20 is about more than prestige — it’s an opportunity to accelerate its fintech ecosystem. Saudi Vision 2030, the nation’s economic transformation plan, explicitly prioritizes fintech as a pillar of diversifying beyond oil. By attracting the world’s top fintech minds to Riyadh, the Kingdom aims to spark new investments, partnerships, and knowledge exchange on home turf. &lt;em&gt;“The decision [to bring Money20/20 to Riyadh] was deeply strategic and almost symbolic of the trajectory the Kingdom’s on,”&lt;/em&gt; explains Steve Durning, the portfolio director for the event, noting that Riyadh’s rise as a financial hub is a direct result of Vision 2030’s reforms. Indeed, Saudi Arabia has spent recent years overhauling regulations, funding startups, and building digital infrastructure to support fintech growth — moves that paved the way for hosting an event of this caliber. The goal, Durning says, is to use Money20/20’s global platform to place the Middle East firmly “at the center of the fintech conversation”. In other words, Saudi Arabia doesn’t want to just participate in fintech’s future; it wants to &lt;strong&gt;co-create&lt;/strong&gt; it. &lt;em&gt;“[This sends] a strong signal to the world that the Middle East isn’t just a consumer of fintech products… [the region] wants to co-create the future of finance and fintech,”&lt;/em&gt; Durning notes.&lt;/p&gt;
&lt;h2 id="fintech-in-saudi-arabia-a-sector-on-the-rise"&gt;Fintech in Saudi Arabia: A Sector on the Rise&lt;/h2&gt;
&lt;p&gt;The excitement around Money20/20 Riyadh comes as Saudi Arabia’s fintech sector experiences an unprecedented boom. A few years ago, fintech in the Kingdom was a nascent scene — in 2018, there were fewer than 20 fintech startups operating locally. Fast forward to today, and there are over &lt;strong&gt;260 fintech companies&lt;/strong&gt; active in Saudi Arabia (as of 2024), far exceeding initial growth targets. In fact, the government’s National Fintech Strategy had aimed for 150 fintech firms by 2024, a benchmark blown away by the rapid development. Now the target is &lt;strong&gt;525 fintech companies by 2030&lt;/strong&gt;, a goal that looks increasingly attainable given the momentum.&lt;/p&gt;
&lt;p&gt;Behind this growth is a potent mix of factors. The government, as part of Vision 2030, has been aggressive in enabling the fintech ecosystem. The Financial Sector Development Program (FSDP) has introduced policies to boost cashless payments and digital banking adoption, while SAMA (the central bank) launched an &lt;strong&gt;open banking framework&lt;/strong&gt; and a Regulatory Sandbox to let fintech innovators test new products in a controlled environment. Fintech Saudi, established by SAMA and the Capital Market Authority, serves as a hub for nurturing startups — running accelerators, hackathons, and even university bootcamps to train talent. Thanks to such support, the number of fintech start-ups in the Kingdom jumped from 89 in 2021 to over 200 by mid-2023.&lt;/p&gt;
&lt;p&gt;Crucially, investors have taken notice. Even amid a global fintech funding downturn in 2023, Saudi fintech companies attracted roughly &lt;strong&gt;$854 million in investment that year — a 270% increase from the previous year**. Local venture capital funds, bolstered by public initiatives, have poured money into homegrown payment apps, lending platforms, and InsurTech ventures. International players are joining in too, drawn by Saudi Arabia’s young, tech-savvy population and high digital adoption rates. The payoff is already visible: in 2022, Saudi’s fintech sector generated around $747 million in revenue. By 2030, it’s expected to contribute about SAR 13.3 billion (~$3.6 billion) to GDP annually. The broader **fintech market size in Saudi Arabia is projected to grow from about $64 billion in 2024 to $87 billion by 2029&lt;/strong&gt;, driven by mass adoption of digital payments and online financial services.&lt;/p&gt;
&lt;p&gt;Government strategy has been a linchpin. Regulations have steadily liberalized to accommodate fintech innovation: for instance, new digital banking licenses have been issued, equity crowdfunding and P2P lending are now permitted, and as noted, open banking is being rolled out to let third-party fintechs securely access bank data (with customer consent) to build new services. Additionally, Saudi authorities have actively encouraged cashless transactions — and it’s working. Today, over 57% of point-of-sale transactions in the Kingdom are non-cash, a figure on track to meet the 70% target by 2030. From smartphone payment apps to instant wires via the SARIE system, digital payments are becoming the norm in daily life.&lt;/p&gt;
&lt;p&gt;It’s no surprise, then, that Saudi Arabia aspires to be the Middle East’s fintech capital, and a significant player globally. The hosting of Money20/20 is validation of that trajectory. As one industry publication put it, &lt;em&gt;“Saudi Arabia’s Vision 2030 is driving a $1 trillion economy with fintech at its core. Money20/20 Middle East is your gateway to that opportunity — where policy meets progress, partnerships take shape, and the money ecosystem gets business done.”&lt;/em&gt; In short, the Kingdom has laid the groundwork to be a &lt;strong&gt;fintech powerhouse&lt;/strong&gt;, and the world is taking notice.&lt;/p&gt;
&lt;h2 id="themes-and-trends-to-watch-at-money2020-riyadh-2025"&gt;Themes and Trends to Watch at Money20/20 Riyadh 2025&lt;/h2&gt;
&lt;p&gt;With Saudi Arabia’s fintech star on the rise, the agenda for Money20/20 Middle East 2025 is expected to tackle the most pertinent themes in finance and technology. The official conference theme is &lt;strong&gt;“Where Money Does Business,”&lt;/strong&gt; signaling a focus on real-world outcomes and deal-making. According to organizers, content will span everything from cutting-edge tech innovations to the policy frameworks needed to govern them. Here are some key topics and trends likely to dominate discussions in Riyadh:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Digital Payments &amp;amp; Cashless Society:&lt;/strong&gt; The push toward digital payments will be front and center. Saudi Arabia’s own progress in reducing cash usage sets a case study for the region. Expect sessions on next-generation payment networks, fintech/payment partnerships with banks, and perhaps the future of &lt;strong&gt;CBDCs&lt;/strong&gt; (central bank digital currencies) in enabling faster, cheaper transactions. With major payment companies like Visa as a founding partner of the event, the dialogue will likely cover everything from contactless retail payments to cross-border remittances and the infrastructure underpinning them (e.g. instant payment rails, SWIFT innovations).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open Banking and Open Finance:&lt;/strong&gt; As the Kingdom rolls out open banking, the conference will examine how freeing up data can spur innovation. &lt;strong&gt;Open banking&lt;/strong&gt; allows licensed fintechs to securely plug into bank systems to offer new services like aggregated account dashboards, alternative lending assessments, or personalized financial management apps. Saudi regulators and banks are on board — SAMA launched an Open Banking Lab in 2023 to accelerate development — so Riyadh 2025 should feature both success stories and remaining challenges in this domain. Broader “open finance” could also be addressed, extending data-sharing beyond banks to insurers, investment platforms, and telecoms for a more holistic fintech ecosystem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artificial Intelligence in Fintech:&lt;/strong&gt; Hardly any tech conference in 2025 can ignore the impact of AI, and Money20/20 is no exception. AI in finance ranges from machine learning algorithms detecting fraud and assessing credit risk, to the recent buzz around &lt;strong&gt;generative AI&lt;/strong&gt; chatbots for customer service or financial advice. The agenda will delve into how AI and data analytics are transforming everything from risk management to personalized banking experiences. Given that the first two content “pillars” of the event focus on governance and regulation of fast-evolving tech like AI, there will be debate on balancing innovation with consumer protection. Global experts — like Standard Chartered’s Chief Data Officer and the CFTC’s acting chairman, who are slated to speak — will bring insights on leveraging AI safely in financial markets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RegTech and Evolving Regulation:&lt;/strong&gt; With fintech innovation comes the need for smart regulation. A major theme will be &lt;strong&gt;RegTech&lt;/strong&gt; — technology that helps institutions comply with regulations more efficiently (think AI-driven compliance checks or blockchain-based audit trails). Saudi Arabia’s regulators have shown willingness to engage with fintech (e.g., the Regulatory Sandbox and inviting international regulators to Money20/20), so expect robust discussions on harmonizing rules across borders and regulating emerging sectors like crypto assets and digital banking. The presence of high-profile regulators like the UK Financial Conduct Authority’s innovation head and the U.S. CFTC’s chair at the event underscores the emphasis on regulatory collaboration. Topics such as cross-border regulatory alignment, data privacy laws, and risk management for new technologies will likely feature on stage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fintech Investment &amp;amp; Start-up Growth:&lt;/strong&gt; Given the investor turnout, Money20/20 Riyadh will double as a marketplace for venture capital in the region. Panels and forums will explore the &lt;strong&gt;investment landscape&lt;/strong&gt; — from venture funding trends in fintech to the role of government funds and bank accelerators in nurturing startups. Saudi officials like Nabeel Koshak, CEO of Saudi Venture Capital Co., are on the speaker roster to discuss the burgeoning start-up scene and funding ecosystem. Moreover, a dedicated forum called &lt;em&gt;Venturescape&lt;/em&gt; will connect global VCs, family offices, and founders for deal-making, reflecting the event’s “money does business” mantra. The conference also features a startup pitch competition (MoneySurge 20/20) offering $400,000 in prize funding, with AI-driven matchmaking to link entrepreneurs with investors. All of this signals that beyond talk, the event is set up to catalyze concrete investments in new ventures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedded Finance and Blockchain:&lt;/strong&gt; In line with global trends, expect talk on embedding financial services into other platforms (so-called &lt;strong&gt;embedded finance&lt;/strong&gt; — like getting loans or insurance right inside a non-bank app). The Middle East’s large retail and telecom players are keen on this, and fintechs can enable it. Blockchain and digital assets will also be on the agenda. While Gulf regulators approach cryptocurrencies cautiously, they are enthusiastic about blockchain for things like cross-border trade finance and interbank settlements. A notable speaker is Ant Group’s international president, who will likely discuss cross-border payments and perhaps blockchain innovations from Asia. With companies like Ripple, Ethereum-based startups, and traditional financial institutions in attendance, debates may cover the pragmatic uses of blockchain and the path toward digital currencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Financial Inclusion &amp;amp; Purpose-Driven Innovation:&lt;/strong&gt; In emerging markets, fintech’s promise is closely tied to expanding financial access. One of the content pillars is the &lt;em&gt;“rise of purpose-driven start-ups,”&lt;/em&gt; highlighting fintech solutions that aim for social impact alongside profit. Given Saudi Arabia’s large youth population and the broader Middle East/Africa context, we can expect discussion on fintech’s role in improving financial literacy, SME lending, remittances for expatriate communities, and Islamic finance innovation. Deemah AlYahya of the Digital Cooperation Organization (which focuses on digital economy inclusion across developing nations) will speak on how fintech can spur growth in uncertain economic times. &lt;strong&gt;Financial inclusion&lt;/strong&gt; — reaching the unbanked or underbanked with technology — is likely to be a recurring theme, especially as regional policymakers see fintech as a tool for socio-economic development.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-road-ahead-a-new-fintech-frontier-in-riyadh"&gt;The Road Ahead: A New Fintech Frontier in Riyadh&lt;/h2&gt;
&lt;p&gt;As Money20/20 Middle East 2025 approaches, anticipation is running high. The event’s slogan, “Where Money Does Business,” feels particularly apt in the Saudi context — a country that is actively &lt;em&gt;doing business&lt;/em&gt; to transform itself into a global fintech nexus. Over the span of a few days, Riyadh will host an unprecedented convergence of East and West in finance: Wall Street bankers mingling with Gulf investors, Asian fintech unicorns sharing ideas with European regulators, and Middle Eastern start-ups showcasing their solutions to the world. The conference is expected to produce not just dialogue, but deals and initiatives that will reverberate long after the lights go down at RECC Malham.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Money20/20 conferences are known for high-profile keynotes and packed halls. The Riyadh 2025 edition, with over 45,000 expected attendees, will be one of the largest fintech gatherings ever in the region.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For Saudi Arabia, the true measure of success will be what comes next. By putting itself at the center of the fintech map with Money20/20, the Kingdom is aiming to fast-track the outcomes Vision 2030 seeks: diversification of the economy, an influx of foreign investment, and a homegrown tech sector that generates jobs and innovation. Already, the signs are encouraging — fintech is &lt;strong&gt;no longer a fringe experiment in Saudi Arabia, but a mainstream industry&lt;/strong&gt; backed by big banks, regulators, and an eager customer base. Hosting Money20/20 will only reinforce that trend by exposing local entrepreneurs to global best practices and giving international players a first-hand look at opportunities in the Saudi market.&lt;/p&gt;
&lt;p&gt;In the broader scheme, Money20/20 Riyadh 2025 represents a historic moment of financial &lt;em&gt;convergence&lt;/em&gt;. It’s a chance for the Middle East to stake its claim in shaping the next decade of fintech, not just as consumers of technology but as co-creators and innovators on the world stage. As Steve Durning noted, this event sends a message that the region intends to help co-write the future of global finance. By the conference’s end, we’ll likely see new partnerships formed, investments pledged, and perhaps policy understandings reached — all born from face-to-face encounters in Riyadh. In the fast-moving fintech arena, such outcomes are invaluable.&lt;/p&gt;
&lt;p&gt;In a sense, the story of Money20/20 Riyadh is the story of fintech’s evolution itself: once centered in traditional hubs like New York or London, it’s now truly global, with innovation rising from all corners. As the curtain rises on Money20/20 Middle East 2025, Saudi Arabia is stepping confidently into the spotlight, ready to show the world how the future of money is being imagined — and built — in Riyadh.&lt;/p&gt;</description></item><item><title>Open Banking in Saudi: A Practical Startup Playbook</title><link>https://akemara.com/en/talks/open-banking-saudi-playbook/</link><pubDate>Tue, 09 Sep 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/talks/open-banking-saudi-playbook/</guid><description>
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;Data access and user consent are unlocking new fintech products in Saudi Arabia but strict compliance is non‑negotiable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="why-open-banking-matters-in-saudi-arabia"&gt;Why Open Banking Matters in Saudi Arabia&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Illustration of open banking benefits in Saudi Arabia: personalized services, convenience, a level playing field, and fintech startups partnering with banks"
srcset="https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/why-open-banking-matters-in-saudi-arabia_hu_ea4e7cfa98c94591.webp 320w, https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/why-open-banking-matters-in-saudi-arabia_hu_dddf34f3ea7cbed6.webp 480w, https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/why-open-banking-matters-in-saudi-arabia_hu_23837728408ab92a.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/why-open-banking-matters-in-saudi-arabia_hu_ea4e7cfa98c94591.webp"
width="760"
height="407"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/why-open-banking-matters-in-saudi-arabia_hu_326d5bf59265a32f.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Open Banking is transforming Saudi Arabia’s financial landscape by shifting control of financial data to consumers and enabling fintech innovation. Through secure &lt;strong&gt;APIs&lt;/strong&gt;, banks in the Kingdom can share customer account data with licensed third-party providers &lt;strong&gt;only with the customer’s explicit consent&lt;/strong&gt;. This customer-centric model empowers individuals and businesses to leverage their own banking data — previously siloed in banks — to access &lt;strong&gt;tailored financial services&lt;/strong&gt; and new products. Key reasons open banking matters in KSA include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data Access and Aggregation:&lt;/strong&gt; Fintech apps can aggregate accounts from multiple banks to give users a unified view of their finances. For example, account information services (AIS) allow a &lt;strong&gt;complete, personalized view&lt;/strong&gt; of finances across different institutions. This transparency helps customers make informed decisions and improves financial literacy in a country with 98% internet usage and a tech-savvy youth population.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customer Consent and Control:&lt;/strong&gt; Open Banking flips the data ownership model — customers are in control of who accesses their financial data and for what purpose. All data sharing requires &lt;strong&gt;explicit, informed consent&lt;/strong&gt;, building user trust through transparency. Consumers can securely share information with chosen apps (budgeting tools, investment platforms, etc.) and &lt;strong&gt;regain control&lt;/strong&gt; over their data, as opposed to banks being the sole custodians.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product Innovation and Competition:&lt;/strong&gt; By opening up data, SAMA (the Saudi Central Bank) fosters a &lt;strong&gt;wave of fintech innovation&lt;/strong&gt;. Startups can develop new solutions like personal finance management (PFM) tools for multi-bank account aggregation, real-time account aggregation, &lt;strong&gt;Buy Now Pay Later (BNPL)&lt;/strong&gt; services, digital lending offerings, and SME financing solutions that were not possible before. Banks, fintechs, and even non-bank businesses can collaborate to create products that improve customer experience, driving competition and diversification of the financial sector in line with Saudi Vision 2030.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, open banking in Saudi Arabia means &lt;strong&gt;consent-driven data sharing&lt;/strong&gt; that fuels product innovation. Customers benefit from personalized services and convenience, while fintech startups gain a &lt;strong&gt;level playing field&lt;/strong&gt; to compete and partner with banks. This ultimately leads to a more inclusive, efficient financial ecosystem with new revenue streams and better financial outcomes for consumers.&lt;/p&gt;
&lt;h2 id="who-benefits"&gt;Who benefits:&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Consumers:&lt;/strong&gt; Greater convenience, transparency, personalized services, and faster loan approvals.
&lt;strong&gt;Startups:&lt;/strong&gt; New data-driven products, broader reach through bank partnerships, and lower fraud/friction by leveraging verified bank data.
&lt;strong&gt;Banks:&lt;/strong&gt; Collaborative opportunities with fintechs, expanded services for customers, and participation in a growing fintech ecosystem.&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;&lt;strong&gt;Guiding principle:&lt;/strong&gt; Consent + Least Privilege. &lt;em&gt;If you don’t need a piece of data, don’t request it.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="aligning-with-samas-open-banking-framework"&gt;Aligning with SAMA’s Open Banking Framework&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;SAMA’s open banking journey has three phases: a design phase, an implementation phase, and the go-live phase. This phased roadmap allowed Saudi regulators to engage stakeholders, test standards, and gradually launch open banking services in the market.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Saudi Arabia’s Open Banking rollout is guided by a comprehensive framework from SAMA (Saudi Central Bank) to ensure innovation and regulation progress hand-in-hand. Fintech founders must deeply align with this framework, which defines &lt;strong&gt;technical standards, operational guidelines, and compliance timelines&lt;/strong&gt; for all participants.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scope of Services:&lt;/strong&gt; The first rollout focused on &lt;strong&gt;Account Information Services (AIS)&lt;/strong&gt; — APIs for sharing banking data (accounts, balances, transactions) with user consent. SAMA issued the &lt;strong&gt;Open Banking Framework&lt;/strong&gt; to cover AIS, including detailed legislation, regulatory guidelines, and technical standards. A second phase covering &lt;strong&gt;Payment Initiation Services (PIS)&lt;/strong&gt; is underway: SAMA released the PIS framework, enabling fintechs to initiate payments from user accounts. According to the roadmap, third-party providers (TPPs) like fintech startups are expected to offer payment services soon as the PIS standards roll out. This phased approach (first data, then payments) ensures a controlled expansion of open banking capabilities in Saudi.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regulatory and Technical Standards:&lt;/strong&gt; The framework provides &lt;strong&gt;standardized API specifications&lt;/strong&gt; to ensure interoperability and security across all banks and TPPs. It draws inspiration from the UK Open Banking and Europe’s PSD2, but is tailored to Saudi’s context. Notably, the KSA Open Banking standard introduced customized features like new customer consent types and an extended “Parties” API endpoint that leverages OpenID Connect for robust identity verification. In practice, this means all banks expose data in a consistent format (accounts, transactions, etc.), reducing fragmentation for fintech developers. The framework also includes &lt;strong&gt;Customer Experience Guidelines&lt;/strong&gt; to ensure transparency and simplicity in user interactions (e.g. how consent screens should look), &lt;strong&gt;Operational Guidelines&lt;/strong&gt; for reliability, security, and support processes, and defined &lt;strong&gt;Use Cases &amp;amp; Business Rules&lt;/strong&gt; that clarify permissible activities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open Banking Lab &amp;amp; Sandbox:&lt;/strong&gt; To support smooth implementation, SAMA launched the &lt;strong&gt;Open Banking Lab&lt;/strong&gt; in 2023 — a secure sandbox environment where banks and fintechs can develop and test their open banking integrations with mock data. This lab provides conformance testing suites to validate that APIs meet the KSA standards before going live. In parallel, SAMA’s broader &lt;strong&gt;Regulatory Sandbox&lt;/strong&gt; is an “always-open” program accepting fintech applications on a rolling basis. This &lt;strong&gt;always-open sandbox&lt;/strong&gt; means startups need not wait for cohorts; you can apply whenever ready, work with SAMA on testing your solution, and get certified for production use. Embracing these facilities is crucial — they reflect SAMA’s &lt;strong&gt;fintech-friendly approach&lt;/strong&gt; but also its expectation that solutions be rigorously tested and compliant before scaling to real customers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For fintech founders, alignment with SAMA’s framework is both a responsibility and a strategic advantage. It ensures your open banking product meets &lt;strong&gt;Saudi regulatory requirements&lt;/strong&gt; (security, privacy, API specs) and can interoperate with all banks. It also signals to investors and customers that your startup is &lt;strong&gt;“fintech compliance KSA”&lt;/strong&gt; ready. Before writing a single line of code, study SAMA’s open banking documentation and updates. Build your solution to &lt;strong&gt;plug into the standard APIs and timelines&lt;/strong&gt;, and engage with SAMA early (they welcome collaboration and feedback during these evolving phases). This groundwork will save countless headaches and position your startup as a compliant, trusted player in Saudi’s open banking ecosystem.&lt;/p&gt;
&lt;h2 id="open-banking-architecture-spec-first-approach"&gt;Open Banking Architecture: Spec-First Approach&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Spec-first open banking architecture: API gateway, consent service, bank connectors, token service, event bus, normalization layer, operational store, analytics platform, and audit logging"
srcset="https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/open-banking-architecture-spec-first-approach_hu_22e252d7594e5517.webp 320w, https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/open-banking-architecture-spec-first-approach_hu_2777b77f54aa75ed.webp 480w, https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/open-banking-architecture-spec-first-approach_hu_fed371c057c44be0.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/open-banking-architecture-spec-first-approach_hu_22e252d7594e5517.webp"
width="760"
height="663"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/open-banking-architecture-spec-first-approach_hu_74de6b0b56b8d768.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Designing your fintech’s architecture around SAMA’s standards (“spec-first”) from the start will make integration and scaling much smoother. A robust open banking architecture typically includes several layers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;API Gateway &amp;amp; App Interface:&lt;/strong&gt; All client (mobile/web) requests go through an API gateway or backend-for-frontend that enforces rate limits, WAF (web application firewall) rules, and input validation. This gateway handles OAuth2 redirects and acts as the front door for your services.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consent Management Service:&lt;/strong&gt; A dedicated service to manage user consent records (scopes requested, consent duration/TTL, status). This handles creating, renewing, and revoking consents and ensures your app only pulls data within the granted scope and period.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bank Connector Layer:&lt;/strong&gt; Modules or microservices that connect to bank APIs. In a spec-first approach, you might build &lt;strong&gt;direct adapters&lt;/strong&gt; for each bank’s open API according to the standard. Optionally, you can also integrate &lt;strong&gt;aggregator SDKs/APIs&lt;/strong&gt; (like those from Tarabut, Lean, etc.) for faster coverage. This layer should use secure mTLS or JWT-based authentication when communicating with banks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token Management Service:&lt;/strong&gt; Handles the OAuth2 &lt;strong&gt;authorization flows&lt;/strong&gt; with banks (using OpenID Connect and the FAPI profile). It stores and rotates tokens securely (e.g., using a KMS/HSM for encryption), and ensures access tokens are short-lived and scoped. It also manages refresh tokens and their rotation, detecting reuse or tampering to revoke if necessary.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event Bus &amp;amp; Data Ingestion:&lt;/strong&gt; An asynchronous stream or job queue for pulling data from banks once consent is given. This ensures you can process data in the background, with retries, idempotency keys, and back-pressure if a bank’s API is slow or down.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Normalization &amp;amp; Data Store:&lt;/strong&gt; A &lt;strong&gt;normalization layer&lt;/strong&gt; translates each bank’s data format into a &lt;strong&gt;canonical schema&lt;/strong&gt; (common data model for accounts, transactions, etc.). Clean, normalized data is then stored in a secure operational database. Sensitive PII (like account numbers) should be encrypted at field-level or tokenized. This database (or data lake) becomes the unified source for your app’s features (e.g., budgeting analytics or credit scoring).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Analytics &amp;amp; ML Platform:&lt;/strong&gt; If your startup leverages data for insights or machine learning (e.g. risk scoring, personalized offers), set up a data pipeline from the normalized store to an analytics database or feature store. This could be a separate data warehouse or lakehouse where you run aggregations, train models, and compute derived metrics without impacting the operational store.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit Logging &amp;amp; Monitoring:&lt;/strong&gt; An immutable audit log system records every data access, consent event, and key action (see below section on logging). Logs should be stored in append-only storage (WORM — write once, read many) and streamed to a SIEM for real-time monitoring and alerts on anomalies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Admin &amp;amp; Support Tools:&lt;/strong&gt; An internal admin console for operations and support, protected by strong RBAC (role-based access control). This allows authorized staff to view logs, assist with user issues, or perform emergency “break-glass” actions (with dual approvals) if needed. All such access is logged for compliance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Key design tenets&lt;/strong&gt; for this architecture are &lt;em&gt;idempotency everywhere&lt;/em&gt; (so re-fetching or reprocessing data won’t create duplicates), robust timeouts and circuit breakers (so a slow bank API doesn’t hang your system), and a “zero trust” mindset internally (no service should have blanket access to data without going through proper auth checks). All secrets and encryption keys belong in secure vaults or HSMs, not in code or config files. By building to the &lt;strong&gt;standardized models&lt;/strong&gt; and security profiles SAMA provides, you avoid bank-specific hacks and ensure your system can plug-and-play as banks update their APIs or as new banks come online.&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;(In summary: &lt;strong&gt;spec-first, not aggregator-first.&lt;/strong&gt; Use aggregators for speed to market, but architect so you can swap direct connections in as you grow.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="consent-ux-flows-and-building-user-trust"&gt;Consent UX Flows and Building User Trust&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Consent UX and user trust in Saudi open banking: clear consent flows on mobile screens and trust signals like security, data control, and informed consent"
srcset="https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/open-banking-architecture-spec-first-approach-2_hu_488043f494965371.webp 320w, https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/open-banking-architecture-spec-first-approach-2_hu_6a15abe86775c8d9.webp 480w, https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/open-banking-architecture-spec-first-approach-2_hu_e59e194b3fe9b708.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/open-banking-architecture-spec-first-approach-2_hu_488043f494965371.webp"
width="760"
height="703"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/open-banking-architecture-spec-first-approach-2_hu_48158d553ce4bcba.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Designing a user consent flow that is clear, intuitive, and trustworthy is &lt;strong&gt;paramount in open banking&lt;/strong&gt;. Since customers must actively grant permission for a fintech app to access their bank data, the &lt;strong&gt;UX around consent can make or break adoption&lt;/strong&gt;. Saudi’s Open Banking Framework explicitly mandates &lt;strong&gt;transparency, simplicity, and informed consent&lt;/strong&gt; in user interactions, so startups should adhere to best practices in both backend process and frontend design:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transparent Communication:&lt;/strong&gt; Clearly explain &lt;em&gt;what&lt;/em&gt; data is being requested and &lt;em&gt;why&lt;/em&gt;. Users should immediately understand the benefit. For example: &lt;em&gt;“Allow &lt;strong&gt;BudgetApp&lt;/strong&gt; to read your last 12 months of transactions from Bank X to provide spending insights.”&lt;/em&gt; Avoid jargon; use the customer’s language (Arabic or English, as appropriate) and keep consent screens concise and easy to read.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Granular and Time-Bound Consent:&lt;/strong&gt; Wherever possible, let users choose the scope of data access. If your app only needs read-access to savings account transactions, &lt;strong&gt;don’t request access to all accounts&lt;/strong&gt;. SAMA’s standards provide for multiple &lt;strong&gt;consent types and scopes&lt;/strong&gt;, so leverage that flexibility. Also, inform users that consent is not open-ended — it might expire after a certain period (e.g. 90 days) unless renewed. This gives users confidence that they aren’t giving a “blank check” forever.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redirection to Bank for Authentication:&lt;/strong&gt; A hallmark of open banking UX (in KSA and globally) is that the user &lt;strong&gt;authenticates directly with their bank&lt;/strong&gt; during consent. In practice, your app will redirect the user to their bank’s secure login page (or app) where they log in and authorize the data sharing, often using multi-factor authentication or biometrics. The fintech app never sees the user’s banking password. Emphasize this flow in your UI (e.g. “You will be securely redirected to your bank to approve this request”). This &lt;strong&gt;builds trust&lt;/strong&gt;, as users feel safer granting access via their familiar bank interface. It also aligns with SAMA’s security principle that banks handle the actual authentication, and TPPs (fintechs) do &lt;strong&gt;not store sensitive credentials&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consent Management and Revocation:&lt;/strong&gt; Treat consent as a &lt;em&gt;living&lt;/em&gt; preference that the user controls. Provide an in-app &lt;strong&gt;dashboard&lt;/strong&gt; or settings page where users can see what consents are active (which banks/accounts are linked) and easily revoke consent at any time. Robust open banking implementations include technical processes to ensure revocations are honored immediately. From a UX perspective, make revocation as easy as one or two clicks — paradoxically, this &lt;em&gt;increases&lt;/em&gt; trust, as users are more willing to grant access if they know it’s simple to undo. Also, send users periodic reminders or notifications of their active consents (e.g. &lt;em&gt;“Your Bank X account data sharing with BudgetApp is set to renew next week”&lt;/em&gt;) as required by good practice and regulation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User Education and Onboarding:&lt;/strong&gt; Don’t assume every user in KSA is familiar with open banking. Briefly educate users during onboarding or the consent process about &lt;em&gt;what&lt;/em&gt; open banking is and how &lt;strong&gt;SAMA regulates it for their safety&lt;/strong&gt;. Mention that your app uses &lt;strong&gt;secure, SAMA-approved channels&lt;/strong&gt; and that &lt;strong&gt;no data is accessed without their explicit approval&lt;/strong&gt;. Highlight security measures (encryption, regulatory oversight, the fact they authenticate via the bank) to preempt common fears. Building this knowledge into your onboarding increases user confidence in both your app and the concept of open banking generally.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By focusing on a &lt;strong&gt;consent-first UX&lt;/strong&gt;, fintech startups can establish a strong trust model with customers. When users feel in control and safe, they are more likely to try new open-banking-powered services like yours. Remember that &lt;strong&gt;conversion at the consent step is critical&lt;/strong&gt; — any confusion or doubt there, and users will drop off. Instrument your funnel to detect issues: measure how many users start the consent process vs. successfully return from the bank, where drop-offs happen (e.g. at bank login), and how long the process takes on average. Gathering such telemetry (and even reasons users revoke access) can highlight UX pain points. Thus, invest in user testing for your consent flow, follow SAMA’s customer experience guidelines, and learn from global best-in-class examples (e.g. popular UK open banking apps) to continually refine the process. Ultimately, a seamless consent experience builds the foundation for a long-term trust relationship between your fintech and its users.&lt;/p&gt;
&lt;h2 id="data-normalization-strategies-for-multi-bank-integration"&gt;Data Normalization Strategies for Multi-Bank Integration&lt;/h2&gt;
&lt;p&gt;One technical challenge for open banking fintechs is handling data from &lt;strong&gt;multiple banks’ APIs&lt;/strong&gt; and making it uniform. Even with SAMA’s standardized API specifications, different banks might have slight variations in implementations or data formats. As a startup, you need a strategy to &lt;strong&gt;normalize and standardize financial data&lt;/strong&gt; coming from various sources so that your product can use it consistently.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Leverage the KSA Standard:&lt;/strong&gt; The good news is Saudi’s Open Banking Framework enforces a baseline schema for accounts, transactions, beneficiaries, etc. This ensures that, for example, all banks will provide an account object with certain core fields (account number, IBAN, currency, balance, etc.) and transaction records in a defined structure. Make sure your developers thoroughly understand the &lt;strong&gt;KSA Open Banking data model&lt;/strong&gt; and build your internal data structures around it. If all banks follow the spec closely, your app can parse their responses with a common parser and data model.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Handle Minor Variations and Extensions:&lt;/strong&gt; In practice, banks may extend the standard or have optional fields (e.g. additional transaction metadata) in their APIs. Plan for a &lt;strong&gt;data mapping layer&lt;/strong&gt; in your backend that translates each bank’s responses into your app’s canonical format. This may involve writing adapters or using an intermediary library. For instance, one bank might label a field “postingDate” vs another using “transactionDate” — your normalization layer should map both to your unified internal field (e.g. &lt;code&gt;date&lt;/code&gt;). Also be mindful of data types and formats (some banks might use different date string formats, etc.). Rigorous testing with each bank&amp;rsquo;s sandbox API is key to ironing these out.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use Aggregation Platforms (if suitable):&lt;/strong&gt; To accelerate multi-bank connectivity, many Saudi fintechs partner with &lt;strong&gt;open banking intermediaries&lt;/strong&gt; or aggregators. Offering a single unified API that aggregates data from many banks. By integrating with such platforms, a startup can offload much of the normalization effort to them — you get &lt;strong&gt;standardized data feeds&lt;/strong&gt;, and they handle the connections to individual banks behind the scenes. The trade-off is dependency and cost, but it can greatly speed up development and ensure comprehensive bank coverage, especially in early stages. &lt;em&gt;(As a rule of thumb, if your product’s differentiation lies mainly in analytics or user experience, an aggregator can be a great starting point; but if you’re building a broad platform or are heavily payment-focused, investing in direct bank integrations sooner is wiser.)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistent Data Modeling:&lt;/strong&gt; Whether you integrate directly or via an aggregator, define a &lt;strong&gt;consistent data model&lt;/strong&gt; internally. For example, create unified classes or objects for a &lt;code&gt;CustomerAccount&lt;/code&gt;, &lt;code&gt;Transaction&lt;/code&gt;, etc. that your application logic will use. Run all external data through a transformation into these canonical objects. This ensures the rest of your system and analytics are agnostic to the data source. If you plan to introduce new banks or even non-bank data (e.g. telecom bills in a future Open Finance scope), your existing model should accommodate those with minimal changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Quality and Enrichment:&lt;/strong&gt; Normalization isn’t just structural; it’s also about making the data &lt;em&gt;useful&lt;/em&gt;. Implement strategies to &lt;strong&gt;clean and enrich data&lt;/strong&gt; after aggregation. Transaction descriptions, for instance, can be cryptic — consider parsing or using third-party services to categorize transactions (e.g. identify merchant names, classify spending into categories like groceries, utilities, etc.). Ensure consistent categorization across all banks’ data to power features like spending analysis or credit scoring. Remove duplicates or overlaps (if a user reconnects an account, ensure you don’t double-count historical data). Maintaining a high-quality, normalized dataset will directly impact the quality of your product’s insights (accurate budgets, reliable credit scores, etc.).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary, &lt;strong&gt;robust data normalization&lt;/strong&gt; is crucial for any multi-bank fintech solution. Saudi’s open banking ecosystem gives you a head start with its standard, but the onus is on your startup to perfect the process. Plan for an ongoing effort: as banks update their APIs or as Phase 2 (payments) comes in, you’ll need to adapt your normalization logic. By investing in this layer, you ensure that your app can scale across banks and deliver a seamless experience — where a customer’s data looks the same regardless of which bank it came from. This consistency builds user confidence and enables you to apply uniform analytics or features on top of the aggregated data. &lt;em&gt;(Aim for high data quality: e.g. &amp;gt;99% deduplication accuracy and minimal uncategorized transactions, so users and regulators alike trust your outputs.)&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="security-scope-management-and-user-access-control"&gt;Security Scope Management and User Access Control&lt;/h2&gt;
&lt;p&gt;In a financial app environment, &lt;strong&gt;security is non-negotiable&lt;/strong&gt;, and open banking adds unique considerations. Fintech startups must implement strong scope management and access controls to ensure that data access is limited to what’s authorized by the user and that both external and internal access are tightly governed. Here’s how to approach security and access control in the context of Saudi open banking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OAuth 2.0 and Fine-Grained Scopes:&lt;/strong&gt; Open Banking in KSA uses an OAuth 2.0–based authorization flow (like other regions), typically enhanced with the &lt;strong&gt;Financial-grade API (FAPI)&lt;/strong&gt; security profile for higher security. When a user grants consent, the result is an &lt;strong&gt;access token&lt;/strong&gt; issued to your app with specific scopes. Scopes define &lt;strong&gt;exactly what your app can do&lt;/strong&gt; — for example, &lt;code&gt;accounts.read&lt;/code&gt;, &lt;code&gt;transactions.read&lt;/code&gt;, or &lt;code&gt;payments.initiate&lt;/code&gt;. Your implementation should request only the scopes needed for your service. More importantly, once you have a token, your app must strictly use it only for the allowed APIs. SAMA&amp;rsquo;s standards and the bank APIs themselves will enforce this (e.g. a transactions API call will fail if your token only has accounts access), but you should also design your software to respect scope boundaries. Never attempt to circumvent or &amp;ldquo;over-reach&amp;rdquo; on data access – not only will that fail compliance, it also erodes the trust model of open banking.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User-Level Access Isolation:&lt;/strong&gt; Each user’s data must be compartmentalized by their own consent and token. Architect your backend such that every service call to fetch data &lt;strong&gt;always operates in the context of a specific user identity and token&lt;/strong&gt;. Avoid any design where a general system-wide token or credential could access multiple users’ data — that violates the principle of least privilege. Instead, tie every data request to a user-specific access token stored securely (e.g. encrypted in a database or vault). Additionally, build safeguards so that one user’s data never gets exposed to another by mistake (strong multi-tenant data separation). On the front-end and in internal APIs, use strong user authentication and session management so requests are always tied to the correct user context.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Internal Access Control and Auditing:&lt;/strong&gt; Within your company, enforce strict policies about who (or what systems) can access sensitive customer data. Use role-based access control (RBAC) or attribute-based control in your internal dashboards and databases. For example, maybe only compliance or support officers can view certain raw data, and even then only when necessary. All access by administrators or engineers should be logged (we’ll cover audit trails next) and periodically reviewed. Adopting an &lt;strong&gt;Identity and Access Management (IAM)&lt;/strong&gt; framework is wise as you scale. Also consider using separate cloud environments or accounts for different data domains (e.g. an isolated environment for handling payments vs. general account info) to limit the blast radius in case of a breach.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Encryption and Data Protection:&lt;/strong&gt; Treat any data retrieved via open banking as highly sensitive personal financial data. Follow SAMA’s cybersecurity guidelines (aligned with global standards) by encrypting data &lt;em&gt;both in transit and at rest&lt;/em&gt;. Use strong protocols (TLS 1.2/1.3) for API calls. At rest, use field-level encryption for particularly sensitive fields like account numbers or IBANs (or avoid storing full account details if not necessary). Mask or tokenize data wherever possible — for instance, card numbers (PAN) are often masked by the bank APIs themselves, so your system never even sees the full PAN, reducing your PCI-DSS scope. Ensure any secrets (API keys, client secrets, certificates) are kept in secure vaults and not hard-coded or exposed in logs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fraud and Anomaly Detection:&lt;/strong&gt; Security scope management isn’t only about normal operations, but also about detecting misuse. Implement monitoring to catch anomalous access patterns — e.g. if suddenly your system is pulling unusually large volumes of data for a single user, or if there are repetitive, rapid payment initiation attempts. These could indicate abuse or a compromised token. Some open banking implementations include fraud analysis layers and notification hooks; design your system to leverage such signals (from banks or your own analytics). If a bank or user revokes consent, your app should immediately stop accessing data and securely delete or archive previously fetched data per your data retention policy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By diligently managing &lt;strong&gt;who can access what&lt;/strong&gt;, you uphold the security promises of open banking. SAMA and the banks take security extremely seriously — the open APIs are built to &lt;strong&gt;bank-grade security standards&lt;/strong&gt; and have undergone extensive hardening. Your startup must match that diligence. Embracing frameworks like OAuth 2.0 with FAPI, OpenID Connect for identity, and robust internal controls will not only keep you compliant but also protect your reputation. In fintech, a single security lapse can be fatal to user trust. Conversely, a strong security posture can be a selling point, especially when dealing with something as sensitive as personal financial data. Always err on the side of caution: &lt;strong&gt;access less, secure more&lt;/strong&gt;, and continuously review and test your security measures as you grow.&lt;/p&gt;
&lt;h2 id="leveraging-open-banking-data-for-risk-scoring-and-credit-modeling"&gt;Leveraging Open Banking Data for Risk Scoring and Credit Modeling&lt;/h2&gt;
&lt;p&gt;One of the most powerful opportunities for fintech startups in open banking is using banking data to enhance &lt;strong&gt;credit risk models and financial analytics&lt;/strong&gt;. Traditionally, lenders in Saudi Arabia relied on credit bureau scores and static income documents to make decisions. Now, with user-permissioned access to bank account histories, fintechs can perform &lt;strong&gt;“cashflow-based underwriting”&lt;/strong&gt; and more nuanced risk assessments. Here’s how open banking data can be leveraged in real-world use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Income and Expense Verification:&lt;/strong&gt; With AIS access, a fintech lender can pull an applicant’s recent bank transactions directly from their accounts to verify income deposits, salary consistency, and recurring expenses. For example, &lt;strong&gt;micro-lending and consumer loan providers&lt;/strong&gt; in KSA use open banking to instantly assess an applicant’s ability to repay. This replaces the need for uploading bank statements or payslips, speeding up loan approvals while reducing fraud (since data comes directly from the bank). Consistent salary credits, overall cashflow patterns, and existing debt obligations seen in the account can feed into an automated scoring model to make fast credit decisions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Behavioral Credit Scoring:&lt;/strong&gt; Going beyond static metrics, transaction data allows analysis of spending habits and financial behavior. &lt;strong&gt;Machine learning models&lt;/strong&gt; can be trained on categorized transaction histories to predict risk. For instance, frequent overdrafts or gambling-related transactions might signal higher risk, while steady savings or regular investment contributions might signal a financially responsible customer. &lt;strong&gt;Banks in Saudi Arabia can increase risk assessment accuracy&lt;/strong&gt; by incorporating such open banking data into their credit scoring models. Fintech startups focused on &lt;strong&gt;lending, BNPL, or credit card issuance&lt;/strong&gt; are already exploring these models — e.g. a BNPL provider could check a user’s recent account balances and spending behavior to set responsible spending limits. In fact, some Saudi BNPL services (like &lt;strong&gt;Tabby&lt;/strong&gt;) reportedly leverage open banking integrations (via providers like Lean) to inform their instant credit decisions, ensuring customers are not over-leveraged.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SME Credit and Cashflow Lending:&lt;/strong&gt; Open banking isn’t just for retail consumers; small businesses can also benefit. An SME financing fintech can use open banking to aggregate a company’s accounts across different banks to get a holistic view of cash flows. This is particularly useful in KSA where many SMEs may not have extensive credit histories. By analyzing patterns such as revenue inflows, expense outflows, seasonality, and average balances, a lender can perform &lt;strong&gt;cashflow-based lending&lt;/strong&gt;. This approach, enabled by open banking data, &lt;strong&gt;provides more competitive lending options to SMEs and improves risk evaluation&lt;/strong&gt; by using real, recent financial data instead of just outdated statements. For example, a working-capital fintech could automatically pull the last 12 months of transactions from a business’s bank accounts and feed it into a risk model that predicts likelihood of default more dynamically than a traditional score.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personal Financial Management &amp;amp; Advice:&lt;/strong&gt; While not a traditional “credit” use case, it’s worth noting that startups providing PFM tools can also derive insights that border on credit modeling. For instance, an app that aggregates a user’s accounts and analyzes spending could proactively advise the user on their &lt;strong&gt;affordability&lt;/strong&gt; for new credit or predict when they might face cash shortfalls. Over time, such an app might even offer tailored micro-loans or lines of credit when it sees the user’s risk is low and they might need extra funds — all derived from open banking data. &lt;strong&gt;Innovative services&lt;/strong&gt; like automated savings or investment advisors also use risk-modeling concepts (assessing how much a user can save or invest given their cashflow).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improving Financial Inclusion:&lt;/strong&gt; A broader impact of using open banking for risk assessment is expanding credit to underserved populations. Saudi Arabia has a large youth population and many gig-economy workers who may not have substantial credit history. However, if they use bank accounts, their transaction data can demonstrate their financial behavior. Fintech startups can tap into this to responsibly lend to segments that banks traditionally found difficult to score. By using &lt;strong&gt;comprehensive, verified transaction histories&lt;/strong&gt; instead of just bureau data, lenders can approve more people for loans or credit at fair rates, &lt;strong&gt;reducing defaults by truly understanding the borrower’s financial picture&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When building such data-driven credit models, you’ll likely ingest &lt;strong&gt;12–24 months&lt;/strong&gt; of transaction history per user via AIS, normalize and enrich it, then aggregate it into useful features (covering income stability, expense patterns, liquidity buffers, etc.). You can then train predictive algorithms (e.g. gradient-boosted tree models) to score risk or affordability. Throughout this process, handle the data responsibly: &lt;strong&gt;user consent&lt;/strong&gt; is not just a legal checkbox — use the data only for the stated purpose (e.g. credit scoring) and ensure decisions remain free of prohibited biases. Maintain &lt;strong&gt;explainability&lt;/strong&gt; in your models (e.g. provide reason codes like “high discretionary spending affected your score” to users) and practice strong model governance (version your models, test for fairness, monitor for drift over time). Also, make sure to &lt;strong&gt;explain decisions or give insights back to the user&lt;/strong&gt; when possible (“Your spending on subscriptions is high, which affected your credit offer — here’s how you could improve”). This keeps users engaged and informed about how their data is being used.&lt;/p&gt;
&lt;p&gt;Finally, remember that credit modeling is heavily regulated — if you’re providing lending or credit scoring services, you may need additional licenses from SAMA. But the combination of &lt;strong&gt;open banking data + smart analytics&lt;/strong&gt; is a game changer. It leads to faster decisions (loans approved in minutes), more personalized credit (amounts tailored to one’s actual cashflow), and overall a more dynamic lending market in Saudi Arabia. Startups that master this will play a key role in driving the Kingdom’s fintech innovation forward by both managing risk better and extending financial inclusion.&lt;/p&gt;
&lt;h2 id="logging-and-audit-trails-for-compliance"&gt;Logging and Audit Trails for Compliance&lt;/h2&gt;
&lt;p&gt;In the highly regulated context of open banking, maintaining comprehensive logs and audit trails is not just good practice — it’s a &lt;strong&gt;compliance necessity&lt;/strong&gt;. Fintech startups must be prepared to demonstrate to regulators like SAMA that every customer’s data access was authorized and appropriately handled. Implementing robust logging and monitoring from day one will save you headaches later and ensure you meet governance standards. Key considerations include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consent Audit Logging:&lt;/strong&gt; Every time a user grants, denies, or revokes consent, record it. This log should include the user identity, the exact permissions (scopes) granted, timestamp, and context (e.g. which interface or device was used). In case of any dispute or inquiry, you should be able to pull up a &lt;strong&gt;consent history&lt;/strong&gt; for each customer. SAMA’s Customer Experience Guidelines emphasize informed consent — an audit trail proves you obtained that consent properly. Also log when consents expire or are auto-renewed (with user action). Many open banking systems generate a consent ID or record; ensure you store that and tie it to the user’s profile.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Access Logs:&lt;/strong&gt; For every API call your system makes to a bank’s open banking API (whether to fetch account info, transactions, or initiate a payment), log the details. At minimum capture: which user/token was used, what endpoint was accessed (e.g. &lt;code&gt;&amp;quot;GET /accounts/{id}/transactions&amp;quot;&lt;/code&gt;), the timestamp, and whether it was successful. &lt;strong&gt;Do not log sensitive payload data in plaintext&lt;/strong&gt; (to avoid creating another security risk), but you can log metadata like number of records fetched or the amount in a payment initiation. These logs create an &lt;strong&gt;audit trail of data access&lt;/strong&gt; – demonstrating that you only accessed data when you had a valid consent and token, and exactly what was retrieved. They are invaluable for troubleshooting as well as for future audits by SAMA. (In many jurisdictions, regulators can ask a fintech: &amp;ldquo;Who accessed customer X&amp;rsquo;s data and when?&amp;rdquo; – you should be able to answer that quickly from your logs.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security Monitoring and Alerts:&lt;/strong&gt; In addition to basic logs, set up monitoring on those logs to catch anomalies. For instance, if there are repeated failed attempts to access data (could indicate an expired token or a possible breach attempt), alert your security team. If a normally low-volume API suddenly spikes in usage, investigate it. Having an automated log monitoring system (a SIEM — Security Information and Event Management tool) helps flag suspicious events in real-time. This ties into compliance because regulators expect you to not just record, but also &lt;strong&gt;act on security incidents&lt;/strong&gt; swiftly. It also helps meet SAMA’s cybersecurity framework requirements, which call for continuous monitoring of systems.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit Trail for Data Handling and Changes:&lt;/strong&gt; Beyond external data access, log any internal handling of that data. For example, if a support agent views a customer’s account data in your admin panel, log that action (e.g. &lt;em&gt;“Agent A viewed Customer Y’s transactions at time Z”&lt;/em&gt;). If any data is transformed, exported, or deleted, log those events too. Also track changes in critical configurations — e.g. if someone updated the permissions of an API client or changed a callback URL for your open banking integration, record it. This level of logging ensures &lt;strong&gt;accountability inside your organization&lt;/strong&gt;. In case of any irregularity, you can trace it to a root cause (for instance, if a user complains “I revoked consent but my data was still accessed afterward,” you can investigate the timeline via logs).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retention and Protection of Logs:&lt;/strong&gt; Treat logs as sensitive data too. They often contain user identifiers and timestamps of activity that could be pieced together to reveal patterns. Secure your logs — use append-only storage or write-once mediums to prevent tampering. Also, maintain them for an adequate period. SAMA or other authorities may require logs to be kept for several years. In absence of specific guidance, many fintechs keep audit logs for at least 5 years, aligning with general financial record-keeping practices. Use efficient log management solutions so performance isn’t impacted as log volume grows. &lt;em&gt;Minimize personal data in logs as well — for example, log user IDs or transaction IDs instead of full names or details, and keep a separate reference if needed.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A strong logging and audit trail regime ensures &lt;strong&gt;transparency and accountability&lt;/strong&gt; in your operations, which is exactly what regulators want to see. It also streamlines your internal compliance: when the time comes for audits, you won’t be scrambling to piece together records. Instead, you can confidently provide evidence of every consent and data exchange, demonstrating adherence to the Open Banking Framework and broader data protection laws. In the words of industry guidelines, better logging leads to &lt;strong&gt;“improved financial transparency”&lt;/strong&gt; and more efficient oversight. For a Saudi fintech aiming to build trust with both users and regulators, investing in these compliance capabilities is as important as building the product’s features.&lt;/p&gt;
&lt;h2 id="from-sandbox-to-production-a-compliance-checklist"&gt;From Sandbox to Production: A Compliance Checklist&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Illustration of the compliance journey from SAMA&amp;rsquo;s regulatory sandbox to production launch as a step-by-step checklist"
srcset="https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/from-sandbox-to-production-a-compliance-checklist_hu_e9a44c0818192b7f.webp 320w, https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/from-sandbox-to-production-a-compliance-checklist_hu_b15dcd0541a0ec8a.webp 480w, https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/from-sandbox-to-production-a-compliance-checklist_hu_2ad0028e4d3f0177.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/from-sandbox-to-production-a-compliance-checklist_hu_e9a44c0818192b7f.webp"
width="760"
height="641"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/talks/open-banking-saudi-playbook/images/webp/from-sandbox-to-production-a-compliance-checklist_hu_40a00336f543b419.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Launching an open banking–powered fintech product in Saudi Arabia requires careful navigation of regulatory milestones and thorough preparation. Below is a &lt;strong&gt;practical checklist&lt;/strong&gt; for founders to ensure a smooth journey from initial development in the sandbox to full production deployment. Following these steps will help satisfy SAMA’s requirements and achieve “fintech compliance KSA” status:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Join SAMA’s Regulatory Sandbox:&lt;/strong&gt; Start by applying to SAMA’s fintech sandbox program, which is always open for applications. Prepare a clear application detailing your open banking use case, business model, and readiness to test. SAMA will evaluate eligibility against criteria like innovation, consumer benefit, and readiness. Once accepted, you’ll receive a temporary permission (Letter of Approval) to operate in the sandbox after meeting any initial requirements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ensure Operational Readiness:&lt;/strong&gt; Before you can start testing with real users or live bank integrations, SAMA will assess your &lt;strong&gt;operational readiness&lt;/strong&gt;. This typically involves meeting an &lt;strong&gt;assessment criteria&lt;/strong&gt; checklist provided by the regulator. Key items include having proper risk management, customer disclosures, systems security, and governance in place. For open banking, make sure you have robust &lt;strong&gt;cybersecurity measures&lt;/strong&gt; aligned with SAMA’s framework (covering data encryption, access control, etc., as discussed above) and clear &lt;strong&gt;customer consent flows&lt;/strong&gt; and privacy policies documented. This stage might also require demonstrating your technology in a controlled setting or providing evidence of thorough internal testing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Technical Conformance Testing:&lt;/strong&gt; Utilize the &lt;strong&gt;Open Banking Lab&lt;/strong&gt; and related sandbox tools to test your API integrations. Validate that your app can connect to banks’ APIs and perform the expected functions (data retrieval or payment initiation) according to the KSA Open Banking standards. You may need to run &lt;strong&gt;conformance tests&lt;/strong&gt; provided by SAMA to certify that your implementation complies with the Open Banking API specs. Fix any issues that arise — it’s easier to resolve them in the sandbox than post-launch. Also ensure you handle error conditions gracefully (e.g. if a bank API is down or returns an error) as part of your testing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security Review and Certifications:&lt;/strong&gt; Before going live, conduct comprehensive security reviews. This includes &lt;strong&gt;penetration testing&lt;/strong&gt; by reputable third-party firms to probe your application and backend for vulnerabilities. Address any findings (SAMA may ask for the pen-test report or a summary). Additionally, consider obtaining relevant &lt;strong&gt;certifications&lt;/strong&gt;: for instance, ISO/IEC 27001 for information security management can demonstrate your commitment to data security. If your app involves payments and touches any card data (though open banking APIs avoid full card info), ensure compliance with &lt;strong&gt;PCI-DSS&lt;/strong&gt;standards — even if not strictly required, it’s good practice. Some fintechs also undergo SOC 2 audits for data handling. While not all of these certifications are mandated, they can speed up regulatory approval and build trust with bank partners. (SAMA’s assessment will definitely include cybersecurity compliance, so use their guidelines as a baseline for required controls.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User Experience and Consent Compliance:&lt;/strong&gt; SAMA (or its sandbox team) might review your app’s interface, especially around how you obtain user consent and disclose information. Be prepared to show screenshots or demo the flow to regulators. Ensure your &lt;strong&gt;terms of service and privacy notices&lt;/strong&gt; are compliant with Saudi laws (like the Personal Data Protection Law) and clearly state what data you collect and how it’s used. Any third-party partnerships (e.g. if you’re using an aggregator like Lean or Spare) should be disclosed as well. Having a legal/compliance advisor review these before submission to SAMA is advisable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gradual Testing (Beta Launch):&lt;/strong&gt; Once you have SAMA’s interim green light, you’ll enter the &lt;strong&gt;Testing Phase in the sandbox (up to 12 months)&lt;/strong&gt;. Use this period to run a controlled beta launch. Onboard a limited number of users (perhaps start with friends &amp;amp; family or a small pilot group) to test your service in real conditions. Closely monitor outcomes, collect user feedback, and demonstrate that you can operate safely at a small scale. SAMA may require periodic reports during this phase — including user metrics, any incidents, and results versus your projected outcomes. The goal is to prove that your solution works as intended and delivers benefits without undue risk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regulatory Approval and Licensing:&lt;/strong&gt; Upon successful sandbox testing (which usually requires at least ~6 months of operation), you will prepare to exit the sandbox. This involves compiling a final report of your test findings and how you met all objectives. If everything is satisfactory, SAMA will allow you to &lt;strong&gt;graduate from the sandbox&lt;/strong&gt;. At this point, depending on your business model, you may need to apply for a full operating license or obtain a specific approval to continue business. For an open banking TPP, this could mean an Account Information Service Provider (AISP) license or similar registration. SAMA’s market activation plan will detail what license or authorization is needed for each type of participant. Ensure you have all documentation ready (business plans, security policies, etc.) when applying for the full license. In some cases, startups partner with licensed institutions or operate under an agency model — for instance, a fintech could operate under an arrangement with a licensed open banking platform like Lean until it obtains its own license. Choose the path that fits your strategy, but make sure SAMA is in agreement.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scaling to Production and Ongoing Compliance:&lt;/strong&gt; With the full license or approval in hand, you can officially launch to all customers in Saudi Arabia. From here, &lt;strong&gt;treat compliance as an ongoing effort&lt;/strong&gt;. Maintain all the logs, security practices, and user protection measures discussed. SAMA may conduct supervisory inspections or require regular compliance reports. Also, stay updated on any &lt;strong&gt;framework updates&lt;/strong&gt; — for example, when Phase 2 (payments) fully rolls out, ensure your services comply with any new standards or update your app to offer new features under the new guidelines. Continue to engage with SAMA and industry forums; Saudi’s open banking is evolving (with Open Finance on the horizon beyond banking), so being an active participant will keep you ahead. Lastly, consider joining Fintech Saudi’s events or communities, where other startups and banks share learnings — this camaraderie can help in tackling common challenges and perhaps shaping future regulations. In parallel, run your operation like a bank: keep an eye on key performance indicators of your service. Measure &lt;strong&gt;consent conversion rates&lt;/strong&gt; (what percentage of users successfully link their accounts), &lt;strong&gt;data freshness&lt;/strong&gt; (how up-to-date the retrieved bank data is), &lt;strong&gt;coverage&lt;/strong&gt; (e.g. number of accounts or banks connected per user), &lt;strong&gt;system reliability&lt;/strong&gt; (uptime and data ingestion success rates), &lt;strong&gt;risk outcomes&lt;/strong&gt; (loan approval vs. default rates if you’re lending), and &lt;strong&gt;security metrics&lt;/strong&gt; (like average time to revoke a compromised token). Tracking these KPIs helps ensure you’re delivering on the open banking promise while staying safe and compliant.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Even with the above roadmap, be mindful of common pitfalls that have tripped up open banking startups. Frequent mistakes to &lt;strong&gt;avoid&lt;/strong&gt; include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Requesting &lt;strong&gt;over-broad data scopes&lt;/strong&gt; — this leads to low user conversion (people abandon consent if you ask for too much) and can invite regulatory scrutiny.&lt;/li&gt;
&lt;li&gt;Treating an &lt;strong&gt;aggregator’s SDK/API as a black box&lt;/strong&gt; — over-reliance on a third-party without understanding its limits can cause opaque failures and vendor lock-in.&lt;/li&gt;
&lt;li&gt;Skipping a &lt;strong&gt;canonical data schema&lt;/strong&gt; — if you don’t standardize data internally, you’ll accumulate analytics debt and brittle, bank-specific code that’s hard to maintain.&lt;/li&gt;
&lt;li&gt;Weak &lt;strong&gt;revocation handling&lt;/strong&gt; — not immediately honoring consent revocations (or deletions) violates PDPL and undermines user trust. Build the plumbing to purge or stop data flows as soon as consent is withdrawn.&lt;/li&gt;
&lt;li&gt;Logging sensitive data in plaintext — dumping bank payloads or user info in your logs or analytics unmasked can dramatically increase the impact of a breach. Always sanitize or encrypt sensitive fields end-to-end.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By following this checklist — and avoiding the above pitfalls — fintech founders can confidently navigate the journey from an idea to a fully compliant open banking product in KSA. The process may seem intensive, but it’s designed to ensure &lt;strong&gt;customer safety, data security, and financial stability&lt;/strong&gt; — all of which ultimately benefit your startup through increased user trust and a level playing field. Saudi Arabia is positioning itself as a global fintech hub, and regulators are quite supportive of innovators who do things the right way. As you graduate from sandbox to production, you’ll not only have a viable product but also the credibility of having met SAMA’s high standards, which is invaluable for winning customers, bank partnerships, and investor confidence. Good luck on your open banking journey in the Kingdom, and remember: &lt;strong&gt;compliance and innovation go hand-in-hand&lt;/strong&gt; in unlocking the full potential of &lt;em&gt;Saudi open banking&lt;/em&gt; for your startup’s success.&lt;/p&gt;</description></item><item><title>AI Agents in Software Development: Navigating the Skies of Modern Coding</title><link>https://akemara.com/en/blog/ai-agents-software-development/</link><pubDate>Tue, 22 Apr 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/ai-agents-software-development/</guid><description>&lt;p&gt;In the ever-evolving landscape of software development, the emergence of AI agents marks a transformative shift. These intelligent systems are redefining how applications are built, tested, and deployed. To grasp the magnitude of this change, consider an analogy from aviation: the transition from Visual Flight Rules (VFR) to Instrument Flight Rules (IFR). Just as IFR allows pilots to navigate safely using instruments rather than relying solely on visual cues, AI agents empower developers to navigate complex coding environments with enhanced precision and efficiency.&lt;/p&gt;
&lt;h2 id="understanding-ai-agents-the-new-co-pilots-in-software-development"&gt;Understanding AI Agents: The New Co-Pilots in Software Development&lt;/h2&gt;
&lt;p&gt;AI agents are intelligent systems capable of performing tasks such as code generation, debugging, and even architectural planning. They serve as co-pilots, offering suggestions, identifying potential issues, and streamlining the development process. This shift allows developers to focus more on strategic decision-making and less on routine tasks.&lt;/p&gt;
&lt;p&gt;Importantly, AI agents are not monolithic tools. They vary in autonomy, ranging from autocomplete systems to fully autonomous agents that can plan and execute software tasks. Their impact is amplified when these agents can communicate with each other and with external tools and data. This is where protocols like Agent-to-Agent (A2A) and Model Context Protocol (MCP) come into play.&lt;/p&gt;
&lt;h2 id="from-visual-to-instrumental-the-coding-evolution"&gt;From Visual to Instrumental: The Coding Evolution&lt;/h2&gt;
&lt;p&gt;Traditional coding is much like flying under VFR. Developers rely on their own knowledge, intuition, and experience — navigating through documentation, Stack Overflow threads, and mental models of systems. This approach is flexible but limited. When entering complex systems, the lack of external guidance becomes a bottleneck.&lt;/p&gt;
&lt;p&gt;AI-assisted coding is the IFR equivalent. AI agents act as navigational instruments — suggesting code, flagging bugs, recommending libraries, and even generating scaffolding. The developer becomes the pilot-in-command, using AI as a system of instruments to navigate and operate more effectively. The result: faster iteration, broader technological reach, and improved productivity.&lt;/p&gt;
&lt;h2 id="the-productivity-surge-ai-as-a-development-catalyst"&gt;The Productivity Surge: AI as a Development Catalyst&lt;/h2&gt;
&lt;p&gt;According to studies by GitHub and Harvard Business School, developers using AI tools like Copilot spend significantly more time coding and less on boilerplate and coordination. Productivity can increase by as much as 45%. This allows developers to spend more time on solving core business problems and innovating.&lt;/p&gt;
&lt;p&gt;AI agents also encourage broader exploration. Developers feel more confident using unfamiliar technologies because the AI can guide them. The ability to instantly prototype, test, and refine ideas encourages creativity and experimentation. AI isn’t replacing the developer — it’s augmenting their potential.&lt;/p&gt;
&lt;h2 id="agent-to-agent-a2a-protocol-facilitating-inter-agent-communication"&gt;Agent-to-Agent (A2A) Protocol: Facilitating Inter-Agent Communication&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Diagram of the Agent-to-Agent (A2A) protocol connecting AI agents across platforms"
srcset="https://akemara.com/en/blog/ai-agents-software-development/images/webp/agent-to-agent-a2a-protocol-facilitating-inter-agent-communication_hu_319aa913fd1c7a4d.webp 320w, https://akemara.com/en/blog/ai-agents-software-development/images/webp/agent-to-agent-a2a-protocol-facilitating-inter-agent-communication_hu_84085d0cf365555d.webp 480w, https://akemara.com/en/blog/ai-agents-software-development/images/webp/agent-to-agent-a2a-protocol-facilitating-inter-agent-communication_hu_3dfd7fd438346308.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-agents-software-development/images/webp/agent-to-agent-a2a-protocol-facilitating-inter-agent-communication_hu_319aa913fd1c7a4d.webp"
width="760"
height="507"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-agents-software-development/images/webp/agent-to-agent-a2a-protocol-facilitating-inter-agent-communication_hu_a65796eec1f2939a.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;The Agent-to-Agent Protocol (A2A), spearheaded by Google and supported by major tech players, is an open standard that enables AI agents to collaborate across platforms. A2A lets agents discover each other, share capabilities, and execute tasks together.&lt;/p&gt;
&lt;h3 id="key-features-of-a2a"&gt;Key Features of A2A&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Agent Discovery&lt;/strong&gt;: Using metadata-rich Agent Cards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Task Management&lt;/strong&gt;: Initiating, tracking, and finalizing workflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: Built-in authentication and authorization.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Long-Running Support&lt;/strong&gt;: Designed for tasks that span hours or days.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With A2A, AI agents become part of a coordinated network rather than isolated tools. This creates the possibility for distributed, collaborative problem-solving across the software stack.&lt;/p&gt;
&lt;h2 id="model-context-protocol-mcp-tool-and-data-access-for-agents"&gt;Model Context Protocol (MCP): Tool and Data Access for Agents&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Diagram of the Model Context Protocol (MCP) giving AI agents access to external tools and data"
srcset="https://akemara.com/en/blog/ai-agents-software-development/images/webp/model-context-protocol-mcp-tool-and-data-access-for-agents_hu_f338b2ab1964a649.webp 320w, https://akemara.com/en/blog/ai-agents-software-development/images/webp/model-context-protocol-mcp-tool-and-data-access-for-agents_hu_d31b659f70e8a96d.webp 480w, https://akemara.com/en/blog/ai-agents-software-development/images/webp/model-context-protocol-mcp-tool-and-data-access-for-agents_hu_401c087308b44471.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-agents-software-development/images/webp/model-context-protocol-mcp-tool-and-data-access-for-agents_hu_f338b2ab1964a649.webp"
width="760"
height="507"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-agents-software-development/images/webp/model-context-protocol-mcp-tool-and-data-access-for-agents_hu_ebd34bc373c43bb7.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Anthropic’s Model Context Protocol (MCP) focuses on enabling AI agents to interact with external data and tools. While A2A handles inter-agent communication, MCP handles agent-to-system integration.&lt;/p&gt;
&lt;h3 id="key-features-of-mcp"&gt;Key Features of MCP&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Standardized Interfaces&lt;/strong&gt;: For predictable, scalable integrations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client-Server Architecture&lt;/strong&gt;: Clear separation of roles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamic Discovery&lt;/strong&gt;: Agents can query their environment for capabilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security Enforcement&lt;/strong&gt;: Host-controlled access boundaries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MCP helps AI agents become smarter, more contextual, and capable of integrating into enterprise workflows without custom wiring for every task.&lt;/p&gt;
&lt;h2 id="a-unified-future-combining-a2a-and-mcp"&gt;A Unified Future: Combining A2A and MCP&lt;/h2&gt;
&lt;p&gt;Together, A2A and MCP offer a powerful vision. Imagine a team of agents: one handles code scaffolding, another tests APIs, a third manages deployment. Through A2A, they coordinate their tasks. Through MCP, they interact with version control, cloud providers, and observability tools.&lt;/p&gt;
&lt;p&gt;Such an architecture paves the way for AI-driven CI/CD pipelines, autonomous testing systems, and more. AI agents stop being individual helpers and become members of the team — each with a specific role and scope of autonomy.&lt;/p&gt;
&lt;h2 id="the-human-in-the-loop-why-software-experts-still-matter"&gt;The Human-in-the-Loop: Why Software Experts Still Matter&lt;/h2&gt;
&lt;p&gt;Despite the power of AI agents, human expertise remains crucial. AI still struggles with nuance, edge cases, and long-term maintainability. Just as pilots monitor instruments for anomalies, developers must oversee AI outputs for correctness, security, and alignment with business needs.&lt;/p&gt;
&lt;p&gt;Developers must also train AI agents with better prompts, correct mistakes, and iteratively guide them toward better outputs. Treating the AI like a junior developer or intern — capable but inexperienced — is often the best metaphor. Mentorship, oversight, and validation remain essential.&lt;/p&gt;
&lt;h2 id="conclusion-embracing-ai-augmented-development"&gt;Conclusion: Embracing AI-Augmented Development&lt;/h2&gt;
&lt;p&gt;AI agents are ushering in the IFR era of software development. Coding no longer needs to be manual-only, just as flying no longer relies on clear skies. With protocols like A2A and MCP, AI agents can communicate, coordinate, and access tools — turning them into full participants in the development process.&lt;/p&gt;
&lt;p&gt;Yet, the best outcomes emerge when this power is paired with human intelligence. Software experts must steer the ship — setting goals, ensuring quality, and making the high-stakes decisions that AI is not ready to own. In this partnership, AI isn’t replacing the developer. It’s elevating them.&lt;/p&gt;
&lt;p&gt;This is not the end of software engineering — it’s the beginning of a more powerful, collaborative future. And it’s time to fly IFR.&lt;/p&gt;</description></item><item><title>The CTO’s Role in Data Management and Governance: Building a Future-Proof Foundation</title><link>https://akemara.com/en/blog/cto-data-management-governance/</link><pubDate>Sat, 22 Feb 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/cto-data-management-governance/</guid><description>&lt;p&gt;In today’s data-driven world, fintech companies rely on secure, accurate, and accessible data to power everything from real-time risk assessments to personalized customer experiences. Data can be a fintech’s greatest asset — but if mismanaged, it can quickly become its biggest liability, leading to breaches, fines, and reputational damage. That’s why the Chief Technology Officer (CTO) holds such a pivotal role in data management and governance. More than simply a technology architect, the CTO serves as a strategic leader, bridging business objectives with robust technical solutions that align with ever-evolving regulatory requirements.&lt;/p&gt;
&lt;p&gt;Below, we explore the comprehensive responsibilities a CTO undertakes to define, build, and sustain a secure and scalable data landscape, highlighting best practices and emerging considerations along the way.&lt;/p&gt;
&lt;h2 id="1-why-data-management-and-governance-matter-for-fintech"&gt;1. Why Data Management and Governance Matter for Fintech&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Safeguarding Customer Trust&lt;/strong&gt;
Fintechs handle sensitive financial and personal data — from account balances to transaction histories. A single breach can undermine customer confidence and tarnish your brand, creating irreversible damage. Strong data governance instills trust by demonstrating that the company prioritizes data protection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regulatory Compliance and Avoidance of Fines&lt;/strong&gt;
Global data protection laws (GDPR in Europe, CCPA in California, and sector-specific regulations like PSD2 in the EU) impose strict obligations around data privacy, consent, and security. Non-compliance can trigger heavy fines and legal proceedings, as well as operational bans or restrictions that limit market opportunities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data as a Competitive Edge&lt;/strong&gt;
Properly governed data enables advanced analytics, artificial intelligence (AI), and machine learning (ML). These capabilities provide valuable insights for product innovation, risk assessment, fraud detection, and personalized user experiences. In an industry where speed and accuracy are critical, data-driven insights can yield significant competitive advantages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Future Growth and Scalability&lt;/strong&gt;
As fintechs scale — whether through user acquisition, new product lines, or international expansion — the complexity of data management multiplies. A well-thought-out governance framework ensures that growth is supported by robust, scalable technology and consistent processes, preventing fragmented, siloed systems down the line.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-crafting-a-unified-data-strategy-aligned-with-business-goals"&gt;2. Crafting a Unified Data Strategy Aligned with Business Goals&lt;/h2&gt;
&lt;h2 id="21-bridging-the-gap-between-business-and-technology"&gt;2.1 Bridging the Gap Between Business and Technology&lt;/h2&gt;
&lt;p&gt;The CTO has a unique vantage point, sitting at the nexus of executive strategy and technical execution. This dual perspective allows them to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Translate Business Objectives into Technical Requirements&lt;/strong&gt;
If the business wants to improve user retention by delivering better in-app insights, the CTO identifies what data must be collected, how it should be stored, and how analytics models could drive personalized user journeys.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coordinate Across Departments&lt;/strong&gt;
Data management affects multiple teams: marketing, finance, compliance, operations, and more. The CTO ensures these stakeholders collaborate under a unified data vision, helping to avoid conflicts over definitions, KPIs, or ownership.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="22-balancing-regulatory-compliance-with-innovation"&gt;2.2 Balancing Regulatory Compliance with Innovation&lt;/h2&gt;
&lt;p&gt;Fintechs are subject to multiple regulatory frameworks that dictate how data is stored, secured, and shared:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Global and Local Regulations&lt;/strong&gt;
A fintech with international customers might need to comply with GDPR in Europe, the California Consumer Privacy Act (CCPA) in the United States, and various local data protection laws in other regions. The CTO helps define processes and systems to meet these obligations, including data subject rights, breach notification procedures, and secure data transfers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Risk and Audit Readiness&lt;/strong&gt;
An effective data strategy includes processes for auditing data usage, implementing internal controls, and rapidly responding to potential security incidents. Automated logging, anomaly detection, and detailed records of data lineage can bolster the company’s ability to demonstrate compliance during regulatory audits.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="23-risk-management-and-mitigation"&gt;2.3 Risk Management and Mitigation&lt;/h2&gt;
&lt;p&gt;In fintech, the stakes are high:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Threat Monitoring and Incident Response&lt;/strong&gt;
The CTO ensures the organization has robust cyber defenses, including real-time monitoring and threat intelligence tools. Equally important is a well-documented incident response plan that quickly addresses vulnerabilities and communicates effectively with customers and stakeholders.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Retention and Destruction Policies&lt;/strong&gt;
Knowing what data to keep and for how long is vital. Over-retaining data can increase storage costs and amplify breach risks, while prematurely deleting data might violate legal requirements. The CTO, in collaboration with legal and compliance teams, defines policies that strike the right balance.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3-building-scalable-data-infrastructures"&gt;3. Building Scalable Data Infrastructures&lt;/h2&gt;
&lt;h2 id="31-choosing-between-data-lakes-and-warehouses"&gt;3.1 Choosing Between Data Lakes and Warehouses&lt;/h2&gt;
&lt;p&gt;A core responsibility for the CTO is deciding how to structure and store data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data Lakes&lt;/strong&gt;
Data lakes store raw, unstructured data in its native format, enabling flexible exploration and analytics. They are particularly useful for machine learning (ML) workloads and advanced analytics where you want to maintain data fidelity. Tools like Hadoop or cloud-native solutions (e.g., Amazon S3, Azure Data Lake Storage, or Google Cloud Storage) commonly form the basis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Warehouses&lt;/strong&gt;
These solutions provide structured repositories optimized for fast SQL queries and standardized reporting. They are essential for business intelligence dashboards and compliance reporting. Popular modern choices include Snowflake, Amazon Redshift, or Google BigQuery.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In many fintech environments, &lt;strong&gt;a hybrid approach&lt;/strong&gt; combines the flexibility of data lakes for raw data and experimentation with the performance of data warehouses for real-time insights and data analytics.&lt;/p&gt;
&lt;h2 id="32-selecting-the-right-database-technologies"&gt;3.2 Selecting the Right Database Technologies&lt;/h2&gt;
&lt;p&gt;Beyond lakes and warehouses, the CTO must also evaluate operational databases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SQL Databases&lt;/strong&gt; (e.g., PostgreSQL, MySQL) excel at transactional consistency and relational queries, making them well-suited for core banking or payment systems where data integrity is paramount.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NoSQL Databases&lt;/strong&gt; (e.g., MongoDB, Cassandra) handle unstructured or semi-structured data and scale horizontally, often used for high-velocity data ingestion, user behavior analytics, or real-time event tracking.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Graph Databases&lt;/strong&gt; (e.g., Neo4j) facilitate relationship-centric queries, helpful in fraud detection scenarios where relationships between entities are critical.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The choice ultimately depends on &lt;strong&gt;query performance, data volume and velocity, scalability, cost constraints, and the nature of the workloads&lt;/strong&gt; (transactional vs. analytical).&lt;/p&gt;
&lt;h2 id="33-ensuring-performance-and-cost-efficiency"&gt;3.3 Ensuring Performance and Cost Efficiency&lt;/h2&gt;
&lt;p&gt;As fintechs grow, so do their data volumes and infrastructure costs. The CTO’s role includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Performance Tuning&lt;/strong&gt;
Partitioning data by date or another logical segment, creating appropriate indexes, and optimizing queries can significantly reduce latency for reports and analytics.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Autoscaling and Elastic Architectures&lt;/strong&gt;
Leveraging cloud providers’ autoscaling capabilities helps handle traffic spikes — common in fintech during peak transactional hours — while avoiding overprovisioning.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observability and Cost Management&lt;/strong&gt;
Monitoring tools can track query performance, infrastructure usage, and associated costs in real-time. Effective observability allows teams to allocate budgets wisely and avoid runaway expenses.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="4-data-governance-lineage-cataloging-and-secure-sharing"&gt;4. Data Governance: Lineage, Cataloging, and Secure Sharing&lt;/h2&gt;
&lt;h2 id="41-data-lineage-and-cataloging"&gt;4.1 Data Lineage and Cataloging&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Data lineage&lt;/strong&gt; maps how data travels from its source to its endpoint, including transformations along the way. For a fintech, this might involve tracing a user’s transaction from initial capture in a payment gateway, through fraud detection systems, and into dashboards for real-time monitoring.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Importance of Lineage&lt;/strong&gt;
Pinpointing where issues originate is easier when every step in the data flow is visible. This transparency also supports compliance audits, where regulators may demand evidence of how data was processed or aggregated.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Data catalogs&lt;/strong&gt; complement lineage by serving as a centralized repository of metadata:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Metadata and Discoverability&lt;/strong&gt;
A catalog documents information about each dataset’s schema, business definition, ownership, and permissible use cases. It streamlines collaboration among data scientists, analysts, and engineers, preventing duplication of effort or conflicting definitions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="42-access-controls-and-data-security"&gt;4.2 Access Controls and Data Security&lt;/h2&gt;
&lt;p&gt;In fintech, data security is paramount:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Role-Based Access Control (RBAC)&lt;/strong&gt;
Sensitive data, such as Personally Identifiable Information (PII) and financial transaction records, should only be accessible to authorized personnel. RBAC ensures employees only view the data necessary for their roles, reducing the risk of insider threats or accidental exposure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Encryption and Tokenization&lt;/strong&gt;
Encryption at rest and in transit, using protocols like TLS and algorithms such as AES-256, is essential for safeguarding sensitive data. Tokenization can replace sensitive fields (e.g., credit card details) with tokens, minimizing the storage of actual data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero Trust Architecture&lt;/strong&gt;
Many fintechs adopt a zero trust approach, where each request to access data is authenticated and authorized, regardless of the user’s location or device. The CTO coordinates the deployment of identity management, threat detection, and multi-factor authentication (MFA) to make this happen.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="43-enabling-secure-data-sharing"&gt;4.3 Enabling Secure Data Sharing&lt;/h2&gt;
&lt;p&gt;Data doesn’t exist in silos; it’s consumed by risk, compliance, product, and marketing teams. The CTO establishes &lt;strong&gt;standardized frameworks&lt;/strong&gt; to share data securely and efficiently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;APIs and Data Virtualization&lt;/strong&gt;
Internal APIs or virtualization layers allow different teams or even external partners to access the data they need without exposing entire datasets. This also facilitates microservices architectures where each service handles a specific function without risking broad data access.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compliance-Aware Sharing&lt;/strong&gt;
Some data points might need masking or obfuscation before being shared, particularly if they fall under strict privacy regulations. Automated pipelines can enforce these compliance rules, ensuring that only appropriate data is accessible to each team.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="5-ensuring-data-quality-reliability-and-monitoring"&gt;5. Ensuring Data Quality, Reliability, and Monitoring&lt;/h2&gt;
&lt;h2 id="51-data-quality-management"&gt;5.1 Data Quality Management&lt;/h2&gt;
&lt;p&gt;Poor data quality can lead to inaccurate analytics, flawed machine learning models, and misguided business decisions. The CTO’s governance framework addresses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Validation and Cleansing&lt;/strong&gt;
Automated scripts or tools can identify anomalies, duplicates, or incomplete fields. Continuous checks at ingestion points ensure questionable records are flagged or corrected in real-time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Master Data Management (MDM)&lt;/strong&gt;
MDM solutions unify and reconcile critical data — like customer or product records — across multiple systems, creating a “single source of truth.” This consistency is vital for accurate reporting and compliance audits.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="52-reliability-and-observability"&gt;5.2 Reliability and Observability&lt;/h2&gt;
&lt;p&gt;Continuous data ops practices ensure that real-time systems function smoothly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data Pipeline Monitoring&lt;/strong&gt;
The CTO implements pipeline monitoring tools (e.g., Apache Airflow, Prefect, or cloud-native orchestration) that offer visibility into data flows. Alerting systems can signal failures or performance degradation, triggering automated or manual interventions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service-Level Agreements (SLAs)&lt;/strong&gt;
For internal stakeholders (e.g., risk analytics teams) or external partners (e.g., payment processors), the CTO often defines SLAs around data availability and latency. Meeting these SLAs is essential for maintaining trust and operational efficiency.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="6-future-proofing-the-data-strategy"&gt;6. Future-Proofing the Data Strategy&lt;/h2&gt;
&lt;h2 id="61-harnessing-ai-and-advanced-analytics"&gt;6.1 Harnessing AI and Advanced Analytics&lt;/h2&gt;
&lt;p&gt;Fintechs increasingly rely on AI/ML models for fraud detection, credit scoring, and personalized marketing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model Governance&lt;/strong&gt;
The CTO ensures that the data feeding these models is accurate, labeled correctly, and free from bias. Governance extends to model explainability and interpretability, especially critical in regulated environments where automated decisions (e.g., loan approvals) must be justifiable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-Time Analytics&lt;/strong&gt;
Stream processing technologies (like Apache Kafka and Spark Streaming) enable near-instant insights. These can detect fraud or deliver personalized recommendations on-the-fly, a capability that can significantly differentiate a fintech product in a competitive market.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="62-scaling-governance-models"&gt;6.2 Scaling Governance Models&lt;/h2&gt;
&lt;p&gt;As companies expand, governance structures that worked for a smaller startup may no longer suffice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Distributed Data Governance&lt;/strong&gt;
A distributed approach empowers each department or business unit to manage its data under a central set of rules and standards. This model can accelerate decision-making but requires careful coordination and tooling to ensure consistent implementation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automation and Self-Service&lt;/strong&gt;
Providing self-service platforms (e.g., data marketplaces or catalogs) can reduce bottlenecks. Business users can discover and request access to datasets without needing one-off approvals for every query.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="63-building-a-culture-of-data-literacy-and-stewardship"&gt;6.3 Building a Culture of Data Literacy and Stewardship&lt;/h2&gt;
&lt;p&gt;Technology alone isn’t enough; &lt;strong&gt;organizational culture&lt;/strong&gt; also shapes data success:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Training and Upskilling&lt;/strong&gt;
Employees must be educated on basic data governance principles — especially regarding privacy regulations and security best practices. Regular training sessions, workshops, or certification programs foster a data-centric mindset.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collaborative Accountability&lt;/strong&gt;
The CTO can champion cross-functional initiatives like data governance councils or “data champions” within each team. These groups ensure that ownership and accountability for data remain clear, preventing confusion or silos from forming.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="7-conclusion"&gt;7. Conclusion&lt;/h2&gt;
&lt;p&gt;Data management and governance in fintech go well beyond mere technical configurations — they represent a strategic imperative that underpins compliance, security, and growth. By creating a unified data strategy aligned with business and regulatory goals, architecting scalable and flexible data infrastructures, and establishing robust governance frameworks, the CTO ensures that data remains a dependable asset rather than a lurking liability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaways:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Strategic Alignment&lt;/strong&gt;: The CTO bridges executive vision with technical realities, ensuring data initiatives serve core business and compliance needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Robust Infrastructure&lt;/strong&gt;: Scalable data lakes, warehouses, and carefully chosen database technologies support real-time analytics and future innovation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Governance Frameworks&lt;/strong&gt;: Data lineage, cataloging, access controls, and secure sharing practices are paramount for mitigating risks and meeting regulatory demands.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quality and Reliability&lt;/strong&gt;: Continuous monitoring, data validation, and MDM ensure accuracy, consistency, and performance at scale.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Future-Proofing&lt;/strong&gt;: As fintechs evolve, governance strategies must adapt to new market conditions, technologies, and global compliance requirements, all while building a strong culture of data literacy.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ultimately, the CTO’s role in data management and governance becomes the cornerstone upon which innovative products, meaningful customer experiences, and strategic growth are built. By investing time, resources, and leadership into these areas, fintechs can confidently navigate an increasingly complex data landscape — turning potential pitfalls into competitive advantages.&lt;/p&gt;</description></item><item><title>The Role of Artificial Intelligence (AI) in Fintech</title><link>https://akemara.com/en/blog/ai-in-fintech/</link><pubDate>Fri, 21 Feb 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/ai-in-fintech/</guid><description>&lt;h2 id="introduction-and-market-overview"&gt;Introduction and Market Overview&lt;/h2&gt;
&lt;p&gt;Did you know that the global market for AI in finance is expected to exceed &lt;strong&gt;$20 billion&lt;/strong&gt; in the next few years? This explosive growth is more than a passing trend; it’s a signal of how rapidly artificial intelligence (AI) is reshaping financial services — commonly referred to as &lt;em&gt;fintech&lt;/em&gt;. From fraud detection to automated investment advice, AI is transforming how institutions and consumers manage, move, and grow money.&lt;/p&gt;
&lt;h2 id="key-definitions-and-concepts"&gt;Key Definitions and Concepts&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Fintech&lt;/strong&gt;: A blend of &lt;em&gt;finance&lt;/em&gt; and &lt;em&gt;technology&lt;/em&gt;, fintech encompasses digital tools and platforms that deliver financial services faster, cheaper, and more efficiently — ranging from mobile banking apps to peer-to-peer lending websites.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Artificial Intelligence&lt;/strong&gt;: AI comprises systems or machines that mimic human intelligence to perform tasks such as problem-solving, decision-making, and learning from experience. Subfields include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Machine Learning (ML)&lt;/strong&gt;: Algorithms learn from data and make predictions or decisions without being explicitly programmed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deep Learning&lt;/strong&gt;: A subset of ML that uses multi-layered neural networks to simulate human-like learning, often used in image or speech recognition.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Natural Language Processing (NLP)&lt;/strong&gt;: Enables machines to understand and generate human language.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="why-ai-is-indispensable-in-fintech"&gt;Why AI Is Indispensable in Fintech&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: AI can process &lt;strong&gt;large volumes of data&lt;/strong&gt; instantly, making real-time decisions possible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accuracy&lt;/strong&gt;: Well-trained machine learning models can outperform human analysts in detecting patterns or anomalies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personalization&lt;/strong&gt;: AI tailors financial products to individual user needs and habits, much like a personal trainer adapts workout routines.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="real-world-applications-and-examples"&gt;Real-World Applications and Examples&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Fraud Detection and Prevention&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;How It Works&lt;/strong&gt;: AI-driven systems continuously analyze transaction data, flagging suspicious activity in real time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-World Example&lt;/strong&gt;: &lt;em&gt;PayPal&lt;/em&gt; uses machine learning to detect fraudulent transactions. Once an anomaly is identified — say, an uncharacteristically large purchase abroad — the system automatically alerts both the merchant and the customer to verify the transaction.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Automated Portfolio Management (Robo-Advisors)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;How It Works&lt;/strong&gt;: Algorithms assess a user’s risk tolerance, goals, and time horizon, then recommend a diversified investment portfolio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-World Example&lt;/strong&gt;: &lt;em&gt;Betterment&lt;/em&gt; and &lt;em&gt;Wealthfront&lt;/em&gt; have become popular for offering low-cost, AI-driven investment services. They automatically rebalance portfolios and reinvest dividends, maximizing returns while minimizing fees.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Credit Scoring and Underwriting&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;How It Works&lt;/strong&gt;: Instead of relying solely on traditional credit scores, AI models incorporate alternative data points — like utility payments or social media behavior — to evaluate creditworthiness.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-World Example&lt;/strong&gt;: &lt;em&gt;Kabbage&lt;/em&gt; and similar fintech lenders use AI-driven analytics to approve small business loans quickly, enabling faster access to capital for entrepreneurs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Customer Service and Chatbots&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;How It Works&lt;/strong&gt;: Chatbots use NLP to respond to common customer queries, such as account balances or transaction disputes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-World Example&lt;/strong&gt;: Many banks and credit card companies now have chat interfaces on their websites or apps, offering 24/7 support without the need for human agents to handle routine tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. Predictive Analytics for Market Insights&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;How It Works&lt;/strong&gt;: AI algorithms sift through massive financial data sets — market prices, economic indicators, even news sentiment — to predict stock performance or currency fluctuations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-World Example&lt;/strong&gt;: Hedge funds and proprietary trading firms rely on AI to identify profitable trading opportunities ahead of competitors, enabling split-second decision-making.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="implementation-considerations"&gt;Implementation Considerations&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Data Quality and Management&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Why It Matters&lt;/em&gt;: Poor data — incomplete, outdated, or biased — can lead to inaccurate model predictions.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Best Practice&lt;/em&gt;: Invest in robust data governance, ensuring data is cleaned, standardized, and securely stored.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Technical Infrastructure&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Cloud vs. On-Premise&lt;/em&gt;: Cloud solutions offer scalability and cost-effectiveness but may raise data sovereignty issues.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Systems Integration&lt;/em&gt;: AI solutions must integrate seamlessly with existing core banking and payment systems, often requiring middleware and API strategies.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Cross-Functional Collaboration&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Why It Matters&lt;/em&gt;: AI success requires alignment between data scientists, compliance officers, operations, and product teams.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Best Practice&lt;/em&gt;: Establish multidisciplinary teams that meet regularly to ensure goals and performance metrics are shared.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="regulatory-and-ethical-landscape"&gt;Regulatory and Ethical Landscape&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Data Privacy and Security&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Key Concern&lt;/em&gt;: Handling sensitive customer data makes fintech a prime target for cyberattacks.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Compliance Tools&lt;/em&gt;: GDPR (in Europe) and other data protection laws mandate stringent security measures, including encryption and anonymization.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Bias and Fairness in AI Models&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Risk&lt;/em&gt;: If training data is skewed, AI models may inadvertently favor or discriminate against certain groups.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Mitigation&lt;/em&gt;: Regularly audit models for bias, use diverse datasets, and involve compliance and legal teams in model governance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Explainability and Accountability&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Explainable AI&lt;/em&gt;: As AI decisions become more complex, financial institutions must clarify how those decisions are made — especially for credit approvals or denials.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Accountability Frameworks&lt;/em&gt;: Clearly define ownership of AI-driven decisions, ensuring stakeholders understand risks and responsibilities.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="addressing-common-questions-and-misconceptions"&gt;Addressing Common Questions and Misconceptions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Will AI Replace Human Jobs in Finance?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Short Answer&lt;/strong&gt;: Certain repetitive, lower-value tasks — like data entry or basic customer service — will be automated. However, AI often creates &lt;em&gt;new&lt;/em&gt; opportunities in data science, product development, and relationship management. The focus shifts from routine work to higher-level problem-solving and strategy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Is AI Always Accurate?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Short Answer&lt;/strong&gt;: AI models are only as good as the data used to train them. Continuous monitoring, retraining, and updates are essential. Models can become stale if not regularly fed with fresh, relevant data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. How Secure Are AI-Driven Systems?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Short Answer&lt;/strong&gt;: Security risks exist, but most fintech companies and banks invest heavily in encryption, intrusion detection, and multi-factor authentication. Regulatory guidelines also enforce minimum security standards to protect consumer data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Can AI Become Biased?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Short Answer&lt;/strong&gt;: Yes, if the training data has inherent biases or lacks diversity. Rigorous testing and a commitment to ethical AI practices help reduce this risk.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="challenges-and-misconceptions"&gt;Challenges and Misconceptions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. High Implementation Costs&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Reality&lt;/em&gt;: Building AI solutions often requires specialized talent and significant R&amp;amp;D investment. Organizations can mitigate costs by starting with pilot projects or partnering with AI-focused vendors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Scalability vs. Complexity&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Reality&lt;/em&gt;: As AI tools become more advanced, they may require more computing power. Balancing performance with manageability can be an ongoing challenge.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Regulatory Uncertainty&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Reality&lt;/em&gt;: Laws and regulations lag behind technology. Fintech companies need to proactively engage with regulators and adapt to new compliance requirements as they arise.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="future-trends-and-predictions"&gt;Future Trends and Predictions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Integration with Blockchain and DeFi&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AI could enhance decentralized finance (DeFi) platforms by predicting lending rates, automating risk assessment, and creating smart contracts that self-execute based on AI-validated conditions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Explainable AI&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As consumers and regulators demand transparency, financial institutions that adopt explainable AI will likely gain a competitive advantage in terms of trust and compliance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Hyper-Personalized Financial Services&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Machine learning will delve deeper into user data — behaviors, lifestyle choices, even social media usage — to offer tailor-made financial products.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Expansion into Emerging Markets&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AI-driven fintech solutions can rapidly scale to regions with underdeveloped banking infrastructures, bringing financial inclusion to unbanked or underbanked populations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="actionable-insights-and-best-practices"&gt;Actionable Insights and Best Practices&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Start Small, Then Scale&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Launch pilot projects — like a chatbot or basic anomaly detection system — and refine them before rolling out to the entire organization.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Invest in Data Governance&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data is the cornerstone of AI success. Prioritize data quality, standardization, and security protocols.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Monitor and Update AI Models Regularly&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Markets evolve; so should your models. Continuous monitoring ensures your AI remains accurate and compliant.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Foster a Culture of Collaboration&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Break down silos between data science, product, compliance, and leadership teams. Shared objectives lead to more cohesive and effective AI strategies.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. Stay Ahead of Regulations&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep tabs on emerging guidelines for AI ethics, data protection, and financial compliance. Early adaptation avoids costly retrofits later.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Artificial Intelligence is revolutionizing the fintech landscape — streamlining operations, enhancing risk assessment, and personalizing services in ways never before possible. From fighting fraud at scale to democratizing investment advice, AI holds immense promise for financial institutions, consumers, and businesses alike. Yet with this promise comes responsibility: ethical considerations, regulatory compliance, and ongoing model maintenance are all critical components of successful AI adoption.&lt;/p&gt;
&lt;p&gt;As a CTO witnessing these developments firsthand, I can attest that organizations that &lt;em&gt;plan carefully&lt;/em&gt;, &lt;em&gt;invest in quality data&lt;/em&gt;, and &lt;em&gt;collaborate across functional lines&lt;/em&gt; will be best positioned to harness AI’s full potential. The financial services industry is at a pivotal moment; embracing AI responsibly will not only shape the future of finance — but empower a more inclusive, efficient, and innovative ecosystem for everyone.&lt;/p&gt;</description></item><item><title>Macroservices (Mini-Services): Filling The Gap Between Monoliths and Microservices</title><link>https://akemara.com/en/blog/macroservices-mini-services/</link><pubDate>Wed, 19 Feb 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/macroservices-mini-services/</guid><description>&lt;h2 id="1-introduction"&gt;1. Introduction&lt;/h2&gt;
&lt;p&gt;In the evolving world of software, choosing the right architecture can make or break an application’s success. Over time, companies have experimented with various architectural styles — from &lt;strong&gt;Monoliths&lt;/strong&gt; in the early days to &lt;strong&gt;Service-Oriented Architecture (SOA)&lt;/strong&gt; and eventually &lt;strong&gt;Microservices&lt;/strong&gt; — each offering its own set of advantages and challenges. However, many organizations have found that &lt;strong&gt;full microservices&lt;/strong&gt; can be overkill, especially for mid-sized teams and systems not operating at the scale of Netflix or Amazon. Enter &lt;strong&gt;Macroservices (Mini-Services)&lt;/strong&gt;, a balanced approach that aims to deliver modularity without the operational overhead of managing countless microservices.&lt;/p&gt;
&lt;p&gt;A 2021 O’Reilly survey found that &lt;strong&gt;77% of respondents&lt;/strong&gt; either planned to use or were already using microservices in production, but &lt;strong&gt;63%&lt;/strong&gt; reported significant complexity and overhead. Macroservices can bridge this gap by offering many of the benefits of service-based architectures while keeping complexity in check.&lt;/p&gt;
&lt;h2 id="2-history-of-software-architecture-monolith-soa-and-microservices"&gt;2. History of Software Architecture: Monolith, SOA, and Microservices&lt;/h2&gt;
&lt;h2 id="21-monolithic-architecture"&gt;2.1 Monolithic Architecture&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Definition&lt;/strong&gt;: A monolith is a single, self-contained software application in which all features, modules, and services reside in one codebase and one deployable unit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Era&lt;/strong&gt;: Common before the 2000s and still widely used for smaller projects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Challenges&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: The entire application must be scaled together, even if only one part experiences heavy load.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deployment Bottlenecks&lt;/strong&gt;: A single change or bug can delay the release of the entire system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team Coordination&lt;/strong&gt;: As the application grows, more developers work on the same codebase, leading to merge conflicts and slower development cycles.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Evidence&lt;/strong&gt;: In a 2019 enterprise survey by Lightbend, &lt;strong&gt;45%&lt;/strong&gt; of large companies cited “monolithic code complexity” as the top reason they sought new architectures.&lt;/p&gt;
&lt;h2 id="22-service-oriented-architecture-soa"&gt;2.2 Service-Oriented Architecture (SOA)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Definition&lt;/strong&gt;: SOA introduced the concept of &lt;strong&gt;services&lt;/strong&gt; that communicate over a network, often via an Enterprise Service Bus (ESB).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Era&lt;/strong&gt;: Popular in the 2000s, especially in large enterprises needing integration across multiple applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Improvements Over Monolith&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Looser Coupling&lt;/strong&gt;: Functions encapsulated into services with well-defined interfaces.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Could scale individual services, though not always as independently as microservices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration&lt;/strong&gt;: Easier integration with legacy systems via the ESB.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Challenges&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Complex ESB&lt;/strong&gt;: The ESB could become a bottleneck or “single point of failure.”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Heavy Protocols&lt;/strong&gt;: SOAP-based services often led to network overhead and complex orchestration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Evidence&lt;/strong&gt;: According to a 2010 Gartner report, more than &lt;strong&gt;60%&lt;/strong&gt; of Fortune 500 companies deployed some form of SOA, but nearly &lt;strong&gt;35%&lt;/strong&gt; reported “unsatisfactory ROI” due to ESB management complexities.&lt;/p&gt;
&lt;h2 id="23-microservices"&gt;2.3 Microservices&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Definition&lt;/strong&gt;: A collection of &lt;strong&gt;small, independent services&lt;/strong&gt;, each responsible for a single business capability and communicating via lightweight protocols (REST, gRPC, or messaging).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Era&lt;/strong&gt;: Gained mainstream popularity in the &lt;strong&gt;2010s&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Advantages&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Independent Deployability&lt;/strong&gt;: Each service can be deployed, scaled, and updated without affecting others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fault Isolation&lt;/strong&gt;: A failure in one microservice doesn’t necessarily bring down the entire system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Polyglot Persistence&lt;/strong&gt;: Each microservice can use the most suitable database or language.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Challenges&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Operational Complexity&lt;/strong&gt;: Requires robust DevOps, logging, monitoring, and distributed tracing tools.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service Proliferation&lt;/strong&gt;: Teams risk creating too many small services, leading to “microservice sprawl.”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Evidence&lt;/strong&gt;: Netflix famously runs over &lt;strong&gt;1,000&lt;/strong&gt; microservices in production, demonstrating extreme scalability but also enormous operational overhead. Uber similarly moved to microservices but struggled with managing hundreds of interconnected systems, necessitating advanced orchestration tooling.&lt;/p&gt;
&lt;h2 id="3-whats-macroservice-mini-service"&gt;3. What’s Macroservice (Mini-Service)?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Macroservices&lt;/strong&gt; (often called &lt;strong&gt;Mini-Services&lt;/strong&gt; or &lt;strong&gt;Microservices Lite&lt;/strong&gt;) are &lt;strong&gt;larger, domain-focused services&lt;/strong&gt; that stand between a single monolith and dozens of microservices. Each macroservice typically handles an entire &lt;strong&gt;bounded context&lt;/strong&gt; — a set of related business functionalities — rather than focusing on one tiny function.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Characteristics&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Domain-Centric&lt;/strong&gt;: Each macroservice covers a broader domain (e.g., “Payments &amp;amp; Billing,” “Product &amp;amp; Inventory”).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fewer Deployable Units&lt;/strong&gt;: Instead of deploying every small feature separately, you have fewer services to maintain and scale.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mixed Database Strategies&lt;/strong&gt;: Macroservices may share a database or maintain separate ones, depending on the domain’s requirements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lower Communication Overhead&lt;/strong&gt;: With fewer services, there’s less inter-service chatter and fewer network calls than in microservices.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="4-why-do-we-need-macroservices-what-gap-are-they-filling"&gt;4. Why Do We Need Macroservices? What Gap Are They Filling?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bridging Monolith and Microservices&lt;/strong&gt;:
A monolith can become unmanageable, but fully embracing microservices can lead to operational chaos. Macroservices offer a &lt;strong&gt;structured decomposition&lt;/strong&gt; without going to the extreme.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reducing Operational Overhead&lt;/strong&gt;:
Managing numerous microservices demands advanced DevOps practices, from Kubernetes orchestration to sophisticated CI/CD pipelines. Macroservices keep &lt;strong&gt;service count&lt;/strong&gt; lower, reducing overhead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team-Sized&lt;/strong&gt;:
One macroservice can be owned by &lt;strong&gt;one or two teams&lt;/strong&gt;, aligning well with the size and skill sets of many organizations. This is in contrast to microservices, where each service might require a dedicated team or squad.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalable Domains&lt;/strong&gt;:
Macroservices still allow &lt;strong&gt;selective scalability&lt;/strong&gt;. If one domain (e.g., “Search &amp;amp; Recommendation”) is under heavy load, you can scale just that macroservice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Evidence&lt;/strong&gt;: A 2022 survey by InfoQ found that &lt;strong&gt;48%&lt;/strong&gt; of mid-market companies (50–500 employees) felt microservices would be “too complicated” for their size, indicating a gap for a lighter but still modular approach.&lt;/p&gt;
&lt;h2 id="5-pros-and-cons-of-macroservices"&gt;5. Pros and Cons of Macroservices&lt;/h2&gt;
&lt;h2 id="51-pros"&gt;5.1 Pros&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Balanced Modularity&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You gain domain boundaries without incurring the complexity of dozens or hundreds of tiny services.&lt;/li&gt;
&lt;li&gt;Easier for teams to understand end-to-end functionality within one domain.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Lower Operational Complexity&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fewer services means fewer pipelines and simpler service discovery.&lt;/li&gt;
&lt;li&gt;Logging, monitoring, and tracing are more manageable than in a full microservices environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Selective Scalability&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allows scaling of specific domains experiencing higher load (e.g., “Checkout” service during Black Friday).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Faster Development &amp;amp; Deployment&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Changes in a single macroservice don’t affect the entire system, reducing monolithic “all-or-nothing” releases.&lt;/li&gt;
&lt;li&gt;Teams can deploy more frequently without microservices’ overhead.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. Easier Transition from Monolith&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Splitting an existing monolith into a few macroservices is simpler than going fully microservice-based.&lt;/li&gt;
&lt;li&gt;Minimizes refactoring risk while still providing benefits of separation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="52-cons"&gt;5.2 Cons&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Less Fine-Grained Fault Isolation&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A crash in one macroservice can impact multiple features within that domain.&lt;/li&gt;
&lt;li&gt;Fault isolation is not as robust as with microservices.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Potential for Domain Overlap&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If domains are not well-defined, macroservices can still become monolithic in nature.&lt;/li&gt;
&lt;li&gt;Shared databases or code can lead to coupling issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Scaling Entire Domain&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If one part of a macroservice domain is under load, you must scale the entire macroservice.&lt;/li&gt;
&lt;li&gt;This can lead to resource inefficiency if domains aren’t carefully scoped.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. May Still Require Future Splitting&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A growing domain may eventually need to be split again, potentially leading to re-architecture later.&lt;/li&gt;
&lt;li&gt;Teams must remain vigilant about domain boundaries.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="6-comparison-at-a-glance"&gt;6. Comparison at a Glance&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Comparison table of monolith, macroservices, and microservices across service count, deployment, scalability, complexity, fault isolation, and best fit"
srcset="https://akemara.com/en/blog/macroservices-mini-services/images/webp/6-comparison-at-a-glance_hu_8a7a955b15f07d77.webp 320w, https://akemara.com/en/blog/macroservices-mini-services/images/webp/6-comparison-at-a-glance_hu_d033e838338af1f7.webp 480w, https://akemara.com/en/blog/macroservices-mini-services/images/webp/6-comparison-at-a-glance_hu_774b5334ab1908c2.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/macroservices-mini-services/images/webp/6-comparison-at-a-glance_hu_8a7a955b15f07d77.webp"
width="760"
height="501"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/macroservices-mini-services/images/webp/6-comparison-at-a-glance_hu_3328682b85596c4a.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="7-real-world-examples"&gt;7. Real-World Examples&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Shopify&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Then&lt;/em&gt;: Started as a monolith powering its e-commerce platform.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Now&lt;/em&gt;: Has decomposed the system into &lt;strong&gt;larger services&lt;/strong&gt; (e.g., Storefront, Checkout, Inventory, Billing), rather than hundreds of microservices.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Benefit&lt;/em&gt;: Lower overhead than microservices while still allowing partial scalability during high-traffic events like Black Friday.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Airbnb&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Then&lt;/em&gt;: Monolithic Ruby on Rails application.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Transition&lt;/em&gt;: Broke the monolith into &lt;strong&gt;macroservices&lt;/strong&gt; such as “Reservations,” “Search,” “Payments.”&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Reason&lt;/em&gt;: They needed domain separation to accelerate development but didn’t want to introduce the complexity of microservices overnight.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Uber (Early Days)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Then&lt;/em&gt;: Monolithic Node.js backend.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Transition&lt;/em&gt;: Introduced a few &lt;strong&gt;mini-services&lt;/strong&gt; for trip management, driver matching, and payments.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Outcome&lt;/em&gt;: Helped them scale globally before eventually moving toward a more granular microservices approach as they grew bigger.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Evidence&lt;/strong&gt;: In 2022, a TechCrunch interview highlighted that &lt;strong&gt;30% of mid-stage startups&lt;/strong&gt; building platforms saw “macroservices” as a strategic stepping stone before potentially going full microservices if required by rapid growth.&lt;/p&gt;
&lt;h2 id="8-when-to-choose-microservices"&gt;8. When to Choose Microservices&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Massive Scale&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have &lt;strong&gt;millions&lt;/strong&gt; of daily active users and need to scale specific functionalities independently and aggressively.&lt;/li&gt;
&lt;li&gt;Example: Netflix runs over a thousand microservices to handle streaming, recommendations, user profiles, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Multiple Independent Teams&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Different teams want to deploy updates independently (the “two-pizza teams” model).&lt;/li&gt;
&lt;li&gt;Each team owns an entire service from code to database.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Requirement for Best-of-Breed Tech&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you need &lt;strong&gt;polyglot&lt;/strong&gt; technologies (e.g., Java for payments, Python for ML), microservices allow each domain to use the best stack.&lt;/li&gt;
&lt;li&gt;Macroservices tend to stick to fewer stacks to reduce complexity.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. High Fault Isolation&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A bug in the payments service shouldn’t affect streaming or search.&lt;/li&gt;
&lt;li&gt;Microservices architecture isolates each function at the service boundary.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="9-when-to-choose-macroservices-mini-services"&gt;9. When to Choose Macroservices (Mini-Services)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Monolith Is Too Big&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have a large application that’s becoming unwieldy.&lt;/li&gt;
&lt;li&gt;Deployments take hours, and unrelated features break with a single error.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Moderate Scale, Moderate Team Size&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You’re not operating at Netflix scale but need to reduce complexity and speed up deployment.&lt;/li&gt;
&lt;li&gt;A mid-sized team of developers often finds macroservices easier to manage than microservices.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Need Partial Scalability&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You want to scale only certain domains (e.g., “Order &amp;amp; Payment” during peak shopping) instead of scaling an entire monolith.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Limited DevOps Resources&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Microservices demand advanced tooling for deployment, monitoring, and debugging.&lt;/li&gt;
&lt;li&gt;Macroservices allow for &lt;strong&gt;fewer pipelines and simpler&lt;/strong&gt; operations, making them more practical for smaller or mid-level DevOps teams.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. Future Flexibility&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can always split a macroservice into multiple microservices if your system grows significantly and demands more fine-grained scaling.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="10-conclusion"&gt;10. Conclusion&lt;/h2&gt;
&lt;p&gt;Choosing the right software architecture is a &lt;strong&gt;strategic decision&lt;/strong&gt; that depends on your team size, scalability needs, and operational maturity. &lt;strong&gt;Macroservices (Mini-Services)&lt;/strong&gt; fill a crucial gap between a single monolith and a full microservices setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Monolith&lt;/strong&gt;: Great for simple, small applications but becomes a bottleneck as you scale.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microservices&lt;/strong&gt;: Ideal for massive, complex systems with many teams but can introduce significant overhead and complexity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Macroservices&lt;/strong&gt;: Provide a balanced approach — giving you &lt;strong&gt;modularity, selective scalability, and reduced deployment risk&lt;/strong&gt; without the exponential complexity that often comes with microservices.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ultimately, &lt;strong&gt;macroservices&lt;/strong&gt; can be an excellent fit for organizations looking to make a &lt;strong&gt;measured, domain-focused&lt;/strong&gt; transition away from monolithic architectures. By carefully defining bounded contexts, setting up robust DevOps pipelines, and staying vigilant about domain growth, you can enjoy the benefits of a distributed architecture with fewer headaches.&lt;/p&gt;
&lt;h2 id="further-reading--references"&gt;Further Reading &amp;amp; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Monolith to Microservices&lt;/em&gt; by Sam Newman (for insights on gradual decomposition).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2021 O’Reilly Microservices Survey&lt;/strong&gt; (for industry adoption stats).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gartner Reports on SOA&lt;/strong&gt; (for historical perspective on service-oriented approaches).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Netflix &amp;amp; Uber Tech Blogs&lt;/strong&gt; (for real-world tales of microservices complexity).&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Monolithic vs SOA vs Microservices Architecture: Choosing the Right Approach for a Fintech Startup</title><link>https://akemara.com/en/blog/monolithic-soa-microservices/</link><pubDate>Tue, 18 Feb 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/monolithic-soa-microservices/</guid><description>&lt;p&gt;In today’s fast-paced digital environment, fintech startups face the challenge of delivering reliable, secure, and highly available services to customers who expect frictionless financial transactions. One of the most critical decisions for a growing fintech company is selecting the right software architecture. Three popular architectural patterns dominate the discussion — &lt;strong&gt;Monolithic&lt;/strong&gt;, &lt;strong&gt;Service-Oriented Architecture (SOA)&lt;/strong&gt;, and &lt;strong&gt;Microservices&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This article explores the key characteristics, benefits, and trade-offs of each architecture style and offers insights into how fintech startups can decide which approach is best for their product and organizational needs. Throughout, we’ll use approximate numerical examples to illustrate real-world scenarios.&lt;/p&gt;
&lt;h2 id="1-monolithic-architecture"&gt;1. Monolithic Architecture&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Diagram of a monolithic application on a single server: front-end, login, data access, user management, and invoicing in one unit connected to one data store"
srcset="https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/monolith-diagram_hu_df642ce322b105dd.webp 320w, https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/monolith-diagram_hu_ac69e637f7072a3e.webp 480w, https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/monolith-diagram_hu_c8914376fdc06b36.webp 521w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/monolith-diagram_hu_df642ce322b105dd.webp"
width="521"
height="386"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/monolith-diagram_hu_8576261406dd1be9.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;Monolithic Architecture&lt;/strong&gt; is one in which all components of a software application — user interface, business logic, and data access — are combined into a single, unified codebase.&lt;/p&gt;
&lt;h2 id="key-characteristics"&gt;Key Characteristics&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single Codebase&lt;/strong&gt;
The entire application is built and deployed as one large unit. As an example, a small fintech MVP might have &lt;strong&gt;5,000–20,000 lines of code&lt;/strong&gt; in a single repository.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Centralized Data Management&lt;/strong&gt;
Since all services share the same database, data access and schemas are often straightforward to manage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tight Coupling&lt;/strong&gt;
Components are interdependent; a change in one module (e.g., a payment processing function) can affect the rest of the system.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="benefits-for-a-fintech-startup"&gt;Benefits for a Fintech Startup&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Simplicity and Ease of Development&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Building and deploying an MVP can be faster because everything resides in one repository.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;small team (2–3 developers)&lt;/strong&gt; can more easily coordinate their work within a single monolith.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Reduced Operational Overhead&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A single build, testing, and deployment pipeline is easier to maintain.&lt;/li&gt;
&lt;li&gt;Fewer moving parts often translate into lower initial infrastructure costs — running on a single cloud VM or a single Docker container for early pilots.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Easier Debugging&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Centralized logging and error handling make it straightforward to pinpoint issues, especially when the overall transaction volume is still modest.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="drawbacks"&gt;Drawbacks&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Limited Scalability&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scaling the entire application rather than specific components can lead to resource inefficiency. For instance, if your user authentication module is the bottleneck, you still have to redeploy the entire app to scale.&lt;/li&gt;
&lt;li&gt;Once transaction volumes grow to &lt;strong&gt;hundreds of thousands of daily operations&lt;/strong&gt;, a monolith can become unwieldy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Slower Development Cycle&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Builds and deployments become slower as the codebase grows into &lt;strong&gt;100,000+ lines&lt;/strong&gt; or more.&lt;/li&gt;
&lt;li&gt;A single bug in one area can delay releases for the entire platform.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Rigid Technology Choices&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adopting new frameworks or tech stacks is an all-or-nothing proposition, complicating upgrades and innovation at scale.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-consider-monolithic"&gt;When to Consider Monolithic&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Early-Stage MVPs&lt;/strong&gt;: If you’re launching a small pilot with &lt;strong&gt;&amp;lt; 5,000 users&lt;/strong&gt;, a monolithic architecture can help you quickly validate your core business model.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Small Teams (2–3 Developers)&lt;/strong&gt;: For startups with limited engineering resources, a single codebase can be easier to manage initially.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="2-service-oriented-architecture-soa"&gt;2. Service-Oriented Architecture (SOA)&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="SOA diagram: clients connect through an ESB to CRM, order management, and billing services that share databases"
srcset="https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/soa-diagram_hu_b0d5e17f9c80b8d3.webp 320w, https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/soa-diagram_hu_ff056088f187f1e5.webp 480w, https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/soa-diagram_hu_115ddad0eb6cd0fb.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/soa-diagram_hu_b0d5e17f9c80b8d3.webp"
width="760"
height="387"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/soa-diagram_hu_29ce6ffcc2a170bf.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Service-Oriented Architecture (SOA)&lt;/strong&gt; is a style where distinct services — each representing a particular business function — communicate through a centralized messaging or streaming system. While traditional SOA often relies on an Enterprise Service Bus (ESB) for orchestration, many modern implementations use &lt;strong&gt;Apache Kafka&lt;/strong&gt; or similar platforms as the backbone for service communication.&lt;/p&gt;
&lt;h2 id="key-characteristics-of-soa"&gt;Key Characteristics of SOA&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shared Communication Layer&lt;/strong&gt;
Services interact via an event streaming or messaging platform like &lt;strong&gt;Apache Kafka&lt;/strong&gt;, which can handle &lt;strong&gt;thousands to tens of thousands of messages per second&lt;/strong&gt; with proper configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loose Coupling&lt;/strong&gt;
Services are developed and managed independently, with Kafka facilitating communication and orchestration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Functional Decomposition&lt;/strong&gt;
Each service focuses on a specific business function (e.g., KYC, transaction processing, fraud detection).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="soa-benefits-for-a-fintech-startup"&gt;SOA Benefits for a Fintech Startup&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Improved Modularity&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each service can be developed, deployed, and maintained independently.&lt;/li&gt;
&lt;li&gt;Teams can specialize in specific business functions without disrupting other parts of the application.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Scalability and Flexibility&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Services can be scaled individually based on demand. For example, if your payment service needs to handle &lt;strong&gt;50,000 daily transactions&lt;/strong&gt;, you can allocate more compute resources specifically for it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apache Kafka&lt;/strong&gt; enables efficient handling of real-time data streams, crucial for fintech applications processing &lt;strong&gt;5,000+ events/second&lt;/strong&gt; during peak times.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Reusability&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Common services (e.g., payment gateways, notification modules) can be reused across different channels or products.&lt;/li&gt;
&lt;li&gt;Standard interfaces (e.g., REST, gRPC) and Kafka topics can be consistently adopted across the organization.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="soa-drawbacks"&gt;SOA Drawbacks&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Complex Infrastructure&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Managing a robust Kafka cluster requires careful setup, monitoring, and ongoing maintenance. A typical production-grade cluster might consist of &lt;strong&gt;3–5 brokers&lt;/strong&gt; to start, scaling to &lt;strong&gt;10+&lt;/strong&gt; for higher throughput.&lt;/li&gt;
&lt;li&gt;Ensuring reliable message delivery and data consistency across services can be complex, especially as you approach &lt;strong&gt;10,000+ messages/second&lt;/strong&gt; throughput.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Potential Bottlenecks&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Under heavy loads with average transactions more than 10,000 messages per second&lt;/strong&gt;, Kafka brokers need scaling and partition management to maintain efficient throughput.&lt;/li&gt;
&lt;li&gt;Poorly designed partitions or inadequate hardware can create a central bottleneck, affecting all services.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Higher Operational Cost&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Operating multiple services alongside a Kafka environment can be more expensive than a monolith, especially for smaller startups with tight budgets.&lt;/li&gt;
&lt;li&gt;Additional layers for monitoring, security, and management are necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-consider-soa"&gt;When to Consider SOA&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Growing Teams and Evolving Product Needs&lt;/strong&gt;: When your startup moves beyond &lt;strong&gt;5–10 developers&lt;/strong&gt; and needs to break down a monolith into manageable services, SOA can smooth the transition.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration with External Systems&lt;/strong&gt;: Kafka’s ability to handle real-time event streams is invaluable when integrating multiple external services (e.g., payment networks, third-party fraud detection).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3-microservices-architecture"&gt;3. Microservices Architecture&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Microservices diagram: a client calls an API gateway that routes to independent services under a management and orchestration layer"
srcset="https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/microservices-diagram_hu_580bb6f11c65f704.webp 320w, https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/microservices-diagram_hu_1bbbcbed6e58b0a0.webp 480w, https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/microservices-diagram_hu_6ca75266b3efd69.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/microservices-diagram_hu_580bb6f11c65f704.webp"
width="760"
height="307"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/monolithic-soa-microservices/images/webp/microservices-diagram_hu_c06f83d7b84deb6e.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Microservices Architecture&lt;/strong&gt; takes service orientation further by decomposing an application into very small, autonomous services, each focusing on a single business capability. They communicate over lightweight protocols — often HTTP/REST, gRPC, or messaging queues — potentially leveraging Kafka for streaming.&lt;/p&gt;
&lt;h2 id="key-characteristics-of-microservices"&gt;Key Characteristics of Microservices&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single Responsibility&lt;/strong&gt;
Each microservice handles one distinct function (e.g., risk scoring, user authentication). &lt;strong&gt;Services might only be 2,000–3,000 lines of code&lt;/strong&gt; each.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decentralized Data Management&lt;/strong&gt;
Each microservice may maintain its own database, reducing shared dependencies and limiting the blast radius for failures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Independent Deployment&lt;/strong&gt;
Each service can be built, tested, and deployed independently of others, ideal for teams practicing &lt;strong&gt;continuous delivery (CD)&lt;/strong&gt; with multiple deployments per day.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="microservices-benefits-for-a-fintech-startup"&gt;Microservices Benefits for a Fintech Startup&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. High Scalability and Resilience&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only the services experiencing high load need to be scaled, making the system more resource-efficient. For instance, a fraud detection service can scale up if it needs to handle &lt;strong&gt;20,000 requests/second&lt;/strong&gt; while user management remains at a lower throughput.&lt;/li&gt;
&lt;li&gt;Failures are isolated; one microservice going down doesn’t necessarily compromise the entire platform.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Faster Time-to-Market&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Independent service lifecycles allow for rapid iteration and deployment. A microservice can go from code to production in &lt;strong&gt;minutes&lt;/strong&gt;, assuming strong CI/CD pipelines.&lt;/li&gt;
&lt;li&gt;Smaller codebases per service mean fewer merge conflicts and faster testing cycles.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Technology Diversity&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each microservice can use the tech stack best suited for its needs (e.g., Python for machine learning, Go for high-performance services).&lt;/li&gt;
&lt;li&gt;Encourages experimentation with emerging frameworks (e.g., Rust, Elixir) for specialized tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Enhanced Compliance and Security&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sensitive data (e.g., cardholder details) can be confined to a single microservice that’s tightly locked down and audited.&lt;/li&gt;
&lt;li&gt;Microservices reduce the attack surface for each individual service, aiding in meeting compliance requirements like &lt;strong&gt;PCI DSS&lt;/strong&gt; or &lt;strong&gt;ISO 27001&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="microservices-drawbacks"&gt;Microservices Drawbacks&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Increased Complexity&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Managing dozens (or hundreds) of microservices is non-trivial, requiring advanced DevOps skills and robust observability. A large-scale setup might involve &lt;strong&gt;50+ microservices&lt;/strong&gt; each with its own pipeline.&lt;/li&gt;
&lt;li&gt;Network latency and distributed transaction handling can introduce new failure modes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Higher Infrastructure Costs&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each microservice demands its own runtime environment and often its own database. With &lt;strong&gt;10+ microservices&lt;/strong&gt; in production, container orchestration (e.g., Kubernetes) and multiple databases can drive up monthly bills from &lt;strong&gt;$1,000** to **$5,000+&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Requires sophisticated monitoring and logging (e.g., Prometheus + Grafana or DataDog), which can also add cost.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Steep Learning Curve&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Development teams must be well-versed in distributed systems, containerization, orchestration, and effective communication patterns.&lt;/li&gt;
&lt;li&gt;Aligning multiple teams (e.g., &lt;strong&gt;10+ squads&lt;/strong&gt;) on best practices, versioning, and standards requires strong leadership and organizational discipline.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-consider-microservices"&gt;When to Consider Microservices&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rapid Growth and High Transaction Volumes&lt;/strong&gt;: If your platform expects &lt;strong&gt;&amp;gt;1 million transactions/day&lt;/strong&gt; and needs to serve &lt;strong&gt;10,000+ concurrent users&lt;/strong&gt;, microservices can handle surging traffic while maintaining performance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex Compliance Requirements&lt;/strong&gt;: Auditing, logging, and isolating data at the service level can streamline compliance processes, essential in finance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mature Engineering and DevOps Culture&lt;/strong&gt;: If you have (or plan to build) advanced DevOps capabilities, microservices offer unparalleled agility with minimal downtime.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="4-choosing-the-right-architecture-for-a-fintech-startup"&gt;4. Choosing the Right Architecture for a Fintech Startup&lt;/h2&gt;
&lt;p&gt;Selecting between monolithic, SOA (with Apache Kafka), and microservices depends on factors such as current stage, team size, technological maturity, and long-term vision. Below are a few scenarios to guide your decision:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Early-Stage Fintech MVP&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Monolithic&lt;/strong&gt; architecture often works best when you’re validating your core business model with &lt;strong&gt;&amp;lt;5,000 daily active users&lt;/strong&gt; and a small codebase. It’s a quick way to market with minimal complexity.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Scaling and Integration Needs&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As you grow from &lt;strong&gt;2–3 developers&lt;/strong&gt; to &lt;strong&gt;10+&lt;/strong&gt;, you may need to break down your monolith. An &lt;strong&gt;SOA&lt;/strong&gt; using Kafka can help you manage real-time data pipelines, facilitate new feature rollouts, and integrate with external services.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Complex, High-Volume Operations&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Microservices&lt;/strong&gt; provide the agility and resilience needed for fintech platforms that handle &lt;strong&gt;tens of thousands of transactions per second&lt;/strong&gt; at peak. Each service can be scaled independently, improving resource allocation and minimizing downtime.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Regulatory Compliance&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SOA&lt;/strong&gt; and &lt;strong&gt;Microservices&lt;/strong&gt; can isolate sensitive data, providing more granular control over compliance and security. Microservices, in particular, offer fine-grained isolation that simplifies audits for large-scale fintech companies operating in multiple regions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="5-best-practices-and-recommendations"&gt;5. Best Practices and Recommendations&lt;/h2&gt;
&lt;p&gt;Regardless of the architecture you choose, consider the following best practices to maximize success:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Embrace DevOps and Automation&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Streamline builds, deployments, and monitoring. Leverage CI/CD pipelines (e.g., GitHub Actions, Jenkins) and Infrastructure as Code (e.g., Terraform, AWS CloudFormation).&lt;/li&gt;
&lt;li&gt;For example, aim for &lt;strong&gt;multiple deployments per week&lt;/strong&gt; or even daily if your team’s size and process maturity allow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Prioritize Security&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For fintech, robust encryption, secure coding practices, and role-based access controls (RBAC) are essential to handle potentially &lt;strong&gt;100,000+ user records&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Regular penetration testing and vulnerability scanning can help maintain trust.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Focus on Observability&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Implement comprehensive logging, metrics, and distributed tracing. Tools like Prometheus, Grafana, and OpenTelemetry can handle &lt;strong&gt;thousands of metrics&lt;/strong&gt; per second in microservices environments.&lt;/li&gt;
&lt;li&gt;Quickly identifying root causes can reduce downtime costs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Design for Failure and Resilience&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use patterns like retries, circuit breakers, and timeouts to handle transient errors. For instance, a “circuit breaker” threshold might be triggered when &lt;strong&gt;over 100 failed requests&lt;/strong&gt; occur within a 30-second window.&lt;/li&gt;
&lt;li&gt;Plan redundancy and consider multi-region deployments for high availability to ensure &lt;strong&gt;99.99%&lt;/strong&gt; uptime SLAs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. Iterate Gradually&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you plan to move from a monolith to SOA or microservices, do so in stages. Start by splitting off a high-traffic or complex module (e.g., payment processing).&lt;/li&gt;
&lt;li&gt;Measure improvements in lead time, deployment frequency, and error rates to justify further decomposition.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;No single architectural style is a one-size-fits-all solution for fintech startups. &lt;strong&gt;Monolithic&lt;/strong&gt; architectures offer a quick path to market for early-stage companies, &lt;strong&gt;SOA&lt;/strong&gt; (using Kafka or other event streaming platforms) provides modularity and smoother integrations as you grow, and &lt;strong&gt;Microservices&lt;/strong&gt; deliver ultimate scalability and agility for complex, high-volume financial systems.&lt;/p&gt;
&lt;p&gt;Ultimately, the best approach is one that &lt;strong&gt;aligns with your business objectives, team expertise, and long-term growth plans&lt;/strong&gt;. By implementing robust DevOps practices, prioritizing security and compliance, and iterating methodically, you can build a dependable and innovative fintech platform capable of meeting evolving market demands.&lt;/p&gt;</description></item><item><title>Understanding Variable Scope in Golang: Local vs. Global Variables</title><link>https://akemara.com/en/blog/golang-variable-scope/</link><pubDate>Wed, 12 Feb 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/golang-variable-scope/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In Golang, variable scope determines where a variable can be accessed and modified within a program. It sounds like a beginner topic — right up until a global you thought you updated turns out to be untouched, and the culprit is a single &lt;code&gt;:=&lt;/code&gt;. Understanding &lt;strong&gt;local&lt;/strong&gt; and &lt;strong&gt;global&lt;/strong&gt; scopes is crucial for writing maintainable and bug-free code. Additionally, when working across multiple packages, handling global variables correctly ensures modular and reusable code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This article will cover:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local vs. Global variables in Golang&lt;/li&gt;
&lt;li&gt;Updating global variables inside a function&lt;/li&gt;
&lt;li&gt;Using global variables across multiple packages&lt;/li&gt;
&lt;li&gt;Best practices for managing global variables&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="local-vs-global-variables-in-golang"&gt;Local vs. Global Variables in Golang&lt;/h2&gt;
&lt;h2 id="local-variables-functionblock-scope"&gt;Local Variables (Function/Block Scope)&lt;/h2&gt;
&lt;p&gt;A variable is &lt;strong&gt;local&lt;/strong&gt; when declared &lt;strong&gt;inside&lt;/strong&gt; a function, loop, or block. It is only accessible within that specific function or block and cannot be used elsewhere.&lt;/p&gt;
&lt;h3 id="example-local-variable-scope"&gt;Example: Local Variable Scope&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Local variable in main()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Accessible within main()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Local to the if block&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// fmt.Println(y) // ERROR: y is out of scope here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;x&lt;/code&gt; is local to &lt;code&gt;main()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;y&lt;/code&gt; is local to the &lt;code&gt;if&lt;/code&gt; block and cannot be accessed outside of it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="global-variables-package-scope"&gt;Global Variables (Package Scope)&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;global variable&lt;/strong&gt; is declared &lt;strong&gt;outside of any function&lt;/strong&gt; and can be accessed throughout the package.&lt;/p&gt;
&lt;h3 id="example-global-variable"&gt;Example: Global Variable&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;globalVar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Global variable&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Before update:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;globalVar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;updateGlobalVar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;After update:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;globalVar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Value changed globally&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;updateGlobalVar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;globalVar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Modifying global variable&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;globalVar&lt;/code&gt; is declared &lt;strong&gt;outside&lt;/strong&gt; any function, making it available to all functions in the package.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;updateGlobalVar()&lt;/code&gt; modifies &lt;code&gt;globalVar&lt;/code&gt;, and the change is reflected globally.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="updating-a-global-variable-inside-a-function"&gt;Updating a Global Variable Inside a Function&lt;/h2&gt;
&lt;p&gt;Global variables can be updated directly inside functions &lt;strong&gt;unless shadowed&lt;/strong&gt; by a local variable with the same name.&lt;/p&gt;
&lt;h3 id="example-directly-modifying-a-global-variable"&gt;Example: Directly Modifying a Global Variable&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Global variable&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Modifies the global variable&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Before increment:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;After increment:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Value updated globally&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="common-mistake-variable-shadowing"&gt;Common Mistake: Variable Shadowing&lt;/h2&gt;
&lt;p&gt;A common mistake is &lt;strong&gt;redeclaring&lt;/strong&gt; a global variable using &lt;code&gt;:=&lt;/code&gt;, which creates a new local variable instead of modifying the global one. This is the bug that looks like it works — the function prints the new value, while the global quietly keeps the old one.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Global&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;changeData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Local&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Creates a new local variable, doesn&amp;#39;t modify the global one&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Inside function:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Before:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;changeData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;After:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Still &amp;#34;Global&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Fix: Use &lt;code&gt;data = &amp;quot;Modified Global&amp;quot;&lt;/code&gt; instead of &lt;code&gt;:=&lt;/code&gt; to update the global variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="using-global-variables-across-multiple-packages"&gt;Using Global Variables Across Multiple Packages&lt;/h2&gt;
&lt;p&gt;Global variables across multiple packages must be &lt;strong&gt;exported&lt;/strong&gt; (using an uppercase name) and &lt;strong&gt;imported&lt;/strong&gt; correctly.&lt;/p&gt;
&lt;h2 id="project-structure-example"&gt;Project Structure Example&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;project/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│── main.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│── config/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── config.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="step-1-define-a-global-variable-in-config-package"&gt;Step 1: Define a Global Variable in config Package&lt;/h2&gt;
&lt;p&gt;Create &lt;code&gt;config/config.go&lt;/code&gt; and declare an exported variable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Exported global variable (uppercase first letter)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AppName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;My Awesome App&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;PrintConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;App Name:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AppName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="step-2-access-the-global-variable-in-maingo"&gt;Step 2: Access the Global Variable in main.go&lt;/h2&gt;
&lt;p&gt;Import the &lt;code&gt;config&lt;/code&gt; package and use the global variable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;project/config&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Importing the package&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Application:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AppName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Accessing global variable&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PrintConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AppName&lt;/code&gt; is accessible because it’s exported.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config.PrintConfig()&lt;/code&gt; prints the variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="step-3-modifying-global-variable-from-another-package"&gt;Step 3: Modifying Global Variable from Another Package&lt;/h2&gt;
&lt;p&gt;Global variables in a package &lt;strong&gt;can be updated&lt;/strong&gt; from other packages if they are exported.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;project/config&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Before:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AppName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Modifying global variable&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AppName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Updated App Name&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;After:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AppName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="preventing-modification-with-getter-functions"&gt;Preventing Modification with Getter Functions&lt;/h2&gt;
&lt;p&gt;To &lt;strong&gt;protect a global variable&lt;/strong&gt; from modification outside its package, make it &lt;strong&gt;private&lt;/strong&gt; and use a getter.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Unexported variable&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;My Secure App&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Getter function&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GetAppName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, &lt;code&gt;appName&lt;/code&gt; &lt;strong&gt;cannot&lt;/strong&gt; be modified from &lt;code&gt;main.go&lt;/code&gt;, but it can be accessed via &lt;code&gt;config.GetAppName()&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="using-synconce-for-initialization"&gt;Using sync.Once for Initialization&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;sync.Once&lt;/code&gt; to ensure a global variable is initialized &lt;strong&gt;only once&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sync&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;once&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Once&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AppName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;InitConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;once&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AppName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Initialized App Name&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;project/config&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InitConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Application:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AppName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="best-practices-for-global-variables"&gt;Best Practices for Global Variables&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Minimize global variables&lt;/strong&gt;: overuse leads to maintenance challenges.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Use getters/setters&lt;/strong&gt;: if modification should be restricted, expose functions instead of the variable.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Use &lt;code&gt;sync.Once&lt;/code&gt; for initialization&lt;/strong&gt;: prevent multiple (and racy) initializations.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Avoid unnecessary package dependencies&lt;/strong&gt;: keep global variables in dedicated packages to prevent circular imports.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Summary table comparing local, global, and cross-package variable scope in Go"
srcset="https://akemara.com/en/blog/golang-variable-scope/images/webp/conclusion_hu_5cf3393778e2e516.webp 320w, https://akemara.com/en/blog/golang-variable-scope/images/webp/conclusion_hu_f2bfe715b01c318.webp 480w, https://akemara.com/en/blog/golang-variable-scope/images/webp/conclusion_hu_27d6136380d88ecf.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/golang-variable-scope/images/webp/conclusion_hu_5cf3393778e2e516.webp"
width="760"
height="313"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/golang-variable-scope/images/webp/conclusion_hu_d29cfd7fcea5565e.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Golang’s variable scope follows clear rules: a variable is local by default, package-wide when declared at the top level, and visible across packages only when exported.&lt;/p&gt;
&lt;p&gt;By following best practices, you can write &lt;strong&gt;scalable, modular, and maintainable&lt;/strong&gt; Go programs. 🚀&lt;/p&gt;</description></item><item><title>Mastering the Linux column Command: A Comprehensive Guide with Examples</title><link>https://akemara.com/en/blog/linux-column-command/</link><pubDate>Wed, 15 Jan 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/linux-column-command/</guid><description>&lt;p&gt;The &lt;code&gt;column&lt;/code&gt; command in Linux is a powerful yet often overlooked tool for formatting text output into columns. It is particularly useful for making messy or unstructured data more readable by aligning it into neat tables. Whether you&amp;rsquo;re a system administrator, developer, or just a Linux enthusiast, mastering the &lt;code&gt;column&lt;/code&gt; command can save you time and make your work more efficient.&lt;/p&gt;
&lt;p&gt;In this article, we’ll explore the &lt;code&gt;column&lt;/code&gt; command in detail, covering all its use cases with practical examples. By the end, you&amp;rsquo;ll be able to use &lt;code&gt;column&lt;/code&gt; like a pro!&lt;/p&gt;
&lt;h2 id="1-introduction-to-the-column-command"&gt;1. Introduction to the column Command&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;column&lt;/code&gt; command is part of the &lt;code&gt;util-linux&lt;/code&gt; package and is available on most Linux distributions. It is designed to format text into columns, making it easier to read and analyze. It works with both piped input and files, and it supports various options for customizing the output.&lt;/p&gt;
&lt;h2 id="2-basic-usage"&gt;2. Basic Usage&lt;/h2&gt;
&lt;p&gt;The simplest way to use &lt;code&gt;column&lt;/code&gt; is to pipe text into it. By default, it formats the input into columns:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; -e &lt;span class="s2"&gt;&amp;#34;Name Age\nJohn 25\nJane 30\nDoe 22&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; column -t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Name Age
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;John &lt;span class="m"&gt;25&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Jane &lt;span class="m"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Doe &lt;span class="m"&gt;22&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;echo&lt;/code&gt; command generates a list of names and ages.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;column&lt;/code&gt; command formats the output into two columns.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3-filling-rows-before-columns--x"&gt;3. Filling Rows Before Columns (-x)&lt;/h2&gt;
&lt;p&gt;By default, &lt;code&gt;column&lt;/code&gt; fills columns before rows. However, you can use the &lt;code&gt;-x&lt;/code&gt; option to fill rows first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; -e &lt;span class="s2"&gt;&amp;#34;Name Age\nJohn 25\nJane 30\nDoe 22&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; column -x
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Name Age John &lt;span class="m"&gt;25&lt;/span&gt; Jane &lt;span class="m"&gt;30&lt;/span&gt; Doe &lt;span class="m"&gt;22&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;-x&lt;/code&gt; option ensures that rows are filled before columns.&lt;/p&gt;
&lt;h2 id="4-using-column-with-files"&gt;4. Using column with Files&lt;/h2&gt;
&lt;p&gt;Instead of piping input, you can directly pass a file to &lt;code&gt;column&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ column file.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;This formats the contents of &lt;code&gt;file.txt&lt;/code&gt; into columns.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="5-formatting-find-command-output"&gt;5. Formatting find Command Output&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;column&lt;/code&gt; command is particularly useful for cleaning up the output of commands like &lt;code&gt;find&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ find /usr/share/fonts -name &lt;span class="s2"&gt;&amp;#34;*.otf&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; column
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;This lists all &lt;code&gt;.otf&lt;/code&gt; files in &lt;code&gt;/usr/share/fonts&lt;/code&gt; and formats the output into columns.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="6-table-mode--t"&gt;6. Table Mode (-t)&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;-t&lt;/code&gt; option enables table mode, which is ideal for formatting structured data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat /etc/passwd &lt;span class="p"&gt;|&lt;/span&gt; column -t -s &lt;span class="s2"&gt;&amp;#34;:&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;This formats the &lt;code&gt;/etc/passwd&lt;/code&gt; file into a table, using &lt;code&gt;:&lt;/code&gt; as the delimiter.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="7-adding-column-headers--n"&gt;7. Adding Column Headers (-N)&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;-N&lt;/code&gt; option allows you to add column headers to the output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat /etc/passwd &lt;span class="p"&gt;|&lt;/span&gt; column -t -s &lt;span class="s2"&gt;&amp;#34;:&amp;#34;&lt;/span&gt; -N &lt;span class="s2"&gt;&amp;#34;USERNAME,PW,UID,GID,COMMENT,HOME,INTERPRETER&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;This formats &lt;code&gt;/etc/passwd&lt;/code&gt; into a table with custom column headers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="8-hiding-columns--h"&gt;8. Hiding Columns (-H)&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;-H&lt;/code&gt; option allows you to hide specific columns:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat /etc/passwd &lt;span class="p"&gt;|&lt;/span&gt; column -t -s &lt;span class="s2"&gt;&amp;#34;:&amp;#34;&lt;/span&gt; -N &lt;span class="s2"&gt;&amp;#34;USERNAME,PW,UID,GID,COMMENT,HOME,INTERPRETER&amp;#34;&lt;/span&gt; -H &lt;span class="s2"&gt;&amp;#34;PW&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;This hides the &lt;code&gt;PW&lt;/code&gt; (password) column from the output.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="9-reordering-columns--o"&gt;9. Reordering Columns (-O)&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;-O&lt;/code&gt; option allows you to reorder columns:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat /etc/passwd &lt;span class="p"&gt;|&lt;/span&gt; column -t -s &lt;span class="s2"&gt;&amp;#34;:&amp;#34;&lt;/span&gt; -N &lt;span class="s2"&gt;&amp;#34;USERNAME,PW,UID,GID,COMMENT,HOME,INTERPRETER&amp;#34;&lt;/span&gt; -O &lt;span class="s2"&gt;&amp;#34;USERNAME,UID,GID,COMMENT,HOME,INTERPRETER&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;This reorders the columns, removing the &lt;code&gt;PW&lt;/code&gt; column.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="10-json-output--j"&gt;10. JSON Output (-J)&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;-J&lt;/code&gt; option outputs the data in JSON format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat /etc/passwd &lt;span class="p"&gt;|&lt;/span&gt; column -t -s &lt;span class="s2"&gt;&amp;#34;:&amp;#34;&lt;/span&gt; -N &lt;span class="s2"&gt;&amp;#34;USERNAME,PW,UID,GID,COMMENT,HOME,INTERPRETER&amp;#34;&lt;/span&gt; -J -n &lt;span class="s2"&gt;&amp;#34;users&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;This converts the &lt;code&gt;/etc/passwd&lt;/code&gt; data into a JSON object named &lt;code&gt;users&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="11-combining-options-for-advanced-formatting"&gt;11. Combining Options for Advanced Formatting&lt;/h2&gt;
&lt;p&gt;You can combine multiple options for more advanced formatting:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat /etc/passwd &lt;span class="p"&gt;|&lt;/span&gt; column -t -s &lt;span class="s2"&gt;&amp;#34;:&amp;#34;&lt;/span&gt; -N &lt;span class="s2"&gt;&amp;#34;USERNAME,PW,UID,GID,COMMENT,HOME,INTERPRETER&amp;#34;&lt;/span&gt; -H &lt;span class="s2"&gt;&amp;#34;PW&amp;#34;&lt;/span&gt; -O &lt;span class="s2"&gt;&amp;#34;USERNAME,UID,GID,COMMENT,HOME,INTERPRETER&amp;#34;&lt;/span&gt; -J -n &lt;span class="s2"&gt;&amp;#34;users&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;This command:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Formats &lt;code&gt;/etc/passwd&lt;/code&gt; into a table.&lt;/li&gt;
&lt;li&gt;Adds column headers.&lt;/li&gt;
&lt;li&gt;Hides the &lt;code&gt;PW&lt;/code&gt; column.&lt;/li&gt;
&lt;li&gt;Reorders the columns.&lt;/li&gt;
&lt;li&gt;Outputs the result as JSON.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="12-conclusion"&gt;12. Conclusion&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;column&lt;/code&gt; command is a versatile tool for formatting text output into columns, making it easier to read and analyze. Whether you&amp;rsquo;re working with command output or files, &lt;code&gt;column&lt;/code&gt; can help you present data in a clean, tabular format.&lt;/p&gt;
&lt;h2 id="key-takeaways"&gt;Key Takeaways:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;-t&lt;/code&gt; for table mode.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;-s&lt;/code&gt; to specify a delimiter.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;-N&lt;/code&gt; to add column headers.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;-H&lt;/code&gt; to hide columns.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;-O&lt;/code&gt; to reorder columns.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;-J&lt;/code&gt; for JSON output.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more advanced data manipulation, consider using tools like &lt;code&gt;awk&lt;/code&gt;. However, for quick and easy formatting, &lt;code&gt;column&lt;/code&gt; is an indispensable tool in your Linux toolkit.&lt;/p&gt;
&lt;h2 id="final-thoughts"&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;column&lt;/code&gt; command is a simple yet powerful way to organize and present data. By mastering its options, you can save time and make your work more efficient. Whether you&amp;rsquo;re a system administrator, developer, or just a Linux enthusiast, &lt;code&gt;column&lt;/code&gt; is a tool you&amp;rsquo;ll want to keep handy.&lt;/p&gt;
&lt;p&gt;Happy formatting! 🚀&lt;/p&gt;</description></item><item><title>About</title><link>https://akemara.com/en/about/</link><pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate><guid>https://akemara.com/en/about/</guid><description>&lt;p&gt;I&amp;rsquo;m &lt;strong&gt;Ahmed K Emara&lt;/strong&gt; — a chief technology officer with 18+ years building and
scaling technology organizations across regulated FinTech, payments, e-commerce,
and enterprise platforms.&lt;/p&gt;
&lt;p&gt;Today I&amp;rsquo;m &lt;strong&gt;CTO at Tarmeez Capital&lt;/strong&gt;, a Saudi FinTech that provides financing and
investment through Islamic Sukuk. I lead the whole technology organization —
architecture, engineering, product, QA, cybersecurity, and platform operations —
and grew it from a four-person, pre-launch team to &lt;strong&gt;40+ people&lt;/strong&gt; through CMA and
SAMA licensing and the launch of a live Sukuk platform.&lt;/p&gt;
&lt;p&gt;Before Tarmeez, I spent years as a CTO and software architect across MENA and
Europe. I scaled &lt;strong&gt;EgyProTech&lt;/strong&gt;&amp;rsquo;s engineering org from &lt;strong&gt;8 to 160 people&lt;/strong&gt;; led
technology for two concurrent ventures — &lt;strong&gt;EgyQuality&lt;/strong&gt; (B2C e-commerce and
warehouse operations) and &lt;strong&gt;Dakakyn&lt;/strong&gt; (an e-commerce marketplace later acquired
into Talabat Egypt); and consulted on architecture, security, and delivery
governance for large public-sector and finance platforms.&lt;/p&gt;
&lt;p&gt;I write here about the problems I actually hit in production — backend
engineering, databases, distributed systems, application and infrastructure
security, microservices, and &lt;strong&gt;FinTech infrastructure&lt;/strong&gt;. This site is the
canonical home for that writing; copies syndicated to Medium and elsewhere point
back here.&lt;/p&gt;
&lt;p&gt;Beyond building, I mentor founders and engineering leaders starting or scaling
regulated fintech — from early architecture decisions to the operational reality
of running licensed financial systems.&lt;/p&gt;
&lt;h2 id="certifications"&gt;Certifications&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Project Management Professional (PMP) — Project Management Institute&lt;/li&gt;
&lt;li&gt;CGEIT — ISACA&lt;/li&gt;
&lt;li&gt;Certified Scrum Product Owner (CSPO)&lt;/li&gt;
&lt;li&gt;Cisco Certified Network Professional (CCNP)&lt;/li&gt;
&lt;li&gt;ISO 27001 Lead Implementer&lt;/li&gt;
&lt;li&gt;AWS Certified Solutions Architect&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&amp;rsquo;d like to work together, see &lt;a href="../mentoring/"&gt;mentoring&lt;/a&gt; or
&lt;a href="../contact/"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Contact</title><link>https://akemara.com/en/contact/</link><pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate><guid>https://akemara.com/en/contact/</guid><description>&lt;p&gt;The best way to reach me is by email: &lt;strong&gt;&lt;a href="mailto:contact@akemara.com"&gt;contact@akemara.com&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Whether it&amp;rsquo;s mentoring, a speaking invitation, or a question about something I&amp;rsquo;ve
written — tell me a little about what you&amp;rsquo;re working on and I&amp;rsquo;ll get back to you.&lt;/p&gt;
&lt;p&gt;You can also find me on the links in the site header.&lt;/p&gt;
&lt;section class="not-prose my-10 rounded-2xl border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5 p-6 sm:p-8 text-start"&gt;
&lt;h2 class="text-2xl font-bold text-gray-900 dark:text-white tracking-tight"&gt;Subscribe to the newsletter&lt;/h2&gt;
&lt;p class="mt-2 text-gray-600 dark:text-gray-300"&gt;New writing on fintech engineering, databases, and security — straight to your inbox. No spam.&lt;/p&gt;
&lt;form class="mt-4 flex flex-col sm:flex-row gap-3"
method="post" action="https://app.loops.so/api/newsletter-form/cms4os52a1tva0jwvae0zcr4v" data-ak-newsletter
data-ok-msg="Thanks — check your inbox to confirm your subscription." data-err-msg="That didn&amp;#39;t go through. Please try again."
aria-label="Subscribe to the newsletter"&gt;
&lt;label class="sr-only" for="nl-fd65b743"&gt;you@example.com&lt;/label&gt;
&lt;input id="nl-fd65b743" type="email" name="email" required placeholder="you@example.com" autocomplete="email"
class="flex-1 rounded-lg border border-gray-300 dark:border-white/15 bg-white dark:bg-white/10 px-4 py-2.5 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500 disabled:opacity-60" /&gt;
&lt;button type="submit"
class="rounded-lg bg-primary-600 hover:bg-primary-700 text-white font-semibold px-6 py-2.5 transition-colors cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed"&gt;Subscribe&lt;/button&gt;
&lt;/form&gt;
&lt;p class="mt-3 text-sm text-gray-600 dark:text-gray-300" data-ak-newsletter-status role="status" aria-live="polite" hidden&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;script&gt;
(function () {
if (window.__akNewsletter) return;
window.__akNewsletter = true;
document.addEventListener("submit", function (event) {
var form = event.target;
if (!form.matches || !form.matches("form[data-ak-newsletter]")) return;
event.preventDefault();
var status = form.parentNode.querySelector("[data-ak-newsletter-status]");
var button = form.querySelector('button[type="submit"]');
if (button) button.disabled = true;
fetch(form.action, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(new FormData(form)).toString(),
})
.then(function (response) { return response.ok; })
.catch(function () { return false; })
.then(function (ok) {
if (button) button.disabled = false;
if (ok) form.reset();
if (!status) return;
status.textContent = ok ? form.dataset.okMsg : form.dataset.errMsg;
status.hidden = false;
});
});
})();
&lt;/script&gt;</description></item><item><title>Mentoring</title><link>https://akemara.com/en/mentoring/</link><pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate><guid>https://akemara.com/en/mentoring/</guid><description>&lt;p&gt;I mentor &lt;strong&gt;founders and engineering leaders&lt;/strong&gt; building or scaling &lt;strong&gt;regulated
fintech&lt;/strong&gt;. If you&amp;rsquo;re wrestling with any of these, I can help:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Architecture &amp;amp; scale&lt;/strong&gt; — payments, ledgers, and data systems that stay
correct under load.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security &amp;amp; compliance&lt;/strong&gt; — building for regulated financial environments
without grinding to a halt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Licensing &amp;amp; audit-readiness&lt;/strong&gt; — getting from pre-launch to a licensed,
audit-ready platform.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Engineering leadership&lt;/strong&gt; — team structure, hiring, and technical strategy as
you scale.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;/h2&gt;
&lt;p&gt;I keep this informal. Tell me a bit about what you&amp;rsquo;re building and where you&amp;rsquo;re
stuck, and we&amp;rsquo;ll find a format that fits — a focused conversation or something
more ongoing.&lt;/p&gt;
&lt;p&gt;&lt;a href="../contact/"&gt;Get in touch&lt;/a&gt; with a few lines about your product and the problem
in front of you.&lt;/p&gt;
&lt;section class="not-prose my-10 rounded-2xl border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5 p-6 sm:p-8 text-start"&gt;
&lt;h2 class="text-2xl font-bold text-gray-900 dark:text-white tracking-tight"&gt;Subscribe to the newsletter&lt;/h2&gt;
&lt;p class="mt-2 text-gray-600 dark:text-gray-300"&gt;New writing on fintech engineering, databases, and security — straight to your inbox. No spam.&lt;/p&gt;
&lt;form class="mt-4 flex flex-col sm:flex-row gap-3"
method="post" action="https://app.loops.so/api/newsletter-form/cms4os52a1tva0jwvae0zcr4v" data-ak-newsletter
data-ok-msg="Thanks — check your inbox to confirm your subscription." data-err-msg="That didn&amp;#39;t go through. Please try again."
aria-label="Subscribe to the newsletter"&gt;
&lt;label class="sr-only" for="nl-741c78a3"&gt;you@example.com&lt;/label&gt;
&lt;input id="nl-741c78a3" type="email" name="email" required placeholder="you@example.com" autocomplete="email"
class="flex-1 rounded-lg border border-gray-300 dark:border-white/15 bg-white dark:bg-white/10 px-4 py-2.5 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500 disabled:opacity-60" /&gt;
&lt;button type="submit"
class="rounded-lg bg-primary-600 hover:bg-primary-700 text-white font-semibold px-6 py-2.5 transition-colors cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed"&gt;Subscribe&lt;/button&gt;
&lt;/form&gt;
&lt;p class="mt-3 text-sm text-gray-600 dark:text-gray-300" data-ak-newsletter-status role="status" aria-live="polite" hidden&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;script&gt;
(function () {
if (window.__akNewsletter) return;
window.__akNewsletter = true;
document.addEventListener("submit", function (event) {
var form = event.target;
if (!form.matches || !form.matches("form[data-ak-newsletter]")) return;
event.preventDefault();
var status = form.parentNode.querySelector("[data-ak-newsletter-status]");
var button = form.querySelector('button[type="submit"]');
if (button) button.disabled = true;
fetch(form.action, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(new FormData(form)).toString(),
})
.then(function (response) { return response.ok; })
.catch(function () { return false; })
.then(function (ok) {
if (button) button.disabled = false;
if (ok) form.reset();
if (!status) return;
status.textContent = ok ? form.dataset.okMsg : form.dataset.errMsg;
status.hidden = false;
});
});
})();
&lt;/script&gt;</description></item><item><title>Now</title><link>https://akemara.com/en/now/</link><pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate><guid>https://akemara.com/en/now/</guid><description>&lt;p&gt;A &lt;a href="https://nownownow.com/about" target="_blank" rel="noopener"&gt;now page&lt;/a&gt; — what I&amp;rsquo;m focused on at the moment.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Placeholder — update this whenever your focus changes.&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Building:&lt;/strong&gt; &lt;em&gt;current project / platform.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Writing:&lt;/strong&gt; &lt;em&gt;what you&amp;rsquo;re drafting next.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reading / learning:&lt;/strong&gt; &lt;em&gt;current topic.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mentoring:&lt;/strong&gt; open to a few founders — see &lt;a href="../mentoring/"&gt;mentoring&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Last updated: replace with the date you edit this.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Uses</title><link>https://akemara.com/en/uses/</link><pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate><guid>https://akemara.com/en/uses/</guid><description>&lt;p&gt;A running list of the tools I use to build and write. &lt;em&gt;Placeholder — replace
with your real setup.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="editor--terminal"&gt;Editor &amp;amp; terminal&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Editor: &lt;em&gt;your editor&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Terminal: &lt;em&gt;your terminal&lt;/em&gt; + shell&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="languages--data"&gt;Languages &amp;amp; data&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Backend: &lt;em&gt;your stack&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Databases: &lt;em&gt;your databases&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="infrastructure"&gt;Infrastructure&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cloud: &lt;em&gt;your provider&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Observability: &lt;em&gt;your tooling&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="desk"&gt;Desk&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Laptop: &lt;em&gt;your machine&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Display, keyboard, etc.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>How To Become One Of The Top 1% Software Engineers</title><link>https://akemara.com/en/blog/top-1-percent-software-engineer/</link><pubDate>Sat, 30 Mar 2024 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/top-1-percent-software-engineer/</guid><description>&lt;p&gt;What separates the engineer everyone consults from the one who just closes tickets? Rarely raw coding talent. It&amp;rsquo;s a set of habits — and every one of them can be learned.&lt;/p&gt;
&lt;h2 id="tldr"&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Be comfortable with ambiguity. Top engineers can solve problems without needing all the details upfront.&lt;/li&gt;
&lt;li&gt;Take initiative. Identify and solve critical problems for the team or company.&lt;/li&gt;
&lt;li&gt;Become an expert. Deepen your knowledge in a specific area and become the go-to person.&lt;/li&gt;
&lt;li&gt;Focus on customers. Build products that solve real problems for real people.&lt;/li&gt;
&lt;li&gt;Keep it simple. Avoid overengineering and focus on solutions that work now.&lt;/li&gt;
&lt;li&gt;Share your knowledge. Help others learn and grow by mentoring and giving back to the community.&lt;/li&gt;
&lt;li&gt;Understand the business. Align yourself with projects that benefit the company’s bottom line.&lt;/li&gt;
&lt;li&gt;Improve the culture. Contribute to a positive work environment that attracts and retains great talent.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;Achieving excellence in software engineering isn’t just about coding skills; it involves emulating the practices and philosophies of the industry’s top 1%&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="embrace-ambiguity"&gt;Embrace Ambiguity&lt;/h2&gt;
&lt;p&gt;The capacity to navigate uncertainty separates the good from the great. Many engineers wait for detailed tasks before they begin work, a habit that stifles innovation and independence. Top performers, however, are those who can take a broad problem statement — like improving test reliability or reducing infrastructure costs — and devise solutions without step-by-step guidance. Embracing ambiguity involves accepting responsibility despite the unknowns, a skill that demonstrates maturity and expertise. Leaders value engineers who can define success criteria, make reasoned assumptions, and explore solutions proactively.&lt;/p&gt;
&lt;h2 id="excel-in-problem-solving"&gt;Excel in Problem-Solving&lt;/h2&gt;
&lt;p&gt;Exceptional engineers are proactive problem-solvers, capable of identifying and prioritizing issues that significantly impact the team or company. This skill requires an in-depth understanding of the codebase, open communication with colleagues, and a keen analysis of metrics to unearth underlying issues. Once a problem is identified, convincing stakeholders of its urgency becomes the next challenge. Effective persuasion is often backed by solid data and a clear demonstration of the issue’s impact on the project or business. Continuous learning and a fresh perspective are invaluable for uncovering new problems and advocating for their resolution.&lt;/p&gt;
&lt;h2 id="deepen-your-expertise"&gt;Deepen Your Expertise&lt;/h2&gt;
&lt;p&gt;Becoming a domain expert means being the go-to person for specific technical or product areas. This status is achieved through relentless learning, tackling ambiguous problems, and contributing to solutions that drive the project forward. Spending time in a particular domain reveals its complexities and nuances, establishing you as a key resource. Confidence and a willingness to share knowledge and assist others are traits that further solidify this reputation. The journey to expertise requires patience, dedication, and an active engagement in learning and problem-solving.&lt;/p&gt;
&lt;h2 id="focus-on-the-customer"&gt;Focus on the Customer&lt;/h2&gt;
&lt;p&gt;The most successful engineers are those who prioritize the needs and feedback of the end-users. Building solutions that address real-world problems ensures relevance and value. This customer-centric approach goes beyond the technical aspects of development to consider the practical impact of the product. Keeping in constant touch with customer feedback and being open to iterative improvements based on their experiences can distinguish a good engineer from a great one.&lt;/p&gt;
&lt;h2 id="simplify-solutions"&gt;Simplify Solutions&lt;/h2&gt;
&lt;p&gt;In a field where complexity is often a given, the ability to simplify solutions is a much-valued skill. Over-engineering can lead to wasted resources and overly complicated systems that are difficult to maintain. Top engineers advocate for building what’s needed now, with an eye towards future adaptability. They prioritize functionality and user experience over technical showmanship, delivering products that are genuinely useful and user-friendly.&lt;/p&gt;
&lt;h2 id="contribute-to-the-community"&gt;Contribute to the Community&lt;/h2&gt;
&lt;p&gt;Beyond individual achievements, the best engineers understand the value of giving back to the community. Whether through mentoring, contributing to open-source projects, or sharing knowledge at conferences and in blogs, these contributions enrich the entire ecosystem. Engaging with the community not only helps others grow but also enhances the contributor’s skills and reputation.&lt;/p&gt;
&lt;h2 id="understand-business-impact"&gt;Understand Business Impact&lt;/h2&gt;
&lt;p&gt;Top engineers grasp the significance of aligning their projects with the company’s financial goals. They choose projects with the potential to significantly affect the bottom line, understanding that technology is a means to an end — driving business success. Awareness of the business context ensures that technical efforts are directed toward high-impact outcomes, making the engineer an invaluable asset to the company.&lt;/p&gt;
&lt;h2 id="cultivate-a-positive-culture"&gt;Cultivate a Positive Culture&lt;/h2&gt;
&lt;p&gt;Finally, the influence of a great engineer extends beyond technical contributions to include cultural impact. Fostering a positive, inclusive, and innovative company culture attracts talent and drives success. Participation in hackathons, blogging, speaking at conferences, and improving hiring practices are all ways in which engineers can contribute to a vibrant and productive work environment.&lt;/p&gt;
&lt;p&gt;In summary, becoming a top software engineer requires a blend of technical excellence, business acumen, and a commitment to the broader community. By focusing on these eight areas, engineers can elevate their careers and make meaningful contributions to their teams, companies, and the industry at large.&lt;/p&gt;</description></item><item><title>Regulated FinTech Platforms</title><link>https://akemara.com/en/projects/regulated-fintech-platforms/</link><pubDate>Mon, 15 Jan 2024 00:00:00 +0000</pubDate><guid>https://akemara.com/en/projects/regulated-fintech-platforms/</guid><description>&lt;p&gt;&lt;strong&gt;The problem.&lt;/strong&gt; Launching a Saudi FinTech that finances and invests through
Islamic Sukuk means building correct, auditable money flows &lt;em&gt;and&lt;/em&gt; clearing CMA
and SAMA licensing before you can go live.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The approach.&lt;/strong&gt; As CTO at Tarmeez Capital, I led architecture and delivery end
to end — financing and investment workflows, back-office and compliance systems,
and the controls and evidence needed for regulatory and Big-4 audits — on a
modern stack (Go, React, Flutter, Kafka, PostgreSQL, Kubernetes).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The impact.&lt;/strong&gt; We took the platform from a four-person pre-launch team to 40+
people and a live, CMA/SAMA-licensed Sukuk financing and investment platform,
with the governance and audit trail to keep it compliant.&lt;/p&gt;</description></item><item><title>Commerce &amp; Marketplace Platforms</title><link>https://akemara.com/en/projects/commerce-marketplace-platforms/</link><pubDate>Fri, 01 Sep 2023 00:00:00 +0000</pubDate><guid>https://akemara.com/en/projects/commerce-marketplace-platforms/</guid><description>&lt;p&gt;&lt;strong&gt;The problem.&lt;/strong&gt; High-volume commerce breaks on legacy systems: manual
reconciliation, fragile inventory, and operations that can&amp;rsquo;t scale at peak.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The approach.&lt;/strong&gt; Across two ventures I rebuilt the core. At &lt;strong&gt;Dakakyn&lt;/strong&gt;, I
replaced a legacy monolith with an ERP-integrated marketplace — bi-directional
order, inventory, and accounting sync, demand forecasting, and a supplier
portal. At &lt;strong&gt;EgyQuality&lt;/strong&gt;, I built multi-warehouse order management with
barcode-driven fulfillment and Redis-backed catalog and inventory caching for
peak load.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The impact.&lt;/strong&gt; Faster, more reliable operations and higher order throughput —
and at Dakakyn, a platform strong enough that the business was acquired
(Otlob → Delivery Hero → Talabat Egypt).&lt;/p&gt;</description></item><item><title>AI-Assisted Credit Decisioning &amp; Automation</title><link>https://akemara.com/en/projects/ai-credit-decisioning/</link><pubDate>Thu, 01 Jun 2023 00:00:00 +0000</pubDate><guid>https://akemara.com/en/projects/ai-credit-decisioning/</guid><description>&lt;p&gt;&lt;strong&gt;The problem.&lt;/strong&gt; Manual credit decisions took days — too slow to compete — but a
regulated lender can&amp;rsquo;t simply hand approvals to a model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The approach.&lt;/strong&gt; I led AI- and automation-assisted credit decisioning that
streamlines financing workflows while keeping a human in the loop for final
approval, in line with regulatory expectations. The same automation push
extended across financing, investment, reconciliation, and back-office
operations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The impact.&lt;/strong&gt; Financing decision turnaround dropped from several days to a few
hours, with regulatory-grade human oversight preserved.&lt;/p&gt;</description></item><item><title>Platform Cost Efficiency &amp; Infrastructure</title><link>https://akemara.com/en/projects/platform-cost-efficiency/</link><pubDate>Wed, 01 Mar 2023 00:00:00 +0000</pubDate><guid>https://akemara.com/en/projects/platform-cost-efficiency/</guid><description>&lt;p&gt;&lt;strong&gt;The problem.&lt;/strong&gt; Cloud bills balloon fast in FinTech, and regulated workloads
still demand reliability, security, and observability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The approach.&lt;/strong&gt; I built a cost-efficient operating model that blends cloud,
bare metal, and virtualization — OCI, OVH, and Proxmox under Kubernetes and
Docker, with GitLab CI/CD, automation, and disciplined vendor governance — plus
SRE practices for monitoring, alerting, and incident response.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The impact.&lt;/strong&gt; One of the most cost-efficient infrastructure models in our
market, without trading away uptime or audit-grade controls.&lt;/p&gt;</description></item></channel></rss>