<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Packt Deep Engineering: Practical Deep-Dives]]></title><description><![CDATA[Hands‑on explorations of tools, patterns, and practices shaping modern software]]></description><link>https://deepengineering.net/s/practical-deep-dives</link><image><url>https://substackcdn.com/image/fetch/$s_!H5BJ!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736bc1ee-d689-497e-83a8-7d9bf9022eb9_600x600.png</url><title>Packt Deep Engineering: Practical Deep-Dives</title><link>https://deepengineering.net/s/practical-deep-dives</link></image><generator>Substack</generator><lastBuildDate>Wed, 12 Aug 2026 19:25:27 GMT</lastBuildDate><atom:link href="https://deepengineering.net/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Packt]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[deepengineering@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[deepengineering@substack.com]]></itunes:email><itunes:name><![CDATA[Packt]]></itunes:name></itunes:owner><itunes:author><![CDATA[Packt]]></itunes:author><googleplay:owner><![CDATA[deepengineering@substack.com]]></googleplay:owner><googleplay:email><![CDATA[deepengineering@substack.com]]></googleplay:email><googleplay:author><![CDATA[Packt]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[I burned 51 million tokens on one merge conflict, and the model was not the problem]]></title><description><![CDATA[Rory Preddy on why agent bills climb while token prices fall, and the caching, compaction, routing and loop controls that cut spend in production]]></description><link>https://deepengineering.net/p/token-efficiency-rory-preddy-agent-token-costs</link><guid isPermaLink="false">https://deepengineering.net/p/token-efficiency-rory-preddy-agent-token-costs</guid><pubDate>Thu, 30 Jul 2026 12:06:07 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/fb227e32-e539-4c24-a3da-5b6ba7b68be5_3200x1800.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p><span>By </span><a href="https://za.linkedin.com/in/rorypreddy"><span>Rory Preddy</span></a><span>, AI Advocate, Developer Relations at </span><strong><span>Microsoft</span></strong><span> and </span><strong><span>GitHub</span></strong><span>. Creator of </span><a href="https://github.com/microsoft/LangChain4j-for-Beginners"><span>LangChain4j for Beginners</span></a><span>. | Edited by </span><span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Saqib Jan&quot;,&quot;id&quot;:427210082,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/997a788a-cd78-4f84-9b3b-c72ab6dc0153_1008x1008.jpeg&quot;,&quot;uuid&quot;:&quot;a2513e3d-aec1-40f3-b727-68756db92fb3&quot;}" data-component-name="MentionToDOM"></span> </p></blockquote><p><span>I have been in IT for 27 years, and 23 of those as a programmer. I started as a Java developer. I moved to Microsoft seven years and eight months ago, and I have been a developer advocate for five and a half of those, first in cloud advocacy and now in AI.</span></p><p><span>For most of that time my job was to tell people to go and build. Lately I have started saying something else first. Take a step back. Not because the excitement is wrong, but because I keep meeting developers who ran out of tokens and cannot tell me where they went. I ask what happened and I hear the same answer. I used the top model, I sent it away, and it ran out. I ask whether they needed the best model for that job and they say they do not know.</span></p><p><span>That is not a model problem. That is a foundation problem, and it is fixable in an afternoon.</span></p><p>So let&#8217;s dig deeper into what tokens actually cost you, the two mechanisms that cut the bill the most, the patterns that keep the expensive model out of cheap work, and the setup you do before any of it.</p><blockquote><p><em>This deep dive is adapted from Rory Preddy&#8217;s Deep Engineering Live session on token efficiency, edited from the session transcript for length and clarity. Slides from the talk are here, and the full recording is here.</em></p></blockquote><div class="file-embed-wrapper" data-component-name="FileToDOM"><div class="file-embed-container-reader"><div class="file-embed-container-top"><image class="file-embed-thumbnail-default" src="https://substackcdn.com/image/fetch/$s_!0Cy0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack.com%2Fimg%2Fattachment_icon.svg"></image><div class="file-embed-details"><div class="file-embed-details-h1">Token Efficiency Foundry Instantmodels</div><div class="file-embed-details-h2">2.37MB &#8729; PDF file</div></div><a class="file-embed-button wide" href="https://deepengineering.net/api/v1/file/027ad221-82df-4234-be06-3561cd784573.pdf"><span class="file-embed-button-text">Download</span></a></div><a class="file-embed-button narrow" href="https://deepengineering.net/api/v1/file/027ad221-82df-4234-be06-3561cd784573.pdf"><span class="file-embed-button-text">Download</span></a></div></div><p><a href="https://deepengineering.net/api/v1/file/e3851c6c-ccc1-4eda-be9b-0c29c3db9a38.pdf">Download</a></p><div id="youtube2-tVRcYTX_HCg" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;tVRcYTX_HCg&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/tVRcYTX_HCg?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><h2><span>Four levers, and one of them gets ignored</span></h2><p><span>You have four things to pull, and they move independently.</span></p><p><span>Model choice sets the unit price on every token in the exchange. Prompt structure sets how many input tokens you send and whether any of them qualify for a discount. Cache-key stability decides whether a repeated block earns the cached rate or gets billed again at full price. And output limits cap the completion, which is the one people skip.</span></p><p><span>Output is where I see the least attention and some of the highest prices. On the GPT-5.6 family the flagship Sol tier runs 5 dollars per million input tokens against 30 dollars per million output. Luna, the small one, is 1 dollar and 6 dollars. Look at the gap between those two numbers on the same row before you tune anything else. Watch your outputs, not just your inputs.</span></p><p><span>One more thing before the mechanisms. You do not have to guess at any of these rates. Azure publishes live pricing at prices.azure.com/api/retail/prices and you can query it. It looks confusing the first time you open it, because a single meter name folds the model family, the tier, the processing mode and the billing unit into one string, and there is a region attached to all of it. So let&#8217;s all breathe. Two things in there matter. The same model bills differently in different regions, and batch processing is its own meter rather than a discount on the standard rate.</span></p><p><span>It is not complicated. It is just not written down anywhere you were looking.</span></p><h2><span>Caching, and why you only pay for the tail</span></h2><p><span>Ask a model to tell you a joke. Now ask it again, exactly the same way. You are not going to be charged the same for the second one, or if you are, it will be a very small charge.</span></p><p><span>Now scale that up. Say I am an insurance company and I need to send millions of customers their latest statement. Every one of those requests carries the same instructions, the same reference material, the same everything, and then right at the bottom there is a name and a dollar amount that changes. That big identical block at the front is the stable prefix. The little bit at the end that changes is the tail.</span></p><p><span>You do not pay full price for the prefix. You pay for the tail.</span></p><p><span>The first call is a miss. The model stores the prefix and bills you the full input at standard rate. Every call after that, sent with the same prefix and the same cache key, is a hit, and that prefix gets billed as cheaper cached input. In my demo the prefix carried around 120 identical reference sections behind a run-scoped key, and warming the cache before the measured calls saved 99 percent on the repeated path.</span></p><p><span>Here is the part that catches people. The saving depends on those bytes being identical. Put a timestamp near the top of your prompt, or a request ID, or a freshly serialised list of tools, and you have moved the boundary and lost the discount on every single call. It will look completely harmless in review. So think ahead with the prompt structure. Decide what is stable and put it first, on purpose, before you need it.</span></p><h2><span>Compaction, so the context stops growing</span></h2><p><span>Caching handles repetition between calls. Compaction handles what piles up inside one conversation.</span></p><p><span>Every time you talk to an agent it remembers the conversation, and it sends that history again on the next turn. So the tokens you are billed per turn climb, and climb, and climb, even when the actual work per turn has not changed at all. Model a twelve-turn session and you are sending around 1,920 context tokens per turn by the end. Compact it along the way and it resets near 795 and never goes above that band. That projection is illustrative, but the summary reduction underneath it is measured, 62 percent, from 795 tokens down to 302.</span></p><p><span>What makes a compaction prompt work is being specific about what survives. Mine asks for at most six sentences, no bullets, no nested lists. It keeps only what the next turn needs, so the goal, the key facts, the files to update, the validation and deploy commands, the blockers and any privacy constraints. It throws out repetition, resolved dead ends, greetings, transient logs, and exact values that do not matter any more, and it turns quota findings into sanitised evidence instead of repeating every number. On a working-notes example that took a prompt from 409 tokens down to 233.</span></p><p><span>I also tried the other direction, squeezing the output instead of the context. I have a prompt that tells the model to answer like a caveman. Why use many token when few do trick. Drop the filler words and the articles, keep every technical fact, every command, every file path and every error string exact. Brain still big, mouth small.</span></p><p><span>It works, and the output count came down against a roughly 600-token baseline. But I will be honest with you about the trade. The caveman answer lost some of the understanding the fuller answer had. It is not as detailed. If your reader has to come back and ask a follow-up question, you have paid for that saving twice. A hard cap on output length is blunter but more predictable, because it truncates rather than condenses, so use it where the shape of the answer is already known.</span></p><h2><span>Set up the project before you write anything</span></h2><p><span>This is the least exciting part and it decides most of your cost.</span></p><p><span>Three files. A devcontainer.json, so the project runs on everyone&#8217;s machine and not just yours. An instructions file, so the agent has standing context and is not working blind. And an agent profile, which is the one people leave out.</span></p><p><span>The agent profile declares which tools the agent can reach for, and mine only has the tools it needs to function. That matters more than it sounds, because every tool you register ships its schema into the request on every single call. Leave a pile of MCP tools switched on that this task never touches and you are paying to send that JSON back and forth all day. Drop the ones you are not using.</span></p><p><span>Then three habits alongside it. Scope your sessions tightly. Keep your prompts precise and short. And if a direct completion will do the job, use a direct completion instead of turning an agent loose on it.</span></p><p><span>I build these three files with the cheapest model I have. Luna, reasoning switched off. It costs me almost nothing to create the baseline, and I would rather learn to do it properly at that price. On a cost-tips run I will say it out loud as I go. I am not using reasoning. I do not need reasoning right here. That is the right tool for the job.</span></p><h2><span>What an agent actually is</span></h2><p><span>An agent has agency. Agency is self determination, which means the agent understands there is a task it has to achieve. You can only give it that task if you understand it yourself.</span></p><p><span>What an agent is not is a large language model that you tell to go in and do whatever it wants. That is madness.</span></p><p><span>Here is the version I trust. I have a supervisor agent that transfers 100 dollars from one person to another and converts it to euros. Underneath it there is a withdraw agent, a credit agent and an exchange agent. Each one has a small scope. It runs on GPT-4o, so it costs me cents per month. It runs on Azure with managed identity, so it is locked down. I created the project with the dev container and the agent profile before any of it ran.</span></p><p><span>That is what I consider an agent. There is a plan, you built it, you locked it down, and it performs one well-contextualised task properly.</span></p><h2><span>Eight patterns that keep the big model out of cheap work</span></h2><p><span>Once the foundation is there, the patterns are about one idea. Do not let the largest model see work it does not need to see. My tiers are gpt-5.6-luna small, gpt-5.6-terra medium and gpt-5.6-sol large, and the numbers below are projections from my own demo harness, not benchmarks.</span></p><p><span>A </span><strong><span>router</span></strong><span> puts a cheap classifier in front and sends the request down one specialist path. 60 to 80 percent. </span><strong><span>Triage</span></strong><span> is similar but the gate is deterministic and costs zero tokens, and it only escalates when the complexity earns it. 50 to 70 percent. </span><strong><span>Context compression</span></strong><span> has Luna condense a long transcript so Sol never sees the whole thing. 30 to 60 percent. </span><strong><span>Retrieval</span></strong><span> pulls the chunks that matter instead of the whole corpus. 40 to 60 percent.</span></p><p><strong><span>Tool use</span></strong><span> moves exact computation out of probabilistic generation and into code, and it goes hand in hand with keeping that tool list short. 30 to 50 percent. </span><strong><span>Step-back planning</span></strong><span> resolves the frame before the expensive execution starts, and it only pays when it prevents retries and rework. 20 to 40 percent. </span><strong><span>Caching</span></strong><span> covers the repeated path, 50 to 90 percent on a hit. And </span><strong><span>batching</span></strong><span> I will not oversell, because a parallel mapper lowers your wall-clock time and saves you no tokens at all.</span></p><p><span>Retrieval deserves more than one line, because everyone reaches for it and plenty of people misconfigure it. The query becomes an embedding, the embedding drives a vector search, the search returns chunks, and the model answers from those chunks alone. So your chunking and your embedding model decide your answer quality and your token count at the same time. The failure I see is sending everything back every time, because that is easier than tuning retrieval, and then the customer asks why it is slow and you ask why it is expensive. My retrieval demo projected 67 percent against sending the whole document. All you need is the dollar amount, not the entire insurance quote.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nzRB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7545fdb-2580-4f83-a1cd-d58d997deeb3_3200x2400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nzRB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7545fdb-2580-4f83-a1cd-d58d997deeb3_3200x2400.png 424w, https://substackcdn.com/image/fetch/$s_!nzRB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7545fdb-2580-4f83-a1cd-d58d997deeb3_3200x2400.png 848w, https://substackcdn.com/image/fetch/$s_!nzRB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7545fdb-2580-4f83-a1cd-d58d997deeb3_3200x2400.png 1272w, https://substackcdn.com/image/fetch/$s_!nzRB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7545fdb-2580-4f83-a1cd-d58d997deeb3_3200x2400.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nzRB!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7545fdb-2580-4f83-a1cd-d58d997deeb3_3200x2400.png" width="1200" height="900" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b7545fdb-2580-4f83-a1cd-d58d997deeb3_3200x2400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:1092,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:434044,&quot;alt&quot;:&quot;the eight-pattern grid slide&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.net/i/209102293?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7545fdb-2580-4f83-a1cd-d58d997deeb3_3200x2400.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="the eight-pattern grid slide" title="the eight-pattern grid slide" srcset="https://substackcdn.com/image/fetch/$s_!nzRB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7545fdb-2580-4f83-a1cd-d58d997deeb3_3200x2400.png 424w, https://substackcdn.com/image/fetch/$s_!nzRB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7545fdb-2580-4f83-a1cd-d58d997deeb3_3200x2400.png 848w, https://substackcdn.com/image/fetch/$s_!nzRB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7545fdb-2580-4f83-a1cd-d58d997deeb3_3200x2400.png 1272w, https://substackcdn.com/image/fetch/$s_!nzRB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7545fdb-2580-4f83-a1cd-d58d997deeb3_3200x2400.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><span>And where I got it wrong</span></h2><p><span>I burnt 51 million tokens recently, and I used the wrong model for a specific purpose.</span></p><p><span>I was resolving a merge conflict, and I gave it to Luna. Luna could not solve the problem. That on its own would have been a cheap mistake. What made it expensive is that I had not set a loop to say, after a while, stop. So it kept going.</span></p><p><span>Two lessons, and they pull against each other slightly. Match the model to the bottleneck rather than always reaching down, because a cheap model that cannot finish costs more than an expensive model that can. And put an exit condition on every iterative agent, every time.</span></p><p><span>I have a refinement loop that writes a story and then reviews its own work. It caps at five iterations and accepts a score around 80 percent. It is not going to be perfect. Sometimes you really need to say, I do not need it perfect. Caching and compaction are both good, and you still want a hard stop when it hits the score you asked for.</span></p><h2><span>Build an agent that watches the bill</span></h2><p><span>You can point agents at your own costs, which is my favourite version of this.</span></p><p><span>I have a token cost runner. Its job is to execute real flows against a deployed Foundry model, read the actual token usage, and compare it against live pricing. So the number comes out of a run rather than out of arithmetic on a rate card. I ask it to find me the lowest-cost path for a prompt with a cached prefix and a changing tail, and it tells me.</span></p><p><span>Build agents to help you lower your cost for agents. Think of it as a pyramid. What you want to achieve sits at the top, and underneath it there is a foundation holding your dev container, your instructions, your agent profiles, your compaction and your compression. Everything above the foundation inherits whatever the foundation does.</span></p><h2><span>Two commands, if you want the short version</span></h2><p><span>If patterns are more than you want right now, there are two commands.</span></p><p><span>The first reads your session history and tells you where the money went. Mine was not flattering. Across sixty days my input tokens outweighed my output roughly 100 to 1. Opus 4.8 took 45.8 million input tokens at an average of 87,000 per event. One single session accounted for 14.4 million on its own. And one session climbed from 49,000 to 174,000 input tokens over 66 turns with no manual compaction at all, so auto-compaction only fired at turn 67, right at the end. Every turn past about turn 30 was resending well over 120,000 tokens on a premium model. I was the one not compacting long sessions.</span></p><p><span>The second sets a budget per session, warns you at a threshold and stops you at the limit. Mine stops after 34.7 of 30 credits and asks whether I want to add credits, raise the limit or remove it.</span></p><p><span>I also keep a canvas built from that same session data as a daily token pulse, and I refresh it across seven and thirty day windows. None of this optimises anything by itself. It is just awareness. But a number nobody looks at twice will never change how anyone works, and most of the waste I see is not a decision anyone made. It is a thing nobody checked.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!k5Mc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47286cfb-c40f-4d3a-8654-2b4179bc8ac4_3200x1800.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!k5Mc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47286cfb-c40f-4d3a-8654-2b4179bc8ac4_3200x1800.png 424w, https://substackcdn.com/image/fetch/$s_!k5Mc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47286cfb-c40f-4d3a-8654-2b4179bc8ac4_3200x1800.png 848w, https://substackcdn.com/image/fetch/$s_!k5Mc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47286cfb-c40f-4d3a-8654-2b4179bc8ac4_3200x1800.png 1272w, https://substackcdn.com/image/fetch/$s_!k5Mc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47286cfb-c40f-4d3a-8654-2b4179bc8ac4_3200x1800.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!k5Mc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47286cfb-c40f-4d3a-8654-2b4179bc8ac4_3200x1800.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/47286cfb-c40f-4d3a-8654-2b4179bc8ac4_3200x1800.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:263312,&quot;alt&quot;:&quot;the two terminal screenshots, the cost-tips output and the session-limit prompt&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.net/i/209102293?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47286cfb-c40f-4d3a-8654-2b4179bc8ac4_3200x1800.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="the two terminal screenshots, the cost-tips output and the session-limit prompt" title="the two terminal screenshots, the cost-tips output and the session-limit prompt" srcset="https://substackcdn.com/image/fetch/$s_!k5Mc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47286cfb-c40f-4d3a-8654-2b4179bc8ac4_3200x1800.png 424w, https://substackcdn.com/image/fetch/$s_!k5Mc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47286cfb-c40f-4d3a-8654-2b4179bc8ac4_3200x1800.png 848w, https://substackcdn.com/image/fetch/$s_!k5Mc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47286cfb-c40f-4d3a-8654-2b4179bc8ac4_3200x1800.png 1272w, https://substackcdn.com/image/fetch/$s_!k5Mc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47286cfb-c40f-4d3a-8654-2b4179bc8ac4_3200x1800.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Cost tips</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QNs4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04c7cf72-8a03-46bb-b6b5-60755af80f47_3200x1800.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QNs4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04c7cf72-8a03-46bb-b6b5-60755af80f47_3200x1800.png 424w, https://substackcdn.com/image/fetch/$s_!QNs4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04c7cf72-8a03-46bb-b6b5-60755af80f47_3200x1800.png 848w, https://substackcdn.com/image/fetch/$s_!QNs4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04c7cf72-8a03-46bb-b6b5-60755af80f47_3200x1800.png 1272w, https://substackcdn.com/image/fetch/$s_!QNs4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04c7cf72-8a03-46bb-b6b5-60755af80f47_3200x1800.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QNs4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04c7cf72-8a03-46bb-b6b5-60755af80f47_3200x1800.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/04c7cf72-8a03-46bb-b6b5-60755af80f47_3200x1800.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:230249,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.net/i/209102293?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04c7cf72-8a03-46bb-b6b5-60755af80f47_3200x1800.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QNs4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04c7cf72-8a03-46bb-b6b5-60755af80f47_3200x1800.png 424w, https://substackcdn.com/image/fetch/$s_!QNs4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04c7cf72-8a03-46bb-b6b5-60755af80f47_3200x1800.png 848w, https://substackcdn.com/image/fetch/$s_!QNs4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04c7cf72-8a03-46bb-b6b5-60755af80f47_3200x1800.png 1272w, https://substackcdn.com/image/fetch/$s_!QNs4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04c7cf72-8a03-46bb-b6b5-60755af80f47_3200x1800.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Session limit</figcaption></figure></div><h2><span>Pick the harness you already pay for</span></h2><p><span>People ask me which harness balances capability against cost. My honest answer is that what you are trying to achieve settles that before cost does.</span></p><p><span>Then I will tell you the best harness to save costs is GitHub Copilot, and you should hear that knowing I work on the Copilot side. My reasoning stands on its own though. If you have a Copilot licence you already have the spend, and Copilot is an agent, not just an editor feature. There is a Copilot SDK and there is the Microsoft Agent Framework, and you route from there. Send the task to Copilot, or to Claude Code, or to a local model, based on the work. It is the same routing decision as picking a model, one level up.</span></p><h2><span>Take a step back</span></h2><p><span>There is a whole industry forming around this. I watched it happen with cloud, where engineers who wrote good scripts became cloud engineers, and then cost optimisation became its own discipline. I used to sit with CTOs of banks who told me their biggest worry was how much they were going to spend. Then it changed, and the question became how much they were going to save. The same thing is happening now with tokens, and the Linux Foundation&#8217;s Tokenomics Foundation is going to matter here, because standards and dashboards and protection mechanisms are what turn this from a habit into a practice.</span></p><p><span>So do not go berserk. Create the foundation. Try Luna, switch reasoning off, and see how far it gets you. If it does not match your needs, add a router and a design pattern. Start small and build up. Rather than spend more, spend less.</span></p><p><span>And one last thing, because I have another whole talk about this. Do not end each day exhausted, not from the work itself, but from managing of the work. Let the adrenaline flow, do the vibe coding, do it in the correct way. Know what you are doing. Be kind to yourself.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WEkT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157ca3c0-4404-44ba-a53f-1c886d59cb19_2400x3000.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WEkT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157ca3c0-4404-44ba-a53f-1c886d59cb19_2400x3000.png 424w, https://substackcdn.com/image/fetch/$s_!WEkT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157ca3c0-4404-44ba-a53f-1c886d59cb19_2400x3000.png 848w, https://substackcdn.com/image/fetch/$s_!WEkT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157ca3c0-4404-44ba-a53f-1c886d59cb19_2400x3000.png 1272w, https://substackcdn.com/image/fetch/$s_!WEkT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157ca3c0-4404-44ba-a53f-1c886d59cb19_2400x3000.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WEkT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157ca3c0-4404-44ba-a53f-1c886d59cb19_2400x3000.png" width="728" height="910" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/157ca3c0-4404-44ba-a53f-1c886d59cb19_2400x3000.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:1820,&quot;width&quot;:1456,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:428971,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.net/i/209102293?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157ca3c0-4404-44ba-a53f-1c886d59cb19_2400x3000.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WEkT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157ca3c0-4404-44ba-a53f-1c886d59cb19_2400x3000.png 424w, https://substackcdn.com/image/fetch/$s_!WEkT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157ca3c0-4404-44ba-a53f-1c886d59cb19_2400x3000.png 848w, https://substackcdn.com/image/fetch/$s_!WEkT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157ca3c0-4404-44ba-a53f-1c886d59cb19_2400x3000.png 1272w, https://substackcdn.com/image/fetch/$s_!WEkT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157ca3c0-4404-44ba-a53f-1c886d59cb19_2400x3000.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><span>Now go and look at your own session history.</span></p><div><hr></div><p><strong>Session notes:</strong></p><blockquote><p>Rory&#8217;s demos run on LangChain4j&#8217;s agentic modules, which matters for teams on the JVM because most token-efficiency tooling ships Python first. His pattern code is at <a href="http://github.com/roryp/token-design-patterns">github.com/roryp/token-design-patterns</a>, the agentic pattern showcase behind the banking demo at aka.ms/agentpatterns, the live cost demo at <strong>aka.ms/costs</strong>, and his LangChain4j course at <a href="http://aka.ms/LangChain4j-for-Beginners">aka.ms/LangChain4j-for-Beginners</a>.</p></blockquote><div><hr></div><p><em><span>This deep dive is adapted from </span><strong><span>Rory&#8217;s Deep Engineering Live session</span></strong><span> on The Future of Software Development. Here are the slides from the talk, and the full recording.</span></em></p><div class="file-embed-wrapper" data-component-name="FileToDOM"><div class="file-embed-container-reader"><div class="file-embed-container-top"><image class="file-embed-thumbnail-default" src="https://substackcdn.com/image/fetch/$s_!0Cy0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack.com%2Fimg%2Fattachment_icon.svg"></image><div class="file-embed-details"><div class="file-embed-details-h1">Token Efficiency Foundry Instantmodels</div><div class="file-embed-details-h2">2.37MB &#8729; PDF file</div></div><a class="file-embed-button wide" href="https://deepengineering.net/api/v1/file/e3851c6c-ccc1-4eda-be9b-0c29c3db9a38.pdf"><span class="file-embed-button-text">Download</span></a></div><a class="file-embed-button narrow" href="https://deepengineering.net/api/v1/file/e3851c6c-ccc1-4eda-be9b-0c29c3db9a38.pdf"><span class="file-embed-button-text">Download</span></a></div></div><p> </p><div id="youtube2-tVRcYTX_HCg" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;tVRcYTX_HCg&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/tVRcYTX_HCg?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p><strong><span>Connect with Rory Preddy on LinkedIn:</span></strong><span> </span><a href="https://za.linkedin.com/in/rorypreddy"><span>https://za.linkedin.com/in/rorypreddy</span></a><span><br></span></p>]]></content:encoded></item><item><title><![CDATA[223 pull requests in 11 days for the project I never had time to build]]></title><description><![CDATA[Drive a fleet of agents by day, a few deep plans by night, and merge the wins over coffee.]]></description><link>https://deepengineering.net/p/223-pull-requests-in-11-days-julien-dubois</link><guid isPermaLink="false">https://deepengineering.net/p/223-pull-requests-in-11-days-julien-dubois</guid><pubDate>Thu, 16 Jul 2026 14:30:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5c775d8a-b3cf-4e8b-8400-b6e1721933d4_2400x1600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p><strong>By <a href="https://www.julien-dubois.com">Julien Dubois</a></strong> <em>Principal Manager, Developer Relations at <strong>Microsoft</strong> and <strong>GitHub</strong>. Creator of <strong><a href="https://www.jhipster.tech/">JHipster</a></strong>.</em></p></blockquote><p>I always wanted a real developer UI for Spring Boot. Every Spring app is a black box in development. Actuator gives you raw JSON, not a console, so what I wanted was health, metrics, security and tracing in one embedded UI.</p><p>I&#8217;d shipped a slice of this in JHipster years ago, but only for generated apps. A real console for any Spring Boot app sat on my wishlist for years. The catch is that it&#8217;s massive. Around 40 panels, each one a backend plus a frontend plus its own tests, deeply integrated across a dozen JVM subsystems. By hand, one experienced developer would need somewhere between six and a half and eight and a half months. Too big to justify, until I stopped writing the code myself.</p><p>Then I did it in 11 days. Not by typing faster. I didn&#8217;t even open my IDE. I managed a fleet of AI agents while I architected, reviewed and steered. The agents did the scaffolding, the panels and the tests. I did the judgement.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-Bqk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F372973aa-1732-4070-b7e4-4844306e284a_2400x1600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-Bqk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F372973aa-1732-4070-b7e4-4844306e284a_2400x1600.png 424w, https://substackcdn.com/image/fetch/$s_!-Bqk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F372973aa-1732-4070-b7e4-4844306e284a_2400x1600.png 848w, https://substackcdn.com/image/fetch/$s_!-Bqk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F372973aa-1732-4070-b7e4-4844306e284a_2400x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!-Bqk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F372973aa-1732-4070-b7e4-4844306e284a_2400x1600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-Bqk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F372973aa-1732-4070-b7e4-4844306e284a_2400x1600.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/372973aa-1732-4070-b7e4-4844306e284a_2400x1600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:182698,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://deepengineering.net/i/207293305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F372973aa-1732-4070-b7e4-4844306e284a_2400x1600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-Bqk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F372973aa-1732-4070-b7e4-4844306e284a_2400x1600.png 424w, https://substackcdn.com/image/fetch/$s_!-Bqk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F372973aa-1732-4070-b7e4-4844306e284a_2400x1600.png 848w, https://substackcdn.com/image/fetch/$s_!-Bqk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F372973aa-1732-4070-b7e4-4844306e284a_2400x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!-Bqk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F372973aa-1732-4070-b7e4-4844306e284a_2400x1600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>What I built, BootUI</h2><p><a href="https://github.com/jdubois/boot-ui">BootUI</a> is a production-grade Spring Boot 4 starter that adds an embedded, local-only developer console to your app. You add one starter to your <code>pom.xml</code>, run locally, and open the console.</p><p>It is a five-module Maven build on Spring Boot 4 and Java 17, integrated across Actuator, Spring Security, Flyway and Liquibase, Hibernate, Micrometer with OTLP, GraalVM, OSV scanning and ArchUnit, published to Maven Central with full CI. The frontend is an embedded Vue 3 single-page app, packaged inside the starter. Each panel is endpoints plus a view plus tests.</p><p>There are around 40 feature panels. Health and metrics, a security advisor, a vulnerabilities view, tracing, and more. Those panels look a lot alike, which made them the biggest source of structural repetition in the codebase. That detail matters, and I will come back to why it made the whole thing work.</p><h2>Eleven days, and how I know the numbers are real</h2><p>The figures here are derived from git history, PR metadata and code metrics, not from time-tracking logs. Through the tagged 1.0.0 release there were around 264 commits on main and about 223 squash-merged pull requests, landing at roughly 20 a day across 11 days. That came to about 83k total tracked source lines, close to 50k of them Java across around 461 files and 81 test classes, plus 52 Vue components, 40 panels, and 35 end-to-end specs. In all, about 116 test suites.</p><p>A cadence of 20 PRs a day with an AI agent co-authoring commits is impossible to reach by hand. The agent did the typing. I dispatched many asynchronous tasks in parallel. The evidence lines up with that. The commit clock runs from around five in the morning to midnight most days, which is consistent with parallel async tasks rather than continuous typing. &#8220;Copilot&#8221; shows up as a named commit and PR author on about 44 commits, and the repo ships a <code>copilot-instructions.md</code> with per-panel conventions, so the workflow was explicitly agent-oriented. Even on release day I was landing a docs site, a scanner dashboard, token charts and a Hikari fix, which is itself several days of solo work.</p><p>Where my own time went was writing prompts, reviewing and merging those 223 PRs, and resolving CI failures around Spring Boot 4, Flyway 11 and OTLP. Review and orchestrate, not write every line.</p><p>The honest by-hand comparison is the part people argue about, so I ran it carefully. One experienced Spring Boot and Vue developer, with no AI codegen, would need 6.5 to 8.5 months for the same polished 1.0.0, roughly 1,100 to 1,450 hours of hands-on effort, and the 40 panels are the dominant cost. A COCOMO organic estimate on 50k lines yields more than 100 person-months, which is too high because much of the code is repetitive scaffolding, so a domain-expert solo figure of about 7.5 months is the defensible middle ground. My actual human effort on BootUI was 80 to 110 hours, about two intense solo weeks. That is a calendar speed-up of roughly 17 to 23 times, and a human-hours speed-up of roughly 12 to 17 times.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XPX-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ae88e98-2dcc-436b-83b8-de20a536cddf_2400x1600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XPX-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ae88e98-2dcc-436b-83b8-de20a536cddf_2400x1600.png 424w, https://substackcdn.com/image/fetch/$s_!XPX-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ae88e98-2dcc-436b-83b8-de20a536cddf_2400x1600.png 848w, https://substackcdn.com/image/fetch/$s_!XPX-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ae88e98-2dcc-436b-83b8-de20a536cddf_2400x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!XPX-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ae88e98-2dcc-436b-83b8-de20a536cddf_2400x1600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XPX-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ae88e98-2dcc-436b-83b8-de20a536cddf_2400x1600.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2ae88e98-2dcc-436b-83b8-de20a536cddf_2400x1600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:292712,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.net/i/207293305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ae88e98-2dcc-436b-83b8-de20a536cddf_2400x1600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XPX-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ae88e98-2dcc-436b-83b8-de20a536cddf_2400x1600.png 424w, https://substackcdn.com/image/fetch/$s_!XPX-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ae88e98-2dcc-436b-83b8-de20a536cddf_2400x1600.png 848w, https://substackcdn.com/image/fetch/$s_!XPX-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ae88e98-2dcc-436b-83b8-de20a536cddf_2400x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!XPX-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ae88e98-2dcc-436b-83b8-de20a536cddf_2400x1600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>I was the manager, not the developer</h2><p>I didn&#8217;t open my IDE. I wasn&#8217;t the developer. I was the manager. Many agents ran in parallel on the panels, the integrations, the frontend, and the CI and docs, and my job was to brief, review and merge.</p><p>Your throughput is not your keyboard. It is your briefs. What blocks a single developer is typing, and what blocks a single agent is you waiting for it to finish. Running many at once removes both, because they come back one after another and there is always something to review. You don&#8217;t type faster this way. You ship what used to take months.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption"><strong>Subscribe for free</strong><span> to get expert-led deep dives, system design breakdowns, and full book chapters like this one every week.</span></p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>A day in the loop</h2><p>The rhythm that produced 20 merged PRs a day is a simple daily loop, and the day is the engine. I am hands-on all day. I spec, launch, review, merge and re-task, live, and most of the day&#8217;s PRs land right there.</p><p>The evening is a hand-off. Before I step away I queue a few deep, long-running plans. The night is a bonus, not the engine. A handful of deep autonomous runs finish by morning, which is the minority of the work. Repeat that for about 11 days and you reach a tagged 1.0.0. Six ingredients make the loop work.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KsfK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F599b15ce-7a3c-42bc-90e7-3b080bfafdf1_2400x1600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KsfK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F599b15ce-7a3c-42bc-90e7-3b080bfafdf1_2400x1600.png 424w, https://substackcdn.com/image/fetch/$s_!KsfK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F599b15ce-7a3c-42bc-90e7-3b080bfafdf1_2400x1600.png 848w, https://substackcdn.com/image/fetch/$s_!KsfK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F599b15ce-7a3c-42bc-90e7-3b080bfafdf1_2400x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!KsfK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F599b15ce-7a3c-42bc-90e7-3b080bfafdf1_2400x1600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KsfK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F599b15ce-7a3c-42bc-90e7-3b080bfafdf1_2400x1600.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/599b15ce-7a3c-42bc-90e7-3b080bfafdf1_2400x1600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:224984,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.net/i/207293305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F599b15ce-7a3c-42bc-90e7-3b080bfafdf1_2400x1600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KsfK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F599b15ce-7a3c-42bc-90e7-3b080bfafdf1_2400x1600.png 424w, https://substackcdn.com/image/fetch/$s_!KsfK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F599b15ce-7a3c-42bc-90e7-3b080bfafdf1_2400x1600.png 848w, https://substackcdn.com/image/fetch/$s_!KsfK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F599b15ce-7a3c-42bc-90e7-3b080bfafdf1_2400x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!KsfK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F599b15ce-7a3c-42bc-90e7-3b080bfafdf1_2400x1600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Ingredient one, write the specifications</h2><p>The first thing I write is never code. It is the house rules. An <code>AGENTS.md</code>, which GitHub Copilot reads as <code>copilot-instructions.md</code>, and every agent reads it first. It sets the conventions once, the stack, the build, the tests and the style, with per-panel conventions so 40 panels come out consistent. Mine runs around 700 lines. That is about the right size, because a file that is too big pollutes the context, and it also costs tokens and money on every run.</p><p>The high-leverage move is writing the known failures into that file once. Most models were trained on Spring Boot 3, so they struggle with the reworked Jackson API in Spring Boot 4, and 10 agents trained on the same data hit the same wall at the same time. Rather than fix that by hand in each one, I write the fix down once. The same goes for operational traps, like giving every agent its own local Maven repository so parallel installs do not corrupt a shared one.</p><p>On top of the house rules I write one spec per task. What to build, where, what &#8220;done&#8221; looks like, and the acceptance test the agent must make pass. Small, self-contained, no hidden dependencies. The spec is the product now. The better the brief, the less you babysit.</p><h2>Ingredient two, build the test harness</h2><p>No agent runs without a test harness. One command compiles and runs the unit and end-to-end tests, and comes back green or red, so an agent can verify its own work before it opens a PR. No tests means you cannot trust the output.</p><p>CI is the trust layer. BootUI has about 116 test suites, 81 in Java and 35 in Playwright, and CodeQL plus the end-to-end tests gate every PR to main. The loop does the work. An agent writes code, pushes it, and GitHub Actions sends back the results. If they are red the agent reads the failure, fixes it, commits and pushes again, and the checks run once more. Dependabot keeps the dependencies current alongside all of this. A compiled, typed language like Java helps too, because a hallucinated call often fails to compile before a test ever runs, and the tests catch what the compiler misses. You cannot read every line of 223 PRs. A green build you trust is what makes the volume reviewable.</p><h2>Ingredient three, split the work and run agents in parallel</h2><p>Parallelism is the whole point, so the work has to be parallel-ready. The 40 near-identical panels are perfect to fan out, because AI is strong at cloning something and adapting it to something similar. I give one task to one agent, small scope and a clear goal, each on its own branch or worktree so they do not collide. Splitting the work cleanly is an architecture problem before it is a prompting one, and getting it wrong is where the merge conflicts come from.</p><p>A fleet is not one chat window. I drove BootUI mostly through the <a href="https://github.com/features/ai/github-app">GitHub Copilot coding agent app</a>, because it lets many agents run live on one machine, and I comfortably keep 10 going at once. More than 10 gets complicated for a human to hold in their head, and the laptop starts to slow down. The mobile app runs agents in Docker containers, so I can keep them moving when I am away from my desk. The CLI and the IDE plugin suit smaller fan-out, and all three share the same back end, the Copilot SDK, which is open source, so you can build your own interface on top of it if you want. The rule stays simple. Don&#8217;t babysit one agent. Run 10.</p><h2>Ingredient four, let a few deep plans run overnight</h2><p>The day is the engine and the night is a bonus shift. Before I log off I hand a few deep, long-running plans to autonomous agents, the big jobs I don&#8217;t want to sit and watch. On BootUI those were things like wiring and testing the security filter chains across 37 rules, pushing coverage further across the suites, and reshaping the Actuator data layer in a larger refactor. Each is a long run that lands a few PRs by morning.</p><p>For the genuinely tricky work I have the agent criticize its own output with two or three other models. One writes the code, the others analyze and vote on the fix. That is also how I catch hallucinations, because the model that invented something usually stands alone against the others, and it shows up plainly in the logs. A good overnight prompt means everything is done when I come back. A weak one means I have nothing, which is why prompt-writing is worth practicing. The night is a bonus on top of the day, not a replacement for the driving.</p><h2>Ingredient five, merge the results over coffee</h2><p>The morning starts over coffee. I triage the few overnight PRs, merge the green ones fast, drop or re-task whatever didn&#8217;t land, cherry-pick the good parts of the rest, and then start driving the day&#8217;s fleet. When I review a PR I look at three things that stay in sync because they are generated together, the code, its tests, and its documentation. Green tests tell me the code and the tests agree, and I squash and merge.</p><p>Review is the real bottleneck. It is not the typing anymore. It is the merging. I don&#8217;t merge blind and I don&#8217;t merge like crazy, because the day you wave through a large diff without reading it is the day something wrong lands in main. So make review a fast, trusted ritual you run all day, not a line-by-line slog at the end.</p><h2>Ingredient six, pick the right model for the task</h2><p>Most of the work went to a workhorse, GPT-5.5 on extra-high reasoning, with around 90 percent of its tokens served from cache. For the tricky parts I brought in Claude Opus 4.8 and Gemini 3.1 Pro, three strong models cross-checking each other to find the best fix. For simple, mechanical tasks a smaller model or Auto mode is fast and cheap, and Auto tends to pick better than I would while carrying its own token rebate.</p><p>The economics are driven by the cache more than by the model price. The whole build ran on about 2.7 billion tokens for roughly 2,000 dollars, with around 95 percent served from cache, so most of the run costs a tenth of the headline price. That changes the obvious advice. Swapping to a cheaper model for one small task often bursts the cache and costs more than staying on the model whose context is already warm. If I have a small task, I spawn a separate small agent for it rather than switching the big agent&#8217;s model. And I avoid editing <code>AGENTS.md</code> mid-run, because it lives at the top of the cache and one change invalidates it, so I update it at the end of a run instead. Knowing how the cache works matters more than picking the cheapest model.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UGCm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03d32df8-a5dc-4241-8fe7-69b4c42e823a_2400x1600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UGCm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03d32df8-a5dc-4241-8fe7-69b4c42e823a_2400x1600.png 424w, https://substackcdn.com/image/fetch/$s_!UGCm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03d32df8-a5dc-4241-8fe7-69b4c42e823a_2400x1600.png 848w, https://substackcdn.com/image/fetch/$s_!UGCm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03d32df8-a5dc-4241-8fe7-69b4c42e823a_2400x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!UGCm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03d32df8-a5dc-4241-8fe7-69b4c42e823a_2400x1600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UGCm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03d32df8-a5dc-4241-8fe7-69b4c42e823a_2400x1600.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/03d32df8-a5dc-4241-8fe7-69b4c42e823a_2400x1600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:192051,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.net/i/207293305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03d32df8-a5dc-4241-8fe7-69b4c42e823a_2400x1600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UGCm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03d32df8-a5dc-4241-8fe7-69b4c42e823a_2400x1600.png 424w, https://substackcdn.com/image/fetch/$s_!UGCm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03d32df8-a5dc-4241-8fe7-69b4c42e823a_2400x1600.png 848w, https://substackcdn.com/image/fetch/$s_!UGCm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03d32df8-a5dc-4241-8fe7-69b4c42e823a_2400x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!UGCm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03d32df8-a5dc-4241-8fe7-69b4c42e823a_2400x1600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Why the multiplier was so large</h2><p>I want to be honest about why BootUI hit a multiplier this large, because not every project will. Three things made this codebase unusually well-suited to agents. First, massive repetition, since around 40 structurally similar panels are cheap for an agent to clone and are the most expensive part by hand. Second, a broad-but-shallow shape, many Spring subsystems each shallow on its own, where the human cost is mostly looking things up, which is exactly what the model already absorbed in training. Third, strong guardrails, because multi-module CI, CodeQL, end-to-end tests and explicit instructions let me accept high throughput safely.</p><p>The net effect was a 6.5 to 8.5 month solo effort compressed into 11 days and about two weeks of human attention. The biggest leverage is on large-surface, pattern-heavy, well-tested code, not on hard algorithms. A project that is mostly novel logic with thin tests will not see these numbers, and I would not claim otherwise.</p><h2>Where this goes wrong</h2><p>The failure modes are consistent, and most of them trace back to the operator. Scope creep is the first. Tell an agent to build the whole thing and it wanders, so keep one tight goal per task. Missing tests are the second. Without a harness you cannot trust the output and you cannot merge at volume, so the harness comes first and a green build gates every merge.</p><p>Giant PRs are the third. A 2,000-line PR is impossible to review well, and review fatigue tempts you to wave it through, so keep the chunks small and reviewable and pace yourself. The wrong model is the fourth. A weak model fails the hard tasks, a strong one is slow and costly on the easy ones, so match the model to the job. When people tell me AI-generated code is not good, the cause is usually one of these. Thin specs, weak tests, oversized PRs, or the wrong model. If the agent generates bad code, that is usually your fault, not the model&#8217;s. Most failed runs are a briefing problem.</p><h2>One page to screenshot</h2><p>If you take one thing away, take this.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tJUl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb20db2fb-0016-4b64-99b8-2e8de8a1fa3b_2400x1600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tJUl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb20db2fb-0016-4b64-99b8-2e8de8a1fa3b_2400x1600.png 424w, https://substackcdn.com/image/fetch/$s_!tJUl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb20db2fb-0016-4b64-99b8-2e8de8a1fa3b_2400x1600.png 848w, https://substackcdn.com/image/fetch/$s_!tJUl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb20db2fb-0016-4b64-99b8-2e8de8a1fa3b_2400x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!tJUl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb20db2fb-0016-4b64-99b8-2e8de8a1fa3b_2400x1600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tJUl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb20db2fb-0016-4b64-99b8-2e8de8a1fa3b_2400x1600.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b20db2fb-0016-4b64-99b8-2e8de8a1fa3b_2400x1600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:327967,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.net/i/207293305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb20db2fb-0016-4b64-99b8-2e8de8a1fa3b_2400x1600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tJUl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb20db2fb-0016-4b64-99b8-2e8de8a1fa3b_2400x1600.png 424w, https://substackcdn.com/image/fetch/$s_!tJUl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb20db2fb-0016-4b64-99b8-2e8de8a1fa3b_2400x1600.png 848w, https://substackcdn.com/image/fetch/$s_!tJUl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb20db2fb-0016-4b64-99b8-2e8de8a1fa3b_2400x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!tJUl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb20db2fb-0016-4b64-99b8-2e8de8a1fa3b_2400x1600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>A new Spring Boot console fell out of it</h2><p>The recipe was the point, but it left behind a real, open-source product. <a href="https://github.com/jdubois/boot-ui">BootUI</a> gives any Spring Boot app an embedded console with live health and metrics, a security advisor that walks your filter chains, an OSV scan of your dependencies, a Flyway and Liquibase data view with a Hibernate advisor, tracing through Micrometer and OTLP, and an architecture view backed by ArchUnit and GraalVM reachability. Add one starter to your <code>pom.xml</code>, run locally, and open the console. The code and the <a href="https://julien-dubois.com/boot-ui">full docs</a> are open.</p><p>Write the specs, give them tests, run them wide, and ship what used to take months. Now go build.</p><div><hr></div><p><em>This deep dive is adapted from <strong>Julien&#8217;s</strong> Deep Engineering workshop, From Coder to Manager of Agents. Here the <a href="https://www.julien-dubois.com/conferences/building-with-ai-agents/index-en.html">slides from the talk</a>.</em></p>]]></content:encoded></item><item><title><![CDATA[Rust Patterns That Leverage the Type System]]></title><description><![CDATA[A deep-dive into four Rust patterns, NewType, parse don't validate, TypeState, and sealed traits, excerpted with permission from Evan Williams' Design Patterns and Best Practices in Rust.]]></description><link>https://deepengineering.net/p/rust-patterns-that-leverage-the-type-system</link><guid isPermaLink="false">https://deepengineering.net/p/rust-patterns-that-leverage-the-type-system</guid><dc:creator><![CDATA[Packt]]></dc:creator><pubDate>Thu, 09 Jul 2026 12:32:44 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9830cb2b-3fe2-48b8-85db-5e66c91cfd67_2400x1600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The patterns in this deep-dive represent well-established techniques from type theory and functional programming, adapted to work with Rust's unique ownership and borrowing system. While these patterns have origins in languages such as Haskell and <strong>MetaLanguage</strong> (<strong>ML</strong>), as well as research on type systems, Rust brings its own contributions: zero-cost abstractions, compile-time enforcement through ownership, and integration with systems programming.</p><p>What makes these patterns particularly valuable in Rust isn&#8217;t their novelty. Most have decades of history in other languages. It is how Rust&#8217;s design makes them practical for systems programming. The combination of strong static typing, zero-cost abstractions, and memory safety without garbage collection creates opportunities to apply these patterns in contexts where they were previously impractical.</p><p>We&#8217;ll apply these patterns to Samsa, a publish/subscribe microservice in the style of a miniature Kafka that the book builds across its later chapters, patterns that have proven their worth across multiple programming language communities. As we explore each pattern, we&#8217;ll examine both its origins and how Rust&#8217;s unique features enhance or constrain its application.</p><blockquote><p><em>This deep-dive is excerpted from <a href="https://www.packtpub.com/en-us/product/design-patterns-and-best-practices-in-rust-9781836209478">Design Patterns and Best Practices in Rust</a> by <a href="https://www.linkedin.com/in/evan-williams-1512092">Evan Williams</a>, shared with permission from Packt, with all rights remaining with the publisher and no reproduction or redistribution without written consent. You can get the full book <a href="https://www.packtpub.com/en-us/product/design-patterns-and-best-practices-in-rust-9781836209478">here</a>.</em></p></blockquote><p>In this chapter the book covers the following main topics:</p><ul><li><p>The NewType pattern</p></li><li><p>Parse, don&#8217;t validate</p></li><li><p>The TypeState pattern</p></li><li><p>Sealed traits</p></li></ul><p>Each pattern builds on the previous ones: the NewType pattern creates distinct types; parse, don&#8217;t validate ensures those types hold only valid data; the TypeState pattern tracks valid state transitions; and sealed traits control which types can participate in our APIs.</p><div class="callout-block" data-callout="true"><p><strong>Technical requirements</strong></p><p>The source code for the exercises can be found on GitHub at <a href="https://github.com/PacktPublishing/Design-Patterns-and-Best-Practices-in-Rust"><span>https://github.com/PacktPublishing/Design-Patterns-and-Best-Practices-in-Rust</span></a>. The repository is organized by chapter. The relevant exercises for this chapter are at <a href="https://github.com/PacktPublishing/Design-Patterns-and-Best-Practices-in-Rust/tree/main/ch10"><span>https://github.com/PacktPublishing/Design-Patterns-and-Best-Practices-in-Rust/tree/main/ch10</span></a>.</p></div><div><hr></div><h2>The NewType pattern</h2><p>The NewType pattern has a long history in statically-typed functional programming, particularly in Haskell, where the <code>newtype</code> keyword has been a language feature since the 1990s. The pattern also appears in ML, OCaml, and other languages with strong type systems. The core idea of wrapping existing types in distinct types for semantic clarity and type safety predates Rust. It is a response to the issue that values that are conceptually different, such as temperature and weight, have the same type representation in the code, for example, a float. From the compiler&#8217;s perspective, they are the same, and that leads to bugs when these values are inadvertently mixed up.</p><p>What Rust brings to this well-established pattern is zero-cost abstraction: the wrapped type compiles to exactly the same representation as the underlying type, with no runtime overhead. In languages with garbage collection or boxing, creating wrapper types often incurs performance costs. Rust&#8217;s design ensures that type safety is free at runtime while remaining enforceable at compile time.</p><h3>Identifying the problem</h3><p>In our Samsa system, we currently use primitive types such as <code>u64</code> for topic IDs, consumer IDs, and message offsets. This approach, which we will call <strong>primitive obsession</strong>, makes it easy to accidentally pass the wrong type of ID to a function. If you&#8217;ve worked with similar systems, you&#8217;ve probably experienced the confusion this creates. The NewType pattern solves this problem with compile-time type checking.</p><p>Let&#8217;s examine our current API and identify where primitive obsession creates issues:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;fa4f79f2-9f41-4b8f-9707-9b71b27ba5f6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">// Current API - prone to mix-ups
impl Broker {
     pub fn subscribe(&amp;self, topic_id: u64, consumer_id: u64) -&gt; Result&lt;()&gt; {
         // Easy to accidentally swap these parameters!
         if !self.topics.contains_key(&amp;topic_id) {
             return Err("Topic not found".to_string());
         }
         Ok(())
     }
 }

impl Consumer {
     pub fn new(broker: Arc&lt;Broker&gt;, topic_id: u64, offset: u64) -&gt; Self {
         // Again, easy to mix up topic_id and offset
         Self { broker, topic_id, offset }
     }
 }</code></pre></div><p>We&#8217;re using raw <code>u64</code> values for different semantic concepts, making it easy to pass the wrong type of ID and causing runtime bugs that are difficult to debug. What is very unfortunate is that the compiler cannot help us identify logical errors when we pass a topic ID instead of a consumer ID.</p><h3>Implementing type-safe wrappers</h3><p>Let&#8217;s create wrapper types that eliminate these mix-ups. Our approach has three parts:</p><ol><li><p>Define semantic wrapper types for our different ID concepts (shown in the first code block)</p></li><li><p>Implement basic methods for usability, such as constructors and accessors (shown in the second code block)</p></li><li><p>Add <code>Display</code> implementations for user-friendly output (shown in the third code block)</p></li></ol><p>Let&#8217;s define semantic wrapper types: <code>TopicId</code> for topic identification, <code>ConsumerId</code> for tracking individual consumers, and <code>MessageId</code> for tracking individual messages. We&#8217;ll use plain <code>u64</code> for message offsets for simplicity (in the current design, message offsets don&#8217;t benefit much from the additional type safety):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;753a42de-c85e-4e7f-8fe4-df765a050ef2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">use std::fmt;

/// Topic identifier for tracking individual topics
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TopicId(String);

/// Consumer identifier for tracking individual consumers
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ConsumerId(String);

/// Message identifier for tracking individual messages
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MessageId(u64);</code></pre></div><p>These NewTypes provide type safety through distinct wrapper types. <code>TopicId</code> and <code>ConsumerId</code> wrap <code>String</code> to allow meaningful identifiers such as <code>"user.events"</code> or <code>"consumer-1"</code>, while <code>MessageId</code> wraps <code>u64</code> for numeric message tracking. Each type has meaningful derives, such as <code>Hash</code> for use in collections. Note that <code>MessageId</code> includes <code>Copy</code> since <code>u64</code> is copyable, while the <code>String</code>-based types do not.</p><p>Now, let&#8217;s add methods for creating and accessing these types:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;b8017b57-f2e3-4a69-b06c-f1e9a178223c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">impl TopicId {
    pub fn new(topic: impl AsRef&lt;str&gt;) -&gt; Result&lt;Self&gt; {
        let topic = topic.as_ref();

        if topic.is_empty() {
            return Err(SamsaError::topic("Topic name cannot be empty"));
        }

        if topic.len() &gt; 128 {
            return Err(SamsaError::topic(
                format!("Topic name too long: {} chars (max 128)",
                         topic.len())
            ));
        }

        Ok(TopicId(topic.to_string()))
    }

    pub fn as_str(&amp;self) -&gt; &amp;str {
        &amp;self.0
    }
}

impl ConsumerId {
    pub fn new(id: impl AsRef&lt;str&gt;) -&gt; Result&lt;Self&gt; {
        let id = id.as_ref();

        if id.is_empty() {
            return Err(SamsaError::consumer("Consumer ID cannot be
                                             empty"));
        }

        Ok(ConsumerId(id.to_string()))
    }

    pub fn as_str(&amp;self) -&gt; &amp;str {
        &amp;self.0
    }
}

impl MessageId {
    pub fn new(id: u64) -&gt; Self {
        MessageId(id)
    }

    pub fn value(&amp;self) -&gt; u64 {
        self.0
    }
}</code></pre></div><p>Each type provides a new constructor and an accessor method. <code>TopicId</code> and <code>ConsumerId</code> validate their inputs at construction time, returning <code>Result</code> to signal potential validation failures. This is the NewType pattern combined with early validation: once you have <code>TopicId</code> or <code>ConsumerId</code>, you know it contains valid data. <code>MessageId</code> uses simple <code>u64</code> wrapping since numeric IDs don&#8217;t require validation.</p><p>Finally, let&#8217;s add <code>Display</code> implementations for user-friendly output:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;e0e3a0b2-2195-4c73-ae55-cde24df118ed&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">impl fmt::Display for TopicId {
    fn fmt(&amp;self, f: &amp;mut fmt::Formatter&lt;'_&gt;) -&gt; fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl fmt::Display for ConsumerId {
    fn fmt(&amp;self, f: &amp;mut fmt::Formatter&lt;'_&gt;) -&gt; fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl fmt::Display for MessageId {
    fn fmt(&amp;self, f: &amp;mut fmt::Formatter&lt;'_&gt;) -&gt; fmt::Result {
        write!(f, "{}", self.0)
    }
}</code></pre></div><p>The <code>Display</code> implementations output the inner value directly. For example, a <code>TopicId</code> containing <code>"user.events"</code> prints as <code>user.events</code>. If we were writing production code, we could also implement a custom <code>Debug</code> trait and print the IDs in a way that is more helpful for debugging, such as <code>"TopicID(user.events)"</code>.</p><p>We&#8217;ve created distinct types for different domain concepts. Each type wraps its inner value and prevents accidental mixing. It&#8217;s now impossible to accidentally pass a <code>TopicId</code> where a <code>ConsumerId</code> is expected. The compiler catches this at compile time, eliminating entire classes of bugs. Each wrapper type is distinct at compile time.</p><p>For <code>MessageId</code> wrapping <code>u64</code>, the type compiles to the same representation as <code>u64</code> at runtime, creating a true zero-cost abstraction. <code>TopicId</code> and <code>ConsumerId</code> use <code>String</code> for semantic identifiers but still provide compile-time type safety.</p><h3>Enhanced Samsa API</h3><p>Let&#8217;s see how NewTypes prevent parameter mix-ups. Consider a subscription function that takes both a consumer and a topic:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;78867920-b4f2-4d31-a312-702083fcaf2a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">fn subscribe(consumer: ConsumerId, topic: TopicId) -&gt; Result&lt;(), SamsaError&gt; {
      println!("Subscribing {} to {}", consumer, topic);
      Ok(())
  }

  // Usage - this compiles:
  let topic = TopicId::new("orders.created")?;
  let consumer = ConsumerId::new("analytics-service")?;
  subscribe(consumer, topic)?;

  // But this won't compile - arguments swapped:
  // subscribe(topic, consumer);
  //           ^^^^^ expected `ConsumerId`, found `TopicId`</code></pre></div><p>Notice how <code>subscribe</code> takes <code>TopicId</code> and <code>ConsumerId</code> as separate types. The compiler would reject any attempt to swap them. Without NewTypes, both <code>topic</code> and <code>consumer</code> would be <code>String</code>, and swapping them would compile silently &#8211; a bug you&#8217;d only discover at runtime (maybe while in production). With NewTypes, the compiler catches the mistake immediately. This is especially valuable in APIs with multiple string-like parameters where mix-ups are easy to make and hard to debug.</p><p>Now that we have our type-safe wrappers, let&#8217;s apply them to Samsa&#8217;s API to see how they prevent the parameter mix-ups we identified earlier. The following code snippet enhances the consumer API with type safety:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;8694ec10-d5cf-4595-ad87-4a2dba6c7d13&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">pub struct Consumer {
     broker: Arc&lt;Broker&gt;,
     id: ConsumerId,
     topic: TopicId,
     offset: u64,
 }

impl Consumer {
     pub fn new(broker: Arc&lt;Broker&gt;, id: ConsumerId, topic: TopicId,
                offset: u64) -&gt; Self {
         Self { broker, id, topic, offset }
     }

    pub fn poll(&amp;mut self) -&gt; Result&lt;Option&lt;Message&gt;, String&gt; {
         let events = self.broker.fetch(self.topic, self.offset, 1)?;
    
        if let Some(message) = events.into_iter().next() {
             self.offset += 1;
             Ok(Some(message))
         } else {
             Ok(None)
         }
     }
 }</code></pre></div><p>The <code>Consumer</code> struct stores typed identifiers for its broker connection, ID, topic, and current offset. The <code>poll()</code> method demonstrates how advancement works with the typed consumer. Each field uses the appropriate NewType, making the struct&#8217;s purpose clear from its type signature alone.</p><p>Our entire API now uses type-safe identifiers. Function signatures are self-documenting, and the compiler prevents mixing up different types of IDs. This eliminates bugs where IDs are confused, provides better error messages, and makes the code more maintainable. With this design, Samsa&#8217;s consumer API becomes self-documenting and resistant to ID mix-ups.</p><p>The NewType pattern provides compile-time guarantees about type correctness while maintaining the runtime performance of primitive types. This combination of safety and efficiency is what makes the pattern particularly valuable in Rust.</p><p>However, while NewTypes give us distinct types, they don&#8217;t enforce <em>validity</em>. A <code>MessageId</code> could wrap any <code>u64</code>, which is OK if every <code>u64</code> is a valid message ID. But if some are not, including those invalid values in our data type can turn into an issue. In the next section, we&#8217;ll see how to combine type distinctions with validity guarantees using the &#8220;parse, don&#8217;t validate&#8221; principle.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption"><strong>Subscribe for free</strong> to get expert-led deep dives, system design breakdowns, and full book chapters like this one every week.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Parse, don&#8217;t validate</h2><p>Alexis King popularized the <strong>parse, don&#8217;t validate</strong> principle with her influential 2019 blog post of the same name (<a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/"><span>https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/</span></a>). King&#8217;s articulation of the principle, which states that parsing generates types that can represent only valid data, while validation merely checks data after the fact, has its roots in functional programming and type theory and helped popularize and name the pattern.</p><p>Rust&#8217;s type system makes this principle particularly natural to apply. The combination of strong typing, pattern matching, and the <code>Result</code> type creates an environment where <em>parse, don&#8217;t validate</em> feels like the path of least resistance rather than additional work.</p><p>The principle suggests that instead of scattering validation logic throughout a code base, we should parse data once at system boundaries into types that can only represent valid states. This transforms validation from a repeated burden into a guarantee encoded in types.</p><h3>The problem with scattered validation</h3><p>Let&#8217;s examine how our current Samsa configuration system handles validation. The current approach is validation scattered everywhere:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;8fc7f1b5-2791-47a9-8cea-64d8e3c3c16b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">pub struct BrokerConfig {
     pub max_connections: i32,    // Could be negative!
     pub buffer_size: usize,      // Could be zero!
     pub port: u16,              // Could be in reserved range!
 }

impl Broker {
     pub fn new(config: BrokerConfig) -&gt; Result&lt;Self, String&gt; {
         // Validation #1 - at construction
         if config.max_connections &lt;= 0 {
             return Err("max_connections must be positive".to_string());
         }
         if config.buffer_size == 0 {
             return Err("buffer_size must be non-zero".to_string());
         }
         Ok(Self { config })
     }

    pub fn accept_connection(&amp;self) -&gt; Result&lt;(), String&gt; {
         // Validation #2 - in business logic (defensive programming)
         if self.config.max_connections &lt;= 0 {
             return Err("Invalid max_connections".to_string());
         }
         Ok(())
     }
 }</code></pre></div><p>Notice how the same validation check, <code>if max_connections &lt;= 0</code>, appears in two different places: once during configuration creation and once defensively in the business logic. This duplication is the core problem. Each location must remember to perform the check, use the same condition, and handle errors consistently. If the validation rule changes (say, <code>max_connections</code> must be at least 10), every location must be updated. This approach is error-prone, inefficient, and creates inconsistent validation logic across different parts of the system.</p><h3>Creating types that enforce validity</h3><p>The solution is to create wrapper types that can only hold valid values. Instead of validating a raw <code>u32</code> everywhere it&#8217;s used, we create a <code>PositiveU32</code> type that validates once at construction time. Any code that receives a <code>PositiveU32</code> type knows, by the type alone, that the value has already been validated.</p><p>Let&#8217;s redesign our configuration using this approach. We&#8217;ll start by creating a wrapper type for positive integers that cannot be zero or negative:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;58b70775-a0f9-413f-81dd-288e5e4776ea&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">use std::num::NonZeroUsize;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PositiveU32(u32);

impl PositiveU32 {
     pub fn new(value: u32) -&gt; Result&lt;Self, ConfigError&gt; {
         if value &gt; 0 {
             Ok(Self(value))
         } else {
             Err(ConfigError::MustBePositive("value".to_string()))
         }
     }

    pub fn get(self) -&gt; u32 {
         self.0
     }
 }</code></pre></div><p>Next, we create a similar wrapper for network ports that ensures the port number is not in the reserved range (ports <code>0</code>&#8211;<code>1023</code>):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;11f42ce4-8094-418d-84ea-d784a67b5405&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NetworkPort(u16);

impl NetworkPort {
     pub fn new(port: u16) -&gt; Result&lt;Self, ConfigError&gt; {
         if port &gt; 1023 {  // Ports 0-1023 are reserved
             Ok(Self(port))
         } else {
             Err(ConfigError::PortInReservedRange(port))
         }
     }

    pub fn get(self) -&gt; u16 {
         self.0
     }
 }</code></pre></div><p>Let&#8217;s also define an error type for configuration errors:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;8de146a1-a498-48df-a2ad-db791d803e8d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">#[derive(Debug, Clone, PartialEq, Eq)]
 pub enum ConfigError {
     MustBePositive(String),
     PortInReservedRange(u16),
 }

impl std::fmt::Display for ConfigError {
     fn fmt(&amp;self, f: &amp;mut std::fmt::Formatter&lt;'_&gt;) -&gt; std::fmt::Result {
         match self {
             ConfigError::MustBePositive(field) =&gt; write!(f, "{} must be positive", field),
             ConfigError::PortInReservedRange(port) =&gt; write!(f, "Port {} is in reserved range", port),
         }
     }
 }

impl std::error::Error for ConfigError {}</code></pre></div><p>Samsa&#8217;s configuration system now uses types that enforce validity at construction time. Once created, these types guarantee their validity throughout the system. This moves all validation to the boundary of our system and eliminates defensive programming in business logic.</p><p>By validating once during construction, we ensure that these types can only ever hold valid values. Rust&#8217;s ownership system guarantees that once created, these values cannot be modified to become invalid.</p><h3>Builder pattern with validation</h3><p>The Builder pattern pairs naturally with validated types. While the wrapper types we defined validate individual values, the Builder pattern coordinates multiple validated values into a complete, consistent configuration. We&#8217;ll create a <code>BrokerConfig</code> that can only be constructed through a builder, ensuring that all validation happens during construction.</p><p>First, let&#8217;s define the configuration struct with validated field types:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;2b997bed-8302-4470-a970-bd71b9793611&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">#[derive(Debug, Clone)]
pub struct BrokerConfig {
    max_connections: PositiveU32,
    buffer_size: NonZeroUsize,
    port: NetworkPort,
}

impl BrokerConfig {
    pub fn builder() -&gt; BrokerConfigBuilder {
        BrokerConfigBuilder::new()
    }

    // Getters that never need validation
    pub fn max_connections(&amp;self) -&gt; u32 {
        self.max_connections.get()
    }

    pub fn buffer_size(&amp;self) -&gt; usize {
        self.buffer_size.get()
    }

    pub fn port(&amp;self) -&gt; u16 {
        self.port.get()
    }
}</code></pre></div><p>The <code>BrokerConfig</code> struct uses validated types for all fields. <code>PositiveU32</code> and <code>NetworkPort</code> ensure that invalid values cannot be stored. The getters return primitive values, which is safe because we know the wrapped values are valid. Notice that all fields are private and accessed through getters. This prevents external code from modifying the configuration after validation.</p><p>Now, we need a way for users to construct a <code>BrokerConfig</code>. Since the struct has private fields with validated types, we can&#8217;t allow direct construction. Users would need access to <code>PositiveU32::new()</code> and <code>NetworkPort::new()</code>, and they&#8217;d have to handle validation errors themselves. Instead, we&#8217;ll provide a builder that accepts raw primitive values, validates them internally, and produces a fully validated <code>BrokerConfig</code> on success.</p><p>The <code>BrokerConfigBuilder</code> struct holds <code>Option</code> fields for each configuration value. As users set values through the builder&#8217;s methods, each value is validated and wrapped in its corresponding type. The final <code>build()</code> method ensures that all required fields are present before constructing the <code>BrokerConfig</code>. Let&#8217;s implement this builder:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;4fc8cddf-65b2-4a42-9177-177eeaacfa42&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">pub struct BrokerConfigBuilder {
    max_connections: Option&lt;PositiveU32&gt;,
    buffer_size: Option&lt;NonZeroUsize&gt;,
    port: Option&lt;NetworkPort&gt;,
}

impl BrokerConfigBuilder {
    pub fn new() -&gt; Self {
        Self {
            max_connections: None,
            buffer_size: None,
            port: None,
        }
    }

    pub fn max_connections(mut self, value: u32) -&gt; Result&lt;Self, ConfigError&gt; {
        self.max_connections = Some(PositiveU32::new(value)?);
        Ok(self)
    }

    pub fn buffer_size(mut self, value: usize) -&gt; Result&lt;Self, ConfigError&gt; {
        self.buffer_size = Some(
            NonZeroUsize::new(value)
                .ok_or_else(|| ConfigError::MustBePositive("buffer_size".to_string()))?
        );
        Ok(self)
    }

    pub fn port(mut self, value: u16) -&gt; Result&lt;Self, ConfigError&gt; {
        self.port = Some(NetworkPort::new(value)?);
        Ok(self)
    }
}</code></pre></div><p>Each setter method validates its input before storing it. The <code>?</code> operator propagates validation errors immediately if validation fails. If validation succeeds, the method consumes <code>self</code> and returns it, enabling method chaining. The use of <code>Option</code> for each field lets us detect missing fields in the <code>build()</code> method.</p><p>The final step is building the configuration, which ensures that all required fields are present and results in a fully validated <code>BrokerConfig</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;45eb0b2e-081e-4bc4-beba-cf37c3606343&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">impl BrokerConfigBuilder {
    pub fn build(self) -&gt; Result&lt;BrokerConfig, ConfigError&gt; {
        Ok(BrokerConfig {
            max_connections: self.max_connections
                .ok_or_else(|| ConfigError::MustBePositive("max_connections not set".to_string()))?,
            buffer_size: self.buffer_size
                .ok_or_else(|| ConfigError::MustBePositive("buffer_size not set".to_string()))?,
            port: self.port
                .ok_or_else(|| ConfigError::PortInReservedRange(0))?,
        })
    }
}</code></pre></div><p>The <code>build()</code> method consumes the builder and returns <code>Result&lt;BrokerConfig, ConfigError&gt;</code>. If any required field is missing, we return an error. This ensures that you can&#8217;t construct an incomplete configuration.</p><p>The usage now looks like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;368ac940-25c3-4fd5-913a-4a11b3e941fb&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">let config = BrokerConfig::builder()
    .max_connections(1000)?
    .buffer_size(4096)?
    .port(8080)?
    .build()?;</code></pre></div><p>The fluent API makes configuration construction readable and catches errors immediately. If any setter returns an error, the <code>?</code> operator short-circuits, and the invalid configuration is never created. The final <code>build()</code> call ensures that no required fields are missing.</p><p>The builder ensures that all configuration values are validated during construction. Once a <code>BrokerConfig</code> exists, all its values are guaranteed to be valid. This design makes it impossible to create invalid configurations, with validation happening once at the system boundary.</p><p>The <em>parse, don&#8217;t validate</em> principle, combined with Rust&#8217;s type system and ownership model, eliminates defensive programming throughout the system by moving all validation to well-defined boundaries.</p><p>So far, we&#8217;ve seen how to create distinct types (NewType) and ensure that those types only hold valid values (parse, don&#8217;t validate). But what about objects that change over time? In the next section, we&#8217;ll explore the TypeState pattern, which uses the type system to track and enforce valid state transitions.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption"><strong>Subscribe for free</strong> to get expert-led deep dives, system design breakdowns, and full book chapters like this one every week.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><div><hr></div><h2>The TypeState pattern</h2><p>The TypeState pattern has academic origins in research on program verification. It was formalized by Robert E. Strom and Shaula Yemini in their 1986 paper <em>TypeState: A Programming Language Concept for Enhancing Software Reliability</em> (which you can read at <a href="https://www.computer.org/csdl/journal/ts/1986/01/06312929/13rRUwIF6aQ"><span>https://www.computer.org/csdl/journal/ts/1986/01/06312929/13rRUwIF6aQ</span></a>). The core idea is to use types to represent different states in which an object can be, making invalid state transitions impossible to express.</p><p>While the concept has existed in type theory for decades, Rust&#8217;s unique combination of features makes it particularly practical to implement. Rust&#8217;s zero-cost abstractions mean the type-level state tracking compiles away entirely, leaving no runtime overhead. The ownership system ensures that state transitions consume the old state, preventing accidental reuse. And generic types with <code>PhantomData</code> provide a clean mechanism for encoding state in the type system.</p><p>Several other languages support similar patterns, most notably session types in research languages and builder patterns in languages such as Java, but Rust&#8217;s combination of performance, safety, and ergonomics makes the TypeState pattern particularly accessible for systems programming.</p><h3>The problem with runtime state management</h3><p>Let&#8217;s examine traditional runtime state management, where state is tracked at runtime:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;743b86b6-25ba-4f46-bd34-c3c56fced5da&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ConsumerState {
     Disconnected,
     Connected,
     Subscribed,
     Paused,
 }

pub struct Consumer {
     broker: Arc&lt;Broker&gt;,
     topic: TopicId,
     consumer_id: ConsumerId,
     state: ConsumerState,
 }

impl Consumer {
     pub fn connect(&amp;mut self) -&gt; Result&lt;(), String&gt; {
         // Runtime check required
         if self.state != ConsumerState::Disconnected {
             return Err("Consumer must be disconnected to connect".to_string());
         }
         self.state = ConsumerState::Connected;
         Ok(())
     }

    pub fn poll(&amp;mut self) -&gt; Result&lt;Option&lt;Message&gt;, String&gt; {
         // Runtime validation in every method
         if self.state != ConsumerState::Subscribed {
             return Err("Consumer must be subscribed to poll
                         messages".to_string());
         }
         Ok(None)
     }
 }</code></pre></div><p>Every method requires runtime validation to ensure a correct state, creating opportunities for errors and adding runtime overhead. The compiler can&#8217;t help us by catching state transition errors, which leads to runtime failures that could have been prevented at compile time.</p><h3>Implementing TypeState for the consumer lifecycle</h3><p>Let&#8217;s redesign the consumer using TypeState. We&#8217;ll create separate types for each state:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;1bfdc9c4-390b-4a41-84eb-908047e3c28d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">use std::marker::PhantomData;
use crate::types::*;

// State marker types organized in a module
pub mod states {
    #[derive(Debug)]
    pub struct Disconnected;

    #[derive(Debug)]
    pub struct Connected;

    #[derive(Debug)]
    pub struct Subscribed;

    #[derive(Debug)]
    pub struct Paused;
}

// Consumer parameterized by state
pub struct Consumer&lt;State&gt; {
    broker: Arc&lt;Broker&gt;,
    topic: Option&lt;TopicId&gt;,
    consumer_id: ConsumerId,
    connection_info: Option&lt;ConnectionInfo&gt;,
    state: PhantomData&lt;State&gt;,
}

/// Type aliases for consumer states
pub type DisconnectedConsumer = Consumer&lt;states::Disconnected&gt;;
pub type ConnectedConsumer = Consumer&lt;states::Connected&gt;;
pub type SubscribedConsumer = Consumer&lt;states::Subscribed&gt;;
pub type PausedConsumer = Consumer&lt;states::Paused&gt;;

#[derive(Debug, Clone)]
pub struct ConnectionInfo {
    pub broker_address: String,
    pub consumer_group: Option&lt;String&gt;,
}</code></pre></div><p>The state marker types (<code>Disconnected</code>, <code>Connected</code>, <code>Subscribed</code>, and <code>Paused</code>) are zero-sized. They exist only at compile time to track state in the type system. The <code>Consumer&lt;State&gt;</code> struct is generic over the state marker, so <code>Consumer&lt;states::Disconnected&gt;</code> and <code>Consumer&lt;states::Connected&gt;</code> are different types even though they contain the same data fields. The type aliases provide convenient shorthand.</p><p>The <code>PhantomData&lt;State&gt;</code> field tells Rust about the <code>state</code> parameter without storing anything at runtime. This is the key to TypeState: state tracked in types with zero runtime cost.</p><p>Let&#8217;s implement the disconnected state:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;0cd0737d-91a1-4313-a2ca-2d242d6363b8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">impl Consumer&lt;states::Disconnected&gt; {
    pub fn new(consumer_id: ConsumerId, broker: Arc&lt;Broker&gt;) -&gt; Self {
        Self {
            broker,
            topic: None,
            consumer_id,
            connection_info: None,
            state: PhantomData,
        }
    }

    pub fn connect(
        self,
        connection_info: ConnectionInfo
    ) -&gt; Result&lt;Consumer&lt;states::Connected&gt;, SamsaError&gt; {
        if connection_info.broker_address.is_empty() {
            return Err(SamsaError::connection("Empty broker address"));
        }

        Ok(Consumer {
            broker: self.broker,
            topic: self.topic,
            consumer_id: self.consumer_id,
            connection_info: Some(connection_info),
            state: PhantomData,
        })
    }
}</code></pre></div><p>The <code>new</code> constructor creates a <code>Consumer&lt;states::Disconnected&gt;</code>. The <code>connect</code> method consumes the disconnected consumer (taking ownership with <code>self</code>) and returns either a <code>Consumer&lt;states::Connected&gt;</code> or an error along with the original consumer. This ownership transfer prevents reuse. Once you call <code>connect</code>, the disconnected consumer is gone.</p><p>Now, let&#8217;s implement the connected state:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;40c10044-f336-4c9a-822c-9b9af376dce6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">impl Consumer&lt;states::Connected&gt; {
    pub fn subscribe(
        mut self,
        topic: TopicId
    ) -&gt; Result&lt;Consumer&lt;states::Subscribed&gt;, SamsaError&gt; {
        self.topic = Some(topic);

        Ok(Consumer {
            broker: self.broker,
            topic: self.topic,
            consumer_id: self.consumer_id,
            connection_info: self.connection_info,
            state: PhantomData,
        })
    }

    pub fn disconnect(self) -&gt; Consumer&lt;states::Disconnected&gt; {
        Consumer {
            broker: self.broker,
            topic: None,
            consumer_id: self.consumer_id,
            connection_info: None,
            state: PhantomData,
        }
    }
}</code></pre></div><p>Each state transition follows the same pattern: consume the current state, perform an operation, and return the new state or an error. The connected consumer can subscribe to a topic or disconnect. Each transition is a separate <code>impl</code> block, making it impossible to call methods from the wrong state.</p><p>Finally, the <code>Subscribed</code> state is where consumers can actually receive messages, while the <code>Paused</code> state can only resume:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;ea81f63a-0d53-4954-a0ad-bb1105186063&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">impl Consumer&lt;states::Subscribed&gt; {
    pub fn receive(&amp;self) -&gt; Option&lt;Event&gt; {
        // Only subscribed consumers can receive - guaranteed by types!
        None
    }

    pub fn pause(self) -&gt; Consumer&lt;states::Paused&gt; {
        Consumer {
            broker: self.broker,
            topic: self.topic,
            consumer_id: self.consumer_id,
            connection_info: self.connection_info,
            state: PhantomData,
        }
    }

    pub fn unsubscribe(mut self) -&gt; Consumer&lt;states::Connected&gt; {
        self.topic = None;
        Consumer {
            broker: self.broker,
            topic: self.topic,
            consumer_id: self.consumer_id,
            connection_info: self.connection_info,
            state: PhantomData,
        }
    }
}

impl Consumer&lt;states::Paused&gt; {
    pub fn resume(self) -&gt; Consumer&lt;states::Subscribed&gt; {
        Consumer {
            broker: self.broker,
            topic: self.topic,
            consumer_id: self.consumer_id,
            connection_info: self.connection_info,
            state: PhantomData,
        }
    }
}</code></pre></div><p>Only <code>Consumer&lt;states::Subscribed&gt;</code> has a <code>receive</code> method. You literally cannot call <code>receive()</code> on consumers in other states. The method doesn&#8217;t exist for those types. The <code>Paused</code> state shows that TypeState can model bidirectional transitions: <code>pause()</code> moves to <code>Paused</code>, and <code>resume()</code> returns to <code>Subscribed</code>.</p><p>This TypeState implementation makes invalid operations impossible to express. The compiler enforces the correct state machine at compile time with no runtime overhead.</p><p>TypeState&#8217;s implementation in Rust uses <code>PhantomData</code> to carry type-level information that exists only at compile time, employs generic types to parameterize over state, and utilizes the ownership system to ensure that state transitions consume the previous state. The result is verification of state machines at compile time with zero runtime cost.</p><h3>Usage examples</h3><p>Here&#8217;s how TypeState improves the developer experience:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;cc52f6b9-6530-4119-82c7-409c07a2fb8a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">fn correct_usage() -&gt; Result&lt;(), SamsaError&gt; {
    let broker = Arc::new(Broker::new());
    let consumer_id = ConsumerId::new("test-consumer")?;
    let topic = TopicId::new("events")?;

    // Create disconnected consumer
    let consumer = Consumer::new(consumer_id, broker);

    // Connect with connection info
    let connection_info = ConnectionInfo::new(
        "localhost:9092".to_string(), None
    );
    let connected = consumer.connect(connection_info)?;

    // Subscribe to topic
    let subscribed = connected.subscribe(topic)?;

    // Can pause and resume
    let paused = subscribed.pause();
    let subscribed = paused.resume();

   // Now we can receive - guaranteed to be in correct state
    if let Some(event) = subscribed.receive() {
        println!("Received: {:?}", event);
    }

    // Unsubscribe and disconnect
    let connected = subscribed.unsubscribe();
    let _disconnected = connected.disconnect();

    Ok(())
}</code></pre></div><p>This code demonstrates the correct sequence: create a disconnected consumer, connect it, subscribe, then receive &#8211; pausing and unpausing, unsubscribing, and disconnecting work the same way. Each state transition is explicit in the code, and the types change at each step. The error handling uses a tuple to return the original consumer on failure, allowing retry logic.</p><p>Invalid usage won&#8217;t compile:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;5dd1cbe0-bb2e-495a-8efa-c134db723838&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">fn invalid_usage() {
    let broker = Arc::new(Broker::new());
    let consumer_id = ConsumerId::new("test").unwrap();

    let consumer = Consumer::new(consumer_id, broker);

    // These lines won't compile:
    // consumer.receive(); // Error: not available on Disconnected
    // consumer.subscribe(topic); // Error: not available on Disconnected
}</code></pre></div><p>The compiler errors shown in the comments aren&#8217;t runtime errors; they&#8217;re compile-time errors. You literally cannot write code that calls <code>receive()</code> on a disconnected consumer because the method doesn&#8217;t exist for that type. This is the power of TypeState: invalid states are unrepresentable.</p><p>The TypeState pattern transforms potential runtime errors into compile-time guarantees, demonstrating how Rust&#8217;s type system and ownership model enable practical implementation of ideas from formal methods and programming language research.</p><p>TypeState controls how individual objects transition through states. But sometimes, we need to control which types can participate in a system at all. In the next section, we&#8217;ll see how sealed traits let us define closed sets of implementations, providing API stability and safety guarantees.</p><div><hr></div><h1>Sealed traits</h1><p>Many object-oriented languages introduce the concept of sealed or final types, which are types that cannot extend beyond a defined scope. Java has final classes, Kotlin has sealed classes, and Scala has sealed traits. The pattern addresses a real problem in API design: sometimes, allowing arbitrary external implementations of an interface creates more problems than it solves.</p><p>Rust doesn&#8217;t have a built-in <code>sealed</code> keyword, but the module system provides a way to achieve the same effect. Following its appearance in various standard library crates and its documentation in API design discussions, the technique gained widespread recognition in the Rust community. While not unique to Rust, the pattern fits naturally with Rust&#8217;s privacy model and module system.</p><p>The sealed traits pattern uses Rust&#8217;s module privacy to create traits that are visible for use but restricted for implementation. This provides library authors with control over trait implementations while maintaining a public interface.</p><h3>The problem with open traits</h3><p>Let&#8217;s understand why we might want to seal traits. Because this is an open trait, anyone can implement it:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;86b67500-c3db-451e-b5c9-805c2c62b4f4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">pub trait MessageSchema {
     type Data;
     type Error: std::error::Error;

    fn parse(data: &amp;[u8]) -&gt; Result&lt;Self::Data, Self::Error&gt;;
     fn serialize(data: &amp;Self::Data) -&gt; Vec&lt;u8&gt;;
     fn validate(data: &amp;Self::Data) -&gt; bool;
 }</code></pre></div><p>Here, we&#8217;ve defined a very straightforward trait defining the signatures of three methods, with associated types for the data payload and the error type. There is nothing unique about this trait, but what we should pay attention to is that this is a public trait, so there are no restrictions on who can implement it or where the implementation happens.</p><p>External crates could implement unsafe versions:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;6be4bd86-9567-4c26-989d-96d5967fb82a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">// struct UnsafeSchema;
// impl MessageSchema for UnsafeSchema {
//     fn parse(_data: &amp;[u8]) -&gt; Result&lt;Self::Data, Self::Error&gt; {
//         unsafe { /* potentially unsafe implementation */ }
//     }
// }</code></pre></div><p>The <code>MessageSchema</code> trait defines an interface that any type can implement. While this flexibility is often desirable, it means we can&#8217;t make guarantees about all implementations. External code could implement the trait incorrectly or in ways that violate our assumptions.</p><p>When external crates can implement <code>MessageSchema</code>, we lose control over quality guarantees and make it difficult to evolve the trait interface without breaking external implementations.</p><p>External implementations might do the following:</p><div class="callout-block" data-callout="true"><ul><li><p>Skip validation or implement it incorrectly</p></li><li><p>Use <code>unsafe</code> code in unexpected ways</p></li><li><p>Depend on undocumented behavior that we later change</p></li></ul></div><p>Additionally, evolving the trait interface becomes difficult. Adding a new required method would break all external implementations, forcing us to use default implementations even when that&#8217;s not ideal.</p><h2>Implementing the sealed traits pattern</h2><p>Let&#8217;s implement the sealed traits pattern for our message schema system, using a private module containing the sealing trait:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;0a693c97-a824-452c-9cd7-35faf8451f9b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">mod private {
    pub trait Sealed {}
}

// Public trait that extends the sealed trait
pub trait MessageSchema: private::Sealed {
    type Message: Clone + std::fmt::Debug;

    fn serialize(message: &amp;Self::Message) -&gt; Vec&lt;u8&gt;;
    fn deserialize(bytes: &amp;[u8]) -&gt; Result&lt;Self::Message, SchemaError&gt;;
    fn schema_id() -&gt; &amp;'static str;
    fn validate(message: &amp;Self::Message) -&gt; Result&lt;(), ValidationError&gt;;
}</code></pre></div><p>The key to the sealed traits pattern is the private module. The <code>Sealed</code> trait is public within the module, but the module itself is private. External crates can see <code>MessageSchema</code> (which extends <code>private::Sealed</code>) but cannot implement <code>Sealed</code>, which means they cannot implement <code>MessageSchema</code>.</p><p>This gives us complete control over which types can implement our schema trait.</p><p>Let&#8217;s implement a JSON schema:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;98571e49-f5ad-4e97-ba83-983848f055a3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">pub struct JsonSchema;

impl private::Sealed for JsonSchema {}

impl MessageSchema for JsonSchema {
    type Message = serde_json::Value;

    fn serialize(message: &amp;Self::Message) -&gt; Vec&lt;u8&gt; {
        serde_json::to_vec(message).unwrap_or_default()
    }

    fn deserialize(bytes: &amp;[u8]) -&gt; Result&lt;Self::Message, SchemaError&gt; {
        serde_json::from_slice(bytes)
            .map_err(|e| SchemaError::DeserializationFailed(e.to_string()))
    }

    fn schema_id() -&gt; &amp;'static str {
        "json_v1"
    }

    fn validate(message: &amp;Self::Message) -&gt; Result&lt;(), ValidationError&gt; {
        if message.is_null() {
            return Err(ValidationError::InvalidValue(
                "Message cannot be null".to_string()
            ));
        }
        Ok(())
    }
}</code></pre></div><p><code>JsonSchema</code> implements both <code>private::Sealed</code> (which we can do because we&#8217;re in the same module) and <code>MessageSchema</code>. The implementation uses <code>serde_json</code> for parsing and serialization and validates that JSON values aren&#8217;t <code>null</code>.</p><p>Let&#8217;s add a text schema for plain <code>String</code> data:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;fac615eb-fe7a-43de-ab11-1c3db6fd5876&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">pub struct TextSchema;

impl private::Sealed for TextSchema {}

impl MessageSchema for TextSchema {
    type Message = String;

    fn serialize(message: &amp;Self::Message) -&gt; Vec&lt;u8&gt; {
        message.as_bytes().to_vec()
    }

    fn deserialize(bytes: &amp;[u8]) -&gt; Result&lt;Self::Message, SchemaError&gt; {
        String::from_utf8(bytes.to_vec())
            .map_err(|e| SchemaError::DeserializationFailed(e.to_string()))
    }

    fn schema_id() -&gt; &amp;'static str {
        "text_v1"
    }

    fn validate(message: &amp;Self::Message) -&gt; Result&lt;(), ValidationError&gt; {
        if message.is_empty() {
            return Err(ValidationError::FieldRequired(
                "message content".to_string()
            ));
        }
        Ok(())
    }
}</code></pre></div><p><code>TextSchema</code> handles plain UTF-8 text. It deserializes byte slices into strings and validates that they aren&#8217;t empty. Both schema implementations are within our module, so both can implement <code>private::Sealed</code>.</p><p>External crates can use <code>JsonSchema</code> and <code>TextSchema</code>, but cannot create their own schema implementations; the sealed traits pattern prevents it.</p><p>The sealed traits pattern leverages Rust&#8217;s module privacy: <code>private::Sealed</code> is public within the module, but the <code>sealed</code> module itself is private. External crates can see and use <code>MessageSchema</code>, but cannot implement <code>Sealed</code>, which means they cannot implement <code>MessageSchema</code>.</p><h3>Type-safe messages with sealed schemas</h3><p>Now, we can combine sealed schemas with the type-safe message patterns we&#8217;ve developed. <code>TypedMessage</code> will be generic over the schema type, ensuring that messages and their schemas are always compatible. The sealed traits pattern prevents external code from creating incompatible schema implementations:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;1ced5220-f28f-4543-bed6-3452d2f1f4eb&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">use std::marker::PhantomData;
use crate::schema::MessageSchema;
use crate::types::*;

#[derive(Debug, Clone)]
pub struct TypedMessage&lt;S: MessageSchema&gt; {
    pub id: MessageId,
    pub content: S::Message,
    pub schema_type: PhantomData&lt;S&gt;,
}

impl&lt;S: MessageSchema&gt; TypedMessage&lt;S&gt; {
    pub fn new(id: MessageId, content: S::Message) -&gt; Result&lt;Self, ValidationError&gt; {
        S::validate(&amp;content)?;

        Ok(TypedMessage {
            id,
            content,
            schema_type: PhantomData,
        })
    }

    pub fn to_bytes(&amp;self) -&gt; Vec&lt;u8&gt; {
        S::serialize(&amp;self.content)
    }

    pub fn schema_id(&amp;self) -&gt; &amp;'static str {
        S::schema_id()
    }
}</code></pre></div><p><code>TypedMessage</code> is generic over the <code>S</code> schema type, where <code>S</code> implements <code>MessageSchema</code>. The <code>content</code> field has the <code>S::Message</code> type, which is the associated type we defined in the message schema trait for the type of our message payload. When we wrote our trait implementations, we defined concrete types for <code>MessageSchema::Message: serde_json::Value</code> for <code>TypedMessage&lt;JsonSchema&gt;</code> and <code>String</code> for <code>TypedMessage&lt;TextSchema&gt;</code>. The <code>schema_type</code> field uses <code>PhantomData</code> to track the schema type at compile time without storing anything at runtime. The <code>new</code> constructor validates the content using the schema before creating the message.</p><p>We can extend <code>TypedMessage</code> with additional methods for parsing raw data. The following shows how you might add a <code>parse</code> method (this extension is not in the base repository, but demonstrates the pattern):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;49bf6db5-608c-481d-992b-cd16144a1f16&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">impl&lt;S: MessageSchema&gt; TypedMessage&lt;S&gt; {
    pub fn parse(
        id: MessageId,
        raw_data: &amp;[u8],
    ) -&gt; Result&lt;Self, S::Error&gt; {
        let content = S::deserialize(raw_data)?;
        Ok(Self {
            id,
            content,
            schema_type: PhantomData,
        })
    }

    pub fn serialize(&amp;self) -&gt; Vec&lt;u8&gt; {
        S::serialize(&amp;self.content)
    }
}

pub type JsonMessage = TypedMessage&lt;JsonSchema&gt;;
pub type TextMessage = TypedMessage&lt;TextSchema&gt;;</code></pre></div><p>The extended <code>parse</code> method takes raw bytes and uses the schema&#8217;s deserializer to construct typed content. If parsing fails, the error type is <code>S::Error</code>, which is <code>serde_json::Error</code> for JSON or <code>std::str::Utf8Error</code> for text. The <code>serialize</code> method converts the content back into bytes. This demonstrates how you can build on the sealed trait foundation to add richer functionality.</p><p>The type aliases provide convenient names: <code>JsonMessage</code> and <code>TextMessage</code> are clearer than writing <code>TypedMessage&lt;JsonSchema&gt;</code> everywhere. These messages are both type-safe (wrong schema = compile error) and sealed (can&#8217;t add new schemas externally).</p><p>Thus, the sealed traits pattern provides controlled extensibility. We maintain the ability to evolve our trait interface while providing compile-time guarantees about which implementations exist.</p><div><hr></div><h4>Summary</h4><p>In this deep-dive, we explored four patterns that leverage Rust&#8217;s type system, each with established origins in programming language research and practice.</p><p>The NewType pattern comes from functional programming languages, particularly Haskell, where it has been a formal language feature since the 1990s. Rust&#8217;s contribution is making the pattern zero-cost: type safety is enforced at compile time and optimized away entirely at runtime, making it practical for systems programming, where performance matters.</p><p><em>Parse, don&#8217;t validate</em> was articulated by Alexis King in 2019, building on ideas from functional programming and type theory. Rust&#8217;s type system, ownership model, and <code>Result</code> type make this principle natural to apply, turning boundary validation into type-level guarantees that persist throughout the program.</p><p>The TypeState pattern originated in formal methods research, specifically in Strom and Yemini&#8217;s 1986 work on program verification. Rust&#8217;s generic types with <code>PhantomData</code>, zero-cost abstractions, and ownership system make this academic concept practical for everyday systems programming, providing compile-time state machine verification with no runtime overhead.</p><p>Sealed traits draw from similar concepts in Java (<code>final</code> classes), Kotlin (<code>sealed</code> classes), and Scala (<code>sealed</code> traits). Rust implements the pattern through its module privacy system, providing a natural fit with the language&#8217;s existing visibility rules.</p><p>What these patterns demonstrate is Rust&#8217;s ability to take established ideas from type theory, functional programming, and formal methods and make them practical for systems programming. The combination of strong static typing, zero-cost abstractions, and memory safety without garbage collection creates opportunities to apply patterns that were previously either too expensive or impractical in systems languages.</p><p>Our enhanced Samsa system now leverages decades of programming language research while maintaining the performance characteristics necessary for systems programming. This synthesis, applying proven patterns in a performant, safe environment, represents one of Rust&#8217;s key contributions to the programming language space.</p><p>The book&#8217;s next chapter explores patterns from functional programming, including function composition pipelines, generics as type classes, pattern matching techniques, and closures as architectural components.</p><div><hr></div><p><strong>More from Evan Williams</strong></p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;a4a65f3b-09c1-4f79-a131-2844a3b93e31&quot;,&quot;caption&quot;:&quot;Eval Driven Development for Engineers&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Deep Engineering #47: Evan Williams on Why Experienced Developers Have the Hardest Time Learning Rust&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:427210082,&quot;name&quot;:&quot;Saqib Jan&quot;,&quot;bio&quot;:&quot;/localhost&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/997a788a-cd78-4f84-9b3b-c72ab6dc0153_1008x1008.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-05-14T16:42:52.048Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/43a42d88-ec70-4d7f-8213-85796343b4f5_677x337.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://deepengineering.net/p/deep-engineering-47-why-experienced-developers-hardest-time-learning-rust&quot;,&quot;section_name&quot;:&quot;Newsletter Issues&quot;,&quot;video_upload_id&quot;:null,&quot;id&quot;:197666671,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:11,&quot;comment_count&quot;:0,&quot;publication_id&quot;:1729053,&quot;publication_name&quot;:&quot;Packt Deep Engineering&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!H5BJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736bc1ee-d689-497e-83a8-7d9bf9022eb9_600x600.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;5badedc9-2f70-48fd-8fcb-2a9def45cb0e&quot;,&quot;caption&quot;:&quot;Evan Williams has been writing software for more than 40 years, across every layer of the stack and more programming languages than most engineers will encounter in a career. His book, Design Patterns and Best Practices in Rust, published by Packt, is not a pattern catalogue. It is an argument for a different way of thinking about code entirely, aimed s&#8230;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Design Patterns, Ownership Models, and Building Resilient Systems in Rust with Evan Williams&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:427210082,&quot;name&quot;:&quot;Saqib Jan&quot;,&quot;bio&quot;:&quot;/localhost&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/997a788a-cd78-4f84-9b3b-c72ab6dc0153_1008x1008.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-05-13T18:07:20.569Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/-ElpmT7DCX4&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://deepengineering.net/p/design-patterns-ownership-models-resilient-systems-rust-evan-williams&quot;,&quot;section_name&quot;:&quot;Interviews&quot;,&quot;video_upload_id&quot;:null,&quot;id&quot;:197553608,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:2,&quot;comment_count&quot;:0,&quot;publication_id&quot;:1729053,&quot;publication_name&quot;:&quot;Packt Deep Engineering&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!H5BJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736bc1ee-d689-497e-83a8-7d9bf9022eb9_600x600.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d27e9897-e080-46a2-83f4-ac2a9681da80&quot;,&quot;caption&quot;:&quot;Rust, who would have thought, has ranked as the most loved programming language in the Stack Overflow developer survey for nine consecutive years. Honestly, I must admit this is an unusual kind of statistic because it measures not just adoption but retention. The engineers who use Rust want to keep using it, and that pattern has only deepened even as th&#8230;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Rust Is Hard for the Engineers with the Most Experience&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:427210082,&quot;name&quot;:&quot;Saqib Jan&quot;,&quot;bio&quot;:&quot;/localhost&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/997a788a-cd78-4f84-9b3b-c72ab6dc0153_1008x1008.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null},{&quot;id&quot;:11407185,&quot;name&quot;:&quot;Francesco Ciulla&quot;,&quot;bio&quot;:&quot;Developer Advocate at @dailydotdev\n&#183; Docker Captain &#128051;\n&#183; Public Speaker\n&#183; Building a 1 Million Community 22%&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8c30606-10ba-4c87-a89b-af2f9dc27a01_400x400.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null,&quot;primaryPublicationSubscribeUrl&quot;:&quot;https://francescociulla.substack.com/subscribe?&quot;,&quot;primaryPublicationUrl&quot;:&quot;https://francescociulla.substack.com&quot;,&quot;primaryPublicationName&quot;:&quot;Francesco's Newsletter&quot;,&quot;primaryPublicationId&quot;:1410908}],&quot;post_date&quot;:&quot;2026-05-18T16:07:41.936Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f3cfa859-6cd7-4d3e-ab4b-31c7b0cd00c5_1440x660.webp&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://deepengineering.net/p/rust-is-hard-for-the-engineers-with-the-most-experience&quot;,&quot;section_name&quot;:&quot;Engineering Leadership&quot;,&quot;video_upload_id&quot;:null,&quot;id&quot;:198261752,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:1729053,&quot;publication_name&quot;:&quot;Packt Deep Engineering&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!H5BJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736bc1ee-d689-497e-83a8-7d9bf9022eb9_600x600.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="pullquote"><p><em>This deep-dive is Chapter 10 of <a href="https://www.packtpub.com/en-us/product/design-patterns-and-best-practices-in-rust-9781836209478">Design Patterns and Best Practices in Rust</a> by <a href="https://www.linkedin.com/in/evan-williams-1512092">Evan Williams</a>, published by <a href="https://www.packtpub.com/">Packt</a> and reproduced here in full with the publisher&#8217;s permission for knowledge sharing with the Deep Engineering community. The book charts a transformation across three hands-on projects, a deliberately broken calculator that shows what goes wrong when you write Java or C++ in Rust syntax, a rebuild that adapts the classic Gang of Four patterns to the ownership model, and Samsa, the publish/subscribe microservice these pages extend with patterns unique to the language. All rights remain with <a href="https://www.packtpub.com/">Packt Publishing</a>, and this content may not be reproduced, redistributed, or remixed in any form without the publisher&#8217;s written consent. We also sat down with Evan Williams to talk about the thinking behind these patterns, including why he finds typestate almost impossible to stop talking about, <a href="https://deepengineering.net/p/design-patterns-ownership-models-resilient-systems-rust-evan-williams">read the conversation here</a>. You can get the full book <a href="https://www.packtpub.com/en-us/product/design-patterns-and-best-practices-in-rust-9781836209478">here</a>.</em></p><div><hr></div></div>]]></content:encoded></item><item><title><![CDATA[Core Architectural Patterns for LLM System Design ]]></title><description><![CDATA[How to design for resilience, latency, cost, and trust when your newest dependency is slower and less predictable than anything else in your stack]]></description><link>https://deepengineering.net/p/core-architectural-patterns-for-llm-system-design</link><guid isPermaLink="false">https://deepengineering.net/p/core-architectural-patterns-for-llm-system-design</guid><dc:creator><![CDATA[Sampriti Mitra]]></dc:creator><pubDate>Thu, 09 Jul 2026 09:25:27 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f88846d4-0cd6-4025-8548-73aae36c03ea_2400x1600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Integrating LLMs into a production system introduces a new class of dependency: one that is non-deterministic, high-latency, and carries a high, variable operational cost. The fundamentals of LLM integration, tokens, embeddings, and the basic idea of retrieval-augmented generation (RAG), are only the starting point.</p><p>As experienced engineers, we already know how to build reliable systems. This deep-dive isn&#8217;t about reinventing those principles, but about adapting them for the unique, messy challenges LLMs throw at us. Consider this an architect&#8217;s playbook that outlines the new patterns needed to meet these challenges.</p><blockquote><p><em>This <strong>deep-dive</strong> is Chapter 2 of <a href="https://www.packtpub.com/en-us/product/system-design-for-the-llm-era-9781807789923">System Design for the LLM Era</a> by <a href="https://in.linkedin.com/in/sampritimitra">Sampriti Mitra</a>, shared with permission from Packt, with all rights remaining with the publisher and no reproduction or redistribution without written consent. You can get the full book <a href="https://www.packtpub.com/en-us/product/system-design-for-the-llm-era-9781807789923">here</a>.</em></p></blockquote><div class="callout-block" data-callout="true"><p>In this deep-dive we&#8217;ll be looking at the following topics:</p><ul><li><p>Designing for resilience and reliability</p></li><li><p>Designing for low latency</p></li><li><p>Designing for cost optimization</p></li><li><p>Designing for grounding and data management</p></li><li><p>Designing for testability and observability</p></li><li><p>Designing for security and trust</p></li><li><p>Engineering for production</p></li><li><p>Training with test data</p></li><li><p>Respecting user privacy</p></li></ul></div><div><hr></div><p><strong>Featured workshop: <a href="https://www.eventbrite.co.uk/e/loop-engineering-for-ai-agents-tickets-1992373400474?aff=deepeng">Loop Engineering for AI Agents</a></strong></p><div class="pullquote"><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.eventbrite.co.uk/e/loop-engineering-for-ai-agents-tickets-1992373400474?aff=deepeng" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!87in!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff593ef62-ef12-4da5-ad48-bcb0d2806b40_900x300.png 424w, https://substackcdn.com/image/fetch/$s_!87in!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff593ef62-ef12-4da5-ad48-bcb0d2806b40_900x300.png 848w, https://substackcdn.com/image/fetch/$s_!87in!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff593ef62-ef12-4da5-ad48-bcb0d2806b40_900x300.png 1272w, https://substackcdn.com/image/fetch/$s_!87in!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff593ef62-ef12-4da5-ad48-bcb0d2806b40_900x300.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!87in!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff593ef62-ef12-4da5-ad48-bcb0d2806b40_900x300.png" width="900" height="300" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f593ef62-ef12-4da5-ad48-bcb0d2806b40_900x300.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:300,&quot;width&quot;:900,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.eventbrite.co.uk/e/loop-engineering-for-ai-agents-tickets-1992373400474?aff=deepeng&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!87in!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff593ef62-ef12-4da5-ad48-bcb0d2806b40_900x300.png 424w, https://substackcdn.com/image/fetch/$s_!87in!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff593ef62-ef12-4da5-ad48-bcb0d2806b40_900x300.png 848w, https://substackcdn.com/image/fetch/$s_!87in!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff593ef62-ef12-4da5-ad48-bcb0d2806b40_900x300.png 1272w, https://substackcdn.com/image/fetch/$s_!87in!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff593ef62-ef12-4da5-ad48-bcb0d2806b40_900x300.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A 4-hour hands-on workshop on <strong>Loop Engineering</strong>, designing agent workflows with Claude Code, SDD, and MCP that verify their own work and stay observable in production.</p><p><em>Use<strong> DEEPENG40</strong> for 40% discount.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.eventbrite.co.uk/e/loop-engineering-for-ai-agents-tickets-1992373400474?aff=deepeng&quot;,&quot;text&quot;:&quot;Register here&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.eventbrite.co.uk/e/loop-engineering-for-ai-agents-tickets-1992373400474?aff=deepeng"><span>Register here</span></a></p></div><h2><span>Designing for resilience and reliability</span></h2><p><span>Our system&#8217;s stability is now tied to an external API that is slower, more expensive, and less predictable than any database or microservice calls. The primary goal is to decouple the application&#8217;s health from the provider&#8217;s health. Two types of pattern are especially valuable in this regard: the </span><strong><span>GenAI service pattern</span></strong><span> (or </span><strong><span>LLM gateway pattern</span></strong><span>) and the </span><strong><span>circuit breaker pattern</span></strong><span>.</span></p><h3><span>Pattern: the GenAI service or LLM gateway</span></h3><p><span>When we first start building with LLMs, our instinct is to treat them like any other third-party API. We install the SDK, generate an API key, and make the call directly from our application code.</span></p><p><span>It feels fast. It feels efficient. We write a Python function, import </span><span data-color="#e06666" style="color: rgb(224, 102, 102);">openai</span><span>, and we are shipping features in minutes.</span></p><p><span>The architecture looks like this:</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!unGq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F442e332f-4895-4c1e-a8b2-e453818752e3_959x423.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!unGq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F442e332f-4895-4c1e-a8b2-e453818752e3_959x423.png 424w, https://substackcdn.com/image/fetch/$s_!unGq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F442e332f-4895-4c1e-a8b2-e453818752e3_959x423.png 848w, https://substackcdn.com/image/fetch/$s_!unGq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F442e332f-4895-4c1e-a8b2-e453818752e3_959x423.png 1272w, https://substackcdn.com/image/fetch/$s_!unGq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F442e332f-4895-4c1e-a8b2-e453818752e3_959x423.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!unGq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F442e332f-4895-4c1e-a8b2-e453818752e3_959x423.png" width="959" height="423" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/442e332f-4895-4c1e-a8b2-e453818752e3_959x423.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:423,&quot;width&quot;:959,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.1: Naive model-calling logic&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.1: Naive model-calling logic" title="Figure 2.1: Naive model-calling logic" srcset="https://substackcdn.com/image/fetch/$s_!unGq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F442e332f-4895-4c1e-a8b2-e453818752e3_959x423.png 424w, https://substackcdn.com/image/fetch/$s_!unGq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F442e332f-4895-4c1e-a8b2-e453818752e3_959x423.png 848w, https://substackcdn.com/image/fetch/$s_!unGq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F442e332f-4895-4c1e-a8b2-e453818752e3_959x423.png 1272w, https://substackcdn.com/image/fetch/$s_!unGq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F442e332f-4895-4c1e-a8b2-e453818752e3_959x423.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><span>Figure 2.1: Naive model-calling logic</span></figcaption></figure></div><p><span>While this works for a weekend hackathon, it creates a high-coupling, low-cohesion architecture in production with the following issues:</span></p><div class="callout-block" data-callout="true"><ul><li><p>Vendor lock-in: If Service A is written using the OpenAI SDK, migrating to Anthropic requires rewriting the entire code block.</p></li><li><p>Inconsistent reliability: Service A might have excellent retry logic, while Service B crashes on the first timeout. There is no standard.</p></li><li><p>Observability black holes: You have no central place to see how much you are spending. You have to log into three different developer consoles to tally up the bill.</p></li><li><p>Security risks: API keys are scattered across multiple environment variables in multiple services, increasing the surface area for leaks.</p></li></ul></div><p><span>Stating the problem more formally now:</span></p><p><span>Problem: Our services should not be calling OpenAI, Anthropic, or Google directly. This creates high-coupling, high maintenance problems.</span></p><p><span>Solution: To solve this, we borrow a pattern from traditional microservices: the API gateway. We stop treating LLMs as external vendors and start treating them as a unified internal resource.</span></p><p><span>Implement a single, centralized LLM gateway. This is a microservice that acts as the only entry point for all LLM calls. Our services talk only to this gateway using a single, unified API format. The gateway handles the messy details of talking to the outside world.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ITfe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103c054b-e903-4484-a925-34533ef075ca_734x1600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ITfe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103c054b-e903-4484-a925-34533ef075ca_734x1600.png 424w, https://substackcdn.com/image/fetch/$s_!ITfe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103c054b-e903-4484-a925-34533ef075ca_734x1600.png 848w, https://substackcdn.com/image/fetch/$s_!ITfe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103c054b-e903-4484-a925-34533ef075ca_734x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!ITfe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103c054b-e903-4484-a925-34533ef075ca_734x1600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ITfe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103c054b-e903-4484-a925-34533ef075ca_734x1600.png" width="734" height="1600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/103c054b-e903-4484-a925-34533ef075ca_734x1600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1600,&quot;width&quot;:734,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.2: The LLM gateway pattern &#8211; decoupling internal services from external providers&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.2: The LLM gateway pattern &#8211; decoupling internal services from external providers" title="Figure 2.2: The LLM gateway pattern &#8211; decoupling internal services from external providers" srcset="https://substackcdn.com/image/fetch/$s_!ITfe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103c054b-e903-4484-a925-34533ef075ca_734x1600.png 424w, https://substackcdn.com/image/fetch/$s_!ITfe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103c054b-e903-4484-a925-34533ef075ca_734x1600.png 848w, https://substackcdn.com/image/fetch/$s_!ITfe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103c054b-e903-4484-a925-34533ef075ca_734x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!ITfe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103c054b-e903-4484-a925-34533ef075ca_734x1600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2.2: The LLM gateway pattern &#8211; decoupling internal services from external providers</figcaption></figure></div><p><span>This pattern brings some significant benefits:</span></p><div class="callout-block" data-callout="true"><ul><li><p>Abstraction: Can switch models (e.g. GPT-5 for Claude 3 Opus) with a config change, not a re-deployment</p></li><li><p>Centralized control: All other resilience, cost, and monitoring patterns are implemented in this one place</p></li><li><p>Authentication: Manages all authentications in one service</p></li><li><p>Fallbacks and reliability: If OpenAI goes down, the gateway can automatically retry the request with Anthropic. The upstream service never even knows there was an outage</p></li></ul></div><h3><span>Pattern: circuit breakers with tiered fallbacks</span></h3><p><span>An LLM provider might be slow, down, or just returning bad data. Simply retrying a failed call (like you would for a 503 on an external service) is often the wrong move.</span></p><p><span>The solution is to combine the circuit breaker pattern with </span><strong><span>tiered fallbacks</span></strong><span> following a three-stage model:</span></p><ol><li><p><span>Monitor: The GenAI Service monitors the health (latency, error rate) of each model provider.</span></p></li><li><p><span>Trip: If a primary model (e.g. GPT-5) exceeds a failure threshold, the circuit opens.</span></p></li><li><p><span>Reroute: All subsequent requests are immediately and automatically rerouted to a backup model.</span></p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XXsV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc79e98b-2ce4-4676-996b-2d0dfbca4d24_907x591.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XXsV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc79e98b-2ce4-4676-996b-2d0dfbca4d24_907x591.png 424w, https://substackcdn.com/image/fetch/$s_!XXsV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc79e98b-2ce4-4676-996b-2d0dfbca4d24_907x591.png 848w, https://substackcdn.com/image/fetch/$s_!XXsV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc79e98b-2ce4-4676-996b-2d0dfbca4d24_907x591.png 1272w, https://substackcdn.com/image/fetch/$s_!XXsV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc79e98b-2ce4-4676-996b-2d0dfbca4d24_907x591.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XXsV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc79e98b-2ce4-4676-996b-2d0dfbca4d24_907x591.png" width="907" height="591" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cc79e98b-2ce4-4676-996b-2d0dfbca4d24_907x591.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:591,&quot;width&quot;:907,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.3: Tiered fallbacks with circuit breakers&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.3: Tiered fallbacks with circuit breakers" title="Figure 2.3: Tiered fallbacks with circuit breakers" srcset="https://substackcdn.com/image/fetch/$s_!XXsV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc79e98b-2ce4-4676-996b-2d0dfbca4d24_907x591.png 424w, https://substackcdn.com/image/fetch/$s_!XXsV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc79e98b-2ce4-4676-996b-2d0dfbca4d24_907x591.png 848w, https://substackcdn.com/image/fetch/$s_!XXsV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc79e98b-2ce4-4676-996b-2d0dfbca4d24_907x591.png 1272w, https://substackcdn.com/image/fetch/$s_!XXsV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc79e98b-2ce4-4676-996b-2d0dfbca4d24_907x591.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2.3: Tiered fallbacks with circuit breakers</figcaption></figure></div><p><span>For example, we might implement the following tiered fallback strategy:</span></p><p><span>Tier 1: GPT-5 (high-cost, high-reasoning)</span></p><p><span>Tier 2 (fallback): Claude 3 Haiku (medium-cost, fast)</span></p><p><span>Tier 3 (fallback): Llama 3 8B (locally hosted, free, less smart)</span></p><p><span>Tier 4 (final fallback): Return a cached good enough response or a graceful error message to the client UI: &#8216;Our AI assistant is at high capacity, please try again in a moment&#8217;</span></p><p><span>We cannot leave the circuit open forever. We need </span><strong><span>recovery logic</span></strong><span> to close the circuit that includes a half-open state:</span></p><p><span>Sleep: After the circuit trips, wait for a defined cooldown (e.g. 30 seconds).</span></p><p><span>Probe: Allow a single canary request to pass through to the primary provider.</span></p><p><span>Reset: If the canary succeeds, close the circuit and resume full traffic. If it fails, restart the cooldown.</span></p><p><span>For our </span><strong><span>retry strategy</span></strong><span> we have two options: interactive and asynchronous:</span></p><div class="callout-block" data-callout="true"><ul><li><p>Interactive/synchronous: Do not use aggressive <strong>exponential backoff</strong>. If a user is waiting, a 60-second retry is effectively downtime. Use capped backoff (start 500 ms, max 1 s) or fail fast to a fallback model.</p></li><li><p>Asynchronous: Use exponential backoff (wait 1 min, 2 min, 4 min). Since no user is waiting, we can afford to wait out a 5-minute provider outage.</p></li></ul></div><div><hr></div><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption"><strong>Subscribe for free</strong> to get expert-led deep dives, system design breakdowns, and full book chapters like this one every week.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h2><span>Designing for low latency</span></h2><p><span>LLM inference is fundamentally slow. A user requesting a search result expects a response in &lt; 500 ms, but an LLM might take 5&#8211;10 seconds to generate a full answer. We cannot change the speed of inference, but we can architect around it. Hybrid processing (synchronous vs. asynchronous), response streaming and caching patterns can help us.</span></p><h3><span>Pattern: hybrid processing (synchronous vs. asynchronous)</span></h3><p><span>Given that we cannot block a user&#8217;s web request for 10 seconds while waiting for an LLM, one option is to separate the workloads. This is the most important latency-saving pattern.</span></p><p><span>Synchronous path (&lt; 2 s): For immediate, low-latency needs. These are tasks that must be fast, like code complete or a real-time e-commerce search. These paths should use fast, cheap models or rely heavily on caching.</span></p><p><span>Asynchronous path (&gt; 10 s): For high-latency, long-running tasks use a </span><strong><span>message queue</span></strong><span> (like Kafka or SQS) to decouple the initial request from the actual LLM work. The client receives an immediate &#8216;202 Accepted&#8217; response. For example, use when generating an AI-powered report, in agentic workflows, or for offline content generation. The client can poll for the result or receive it via a WebSocket/callback.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CF42!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af1d49e-c0ce-406e-ae41-52d86a8caa41_950x954.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CF42!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af1d49e-c0ce-406e-ae41-52d86a8caa41_950x954.png 424w, https://substackcdn.com/image/fetch/$s_!CF42!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af1d49e-c0ce-406e-ae41-52d86a8caa41_950x954.png 848w, https://substackcdn.com/image/fetch/$s_!CF42!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af1d49e-c0ce-406e-ae41-52d86a8caa41_950x954.png 1272w, https://substackcdn.com/image/fetch/$s_!CF42!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af1d49e-c0ce-406e-ae41-52d86a8caa41_950x954.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CF42!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af1d49e-c0ce-406e-ae41-52d86a8caa41_950x954.png" width="950" height="954" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1af1d49e-c0ce-406e-ae41-52d86a8caa41_950x954.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:954,&quot;width&quot;:950,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.4: Sync and async flows&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.4: Sync and async flows" title="Figure 2.4: Sync and async flows" srcset="https://substackcdn.com/image/fetch/$s_!CF42!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af1d49e-c0ce-406e-ae41-52d86a8caa41_950x954.png 424w, https://substackcdn.com/image/fetch/$s_!CF42!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af1d49e-c0ce-406e-ae41-52d86a8caa41_950x954.png 848w, https://substackcdn.com/image/fetch/$s_!CF42!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af1d49e-c0ce-406e-ae41-52d86a8caa41_950x954.png 1272w, https://substackcdn.com/image/fetch/$s_!CF42!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af1d49e-c0ce-406e-ae41-52d86a8caa41_950x954.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2.4: Sync and async flows</figcaption></figure></div><h3><span>Pattern: response streaming</span></h3><p><span>Even a fast 3-second response feels slow if the user is staring at a loading spinner, so if possible stream the response token-by-token. Once the model generates the first word, send it to the client. This dramatically improves perceived latency by lowering the time-to-first-token (TTFT). This is a non-negotiable pattern for any conversational or chat application.</span></p><h4><span>How it works</span></h4><p><span>We use </span><strong><span>SSE</span></strong><span> (</span><strong><span>server-sent events</span></strong><span>) over standard HTTP. SSE is unidirectional (server -&gt; client) and runs over standard HTTP/2, making it firewall-friendly and easy to implement.</span></p><p><span>Client: Opens a persistent connection.</span></p><p><span>Server: Instead of returning a JSON object, it returns a generator (an iterator)</span></p><p><span>Header: The server must set content-type: text/event-stream</span></p><p><span>Format: Data is sent in chunks prefixed &#8216;data:&#8217;:</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pLDx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67b2b3a5-eeec-4a72-8dc0-cb85b875ffa1_1194x1262.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pLDx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67b2b3a5-eeec-4a72-8dc0-cb85b875ffa1_1194x1262.png 424w, https://substackcdn.com/image/fetch/$s_!pLDx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67b2b3a5-eeec-4a72-8dc0-cb85b875ffa1_1194x1262.png 848w, https://substackcdn.com/image/fetch/$s_!pLDx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67b2b3a5-eeec-4a72-8dc0-cb85b875ffa1_1194x1262.png 1272w, https://substackcdn.com/image/fetch/$s_!pLDx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67b2b3a5-eeec-4a72-8dc0-cb85b875ffa1_1194x1262.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pLDx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67b2b3a5-eeec-4a72-8dc0-cb85b875ffa1_1194x1262.png" width="1194" height="1262" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/67b2b3a5-eeec-4a72-8dc0-cb85b875ffa1_1194x1262.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1262,&quot;width&quot;:1194,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.5: Streaming tokens&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.5: Streaming tokens" title="Figure 2.5: Streaming tokens" srcset="https://substackcdn.com/image/fetch/$s_!pLDx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67b2b3a5-eeec-4a72-8dc0-cb85b875ffa1_1194x1262.png 424w, https://substackcdn.com/image/fetch/$s_!pLDx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67b2b3a5-eeec-4a72-8dc0-cb85b875ffa1_1194x1262.png 848w, https://substackcdn.com/image/fetch/$s_!pLDx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67b2b3a5-eeec-4a72-8dc0-cb85b875ffa1_1194x1262.png 1272w, https://substackcdn.com/image/fetch/$s_!pLDx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67b2b3a5-eeec-4a72-8dc0-cb85b875ffa1_1194x1262.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2.5: Streaming tokens</figcaption></figure></div><h3><span>Pattern: caching strategies</span></h3><p><span>LLM calls are slow and expensive, while cache hits are the fastest, cheapest LLM calls you can make. Therefore implement a multi-level caching strategy:</span></p><h4><span>Level 1: exact match</span></h4><div class="callout-block" data-callout="true"><ul><li><p>Scenario: A viral product launch. 10,000 users ask &#8216;When is shipping?&#8217;.</p></li><li><p>Mechanism: Hash the prompt string <mark data-color="rgb(255, 255, 0)" style="background-color: rgb(255, 255, 0); color: rgb(0, 0, 0);">(sha256(&#8221;When is shipping?&#8221;))</mark>. Check Redis.</p></li><li><p>Result: 9,999 users get a 5 ms response.</p></li><li><p>Why: It catches the stampede of identical queries.</p></li></ul></div><h4><span>Level 2: semantic match</span></h4><div class="callout-block" data-callout="true"><ul><li><p>Scenario: User A asks &#8216;How do I reset password?&#8217; User B asks &#8216;Forgot password, help&#8217;.</p></li><li><p>Mechanism: L1 misses (strings don&#8217;t match). We convert &#8216;Forgot password, help&#8217; to a vector. We query the vector DB for similar past questions.</p></li><li><p>Result: The DB finds that &#8216;How do I reset password?&#8217; has a 0.98 similarity. It returns the cached answer for User A to User B.</p></li><li><p>Why: It catches different phrasings of the same intent, saving expensive reasoning costs.</p></li></ul></div><h4><span>Level 3: proactive caching</span></h4><div class="callout-block" data-callout="true"><ul><li><p>Scenario: A personalized &#8216;Daily Report&#8217; for 100,000 users.</p></li><li><p>Mechanism: Don&#8217;t wait for them to open the app at 9:00 AM. Run a batch job at 6:00 AM. Generate the reports and store them into Redis.</p></li><li><p>Result: When users log in, the AI generation feels instant because it happened 3 hours ago.</p></li><li><p>Why: It moves latency from online (user waiting) to offline. This is a core pattern in adaptive learning platforms and e-commerce search.</p></li></ul></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nDhi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db30fb9-3000-4f5e-be1e-2e898e001948_875x1147.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nDhi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db30fb9-3000-4f5e-be1e-2e898e001948_875x1147.png 424w, https://substackcdn.com/image/fetch/$s_!nDhi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db30fb9-3000-4f5e-be1e-2e898e001948_875x1147.png 848w, https://substackcdn.com/image/fetch/$s_!nDhi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db30fb9-3000-4f5e-be1e-2e898e001948_875x1147.png 1272w, https://substackcdn.com/image/fetch/$s_!nDhi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db30fb9-3000-4f5e-be1e-2e898e001948_875x1147.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nDhi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db30fb9-3000-4f5e-be1e-2e898e001948_875x1147.png" width="875" height="1147" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6db30fb9-3000-4f5e-be1e-2e898e001948_875x1147.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1147,&quot;width&quot;:875,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.6: Caching strategy&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.6: Caching strategy" title="Figure 2.6: Caching strategy" srcset="https://substackcdn.com/image/fetch/$s_!nDhi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db30fb9-3000-4f5e-be1e-2e898e001948_875x1147.png 424w, https://substackcdn.com/image/fetch/$s_!nDhi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db30fb9-3000-4f5e-be1e-2e898e001948_875x1147.png 848w, https://substackcdn.com/image/fetch/$s_!nDhi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db30fb9-3000-4f5e-be1e-2e898e001948_875x1147.png 1272w, https://substackcdn.com/image/fetch/$s_!nDhi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db30fb9-3000-4f5e-be1e-2e898e001948_875x1147.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2.6: Caching strategy</figcaption></figure></div><h3><span>Pattern: coalesce caching</span></h3><p><span>During high-traffic events, thousands of users might ask the exact same question simultaneously. A standard cache misses the first time for everyone, causing a stampede of identical requests to the LLM.</span></p><p><span>The solution is middleware that identifies identical in-flight requests. It pauses subsequent requests, waits for the first request to complete, and then serves that single LLM response to all waiting users. This reduces load on the provider by orders of magnitude.</span></p><div><hr></div><h2><span>Designing for cost optimization</span></h2><p><span>LLMs introduce a new, variable, and unbounded operational cost (</span><strong><span>COGS</span></strong><span> &#8211; cost of goods sold). A single complex query can cost dollars. Architectural decisions are now financial decisions. Important in this context are the </span><strong><span>model router</span></strong><span>, </span><strong><span>dynamic traffic control</span></strong><span> and </span><strong><span>prompt engineering</span></strong><span> and </span><strong><span>compression</span></strong><span> patterns.</span></p><h3><span>Pattern: the model router</span></h3><p><span>Not all tasks require the smartest (and most expensive) model. Using GPT-5 for a simple grammar check is like booking a helicopter to avoid traffic. By implementing a rule engine within your LLM Gateway to act as a model router you can dynamically route requests to the cheapest model that is good enough for the task.</span></p><p><span>Example rules:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;29627da5-2b1c-48b1-9bc2-b2f428e7031c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">IF task_type == 'simple_grammar_check' THEN route_to 'local_llama_8b'.
IF task_type == 'complex reasoning' AND user_tier == 'premium' THEN route_to 'GPT-5'.</code></pre></div><h3><span>Pattern: dynamic traffic control (utilization-based routing)</span></h3><p><span>A static rule for traffic routing, e.g. IF task == complex THEN GPT-5, can cause bottlenecks during traffic spikes. In this case we can use a production-grade router that acts as a traffic controller, implementing </span><strong><span>load shedding</span></strong><span> if the primary model&#8217;s latency breaches the P99 SLA (e.g. &gt; 2s) due to provider congestion. In those circumstances the router proactively shifts traffic to the faster or cheaper model, even for complex tasks. A </span><strong><span>cost ceiling</span></strong><span> can be established by enforcing a hard cap on tokens. If a prompt exceeds a threshold (e.g. 8k tokens), force-route to a lower-cost model to prevent a single query causing cost regression.</span></p><h3><span>Pattern: prompt engineering and compression</span></h3><p><span>Cost is based on the number of input and output tokens; large prompts are expensive. Therefore treat your prompt context as a cost to be optimized. Before sending a large document (e.g. 50 pages of chat history) to an LLM, use a cheaper, faster model to summarize or compress it first.</span></p><div><hr></div><h2><span>Designing for grounding and data management</span></h2><p><span>LLMs hallucinate (invent facts) and have knowledge cutoffs (their training data is stale). How can we build a reliable enterprise application on this foundation? Retrieval-augmented generation (RAG) approaches are a fundamental pattern for enabling modern LLM applications to address these fundamental issues.</span></p><h3><span>Pattern: retrieval-augmented generation (RAG)</span></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-p-g!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc2eebf-d8de-4fa6-8370-a93a1dde6308_403x833.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-p-g!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc2eebf-d8de-4fa6-8370-a93a1dde6308_403x833.png 424w, https://substackcdn.com/image/fetch/$s_!-p-g!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc2eebf-d8de-4fa6-8370-a93a1dde6308_403x833.png 848w, https://substackcdn.com/image/fetch/$s_!-p-g!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc2eebf-d8de-4fa6-8370-a93a1dde6308_403x833.png 1272w, https://substackcdn.com/image/fetch/$s_!-p-g!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc2eebf-d8de-4fa6-8370-a93a1dde6308_403x833.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-p-g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc2eebf-d8de-4fa6-8370-a93a1dde6308_403x833.png" width="403" height="833" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ccc2eebf-d8de-4fa6-8370-a93a1dde6308_403x833.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:833,&quot;width&quot;:403,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.7: Retrieval-augmented generation&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.7: Retrieval-augmented generation" title="Figure 2.7: Retrieval-augmented generation" srcset="https://substackcdn.com/image/fetch/$s_!-p-g!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc2eebf-d8de-4fa6-8370-a93a1dde6308_403x833.png 424w, https://substackcdn.com/image/fetch/$s_!-p-g!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc2eebf-d8de-4fa6-8370-a93a1dde6308_403x833.png 848w, https://substackcdn.com/image/fetch/$s_!-p-g!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc2eebf-d8de-4fa6-8370-a93a1dde6308_403x833.png 1272w, https://substackcdn.com/image/fetch/$s_!-p-g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc2eebf-d8de-4fa6-8370-a93a1dde6308_403x833.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2.7: Retrieval-augmented generation</figcaption></figure></div><p><span>Not uncommonly we need the LLM to answer questions about private, real-time, or domain-specific data. Instead of asking the LLM a question, the solution is to tell it the answer. As the RAG name suggests, there are three principal aspects to consider:</span></p><p><span>Retrieve: When a user asks, &#8216;What&#8217;s the status of order #123?&#8217;, you first query the database to get the order details.</span></p><p><span>Augment: You augment the prompt with this retrieved data.</span></p><p><span>Generate: You instruct the LLM &#8211; &#8216;Based only on the following context, generate a friendly response&#8217;.</span></p><p><span>Example context: </span><span data-color="#e06666" style="color: rgb(224, 102, 102);">{order_details_json}</span></p><h3><span>Pattern: the ingestion pipeline</span></h3><p><span>To build the retrieval component of a RAG application, we must prepare the knowledge base (documents, tickets, etc.) for retrieval. That means creating an ingestion pipeline, typically as an asynchronous, scalable job (e.g. using Spark). This pipeline:</span></p><ol><li><p><span>Chunks large documents into small, semantically meaningful pieces</span></p></li><li><p><span>Embeds each chunk by calling an embedding model API</span></p></li><li><p><span>Stores the chunk and its corresponding vector in a </span><strong><span>vector database</span></strong><span> (e.g. OpenSearch, Pinecone, or pg_vector [with postgres])</span></p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ccbq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb05a87bd-534b-4422-9cf8-e26a3ef8e419_307x645.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ccbq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb05a87bd-534b-4422-9cf8-e26a3ef8e419_307x645.png 424w, https://substackcdn.com/image/fetch/$s_!ccbq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb05a87bd-534b-4422-9cf8-e26a3ef8e419_307x645.png 848w, https://substackcdn.com/image/fetch/$s_!ccbq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb05a87bd-534b-4422-9cf8-e26a3ef8e419_307x645.png 1272w, https://substackcdn.com/image/fetch/$s_!ccbq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb05a87bd-534b-4422-9cf8-e26a3ef8e419_307x645.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ccbq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb05a87bd-534b-4422-9cf8-e26a3ef8e419_307x645.png" width="307" height="645" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b05a87bd-534b-4422-9cf8-e26a3ef8e419_307x645.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:645,&quot;width&quot;:307,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.8: Ingestion pipeline&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.8: Ingestion pipeline" title="Figure 2.8: Ingestion pipeline" srcset="https://substackcdn.com/image/fetch/$s_!ccbq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb05a87bd-534b-4422-9cf8-e26a3ef8e419_307x645.png 424w, https://substackcdn.com/image/fetch/$s_!ccbq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb05a87bd-534b-4422-9cf8-e26a3ef8e419_307x645.png 848w, https://substackcdn.com/image/fetch/$s_!ccbq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb05a87bd-534b-4422-9cf8-e26a3ef8e419_307x645.png 1272w, https://substackcdn.com/image/fetch/$s_!ccbq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb05a87bd-534b-4422-9cf8-e26a3ef8e419_307x645.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2.8: Ingestion pipeline</figcaption></figure></div><h4><span>Pattern: hybrid RAG</span></h4><p><span>RAG using just vector search (or even simple term search) is not enough for complex, interconnected data. It&#8217;s good at finding similar content, but bad at traversing relationships. Hybrid RAG approaches such as GraphRAG represent an advanced RAG pattern where the ingestion pipeline populates both a vector database (for semantic similarity) and a knowledge graph (e.g. Neptune or Neo4j) for structured relationships.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bkLW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0422109-57ac-465c-b9a1-743fa4eb97d5_1060x655.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bkLW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0422109-57ac-465c-b9a1-743fa4eb97d5_1060x655.png 424w, https://substackcdn.com/image/fetch/$s_!bkLW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0422109-57ac-465c-b9a1-743fa4eb97d5_1060x655.png 848w, https://substackcdn.com/image/fetch/$s_!bkLW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0422109-57ac-465c-b9a1-743fa4eb97d5_1060x655.png 1272w, https://substackcdn.com/image/fetch/$s_!bkLW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0422109-57ac-465c-b9a1-743fa4eb97d5_1060x655.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bkLW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0422109-57ac-465c-b9a1-743fa4eb97d5_1060x655.png" width="1060" height="655" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d0422109-57ac-465c-b9a1-743fa4eb97d5_1060x655.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:655,&quot;width&quot;:1060,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.9: Hybrid RAG architecture &#8211; combining vector search with knowledge graphs&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.9: Hybrid RAG architecture &#8211; combining vector search with knowledge graphs" title="Figure 2.9: Hybrid RAG architecture &#8211; combining vector search with knowledge graphs" srcset="https://substackcdn.com/image/fetch/$s_!bkLW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0422109-57ac-465c-b9a1-743fa4eb97d5_1060x655.png 424w, https://substackcdn.com/image/fetch/$s_!bkLW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0422109-57ac-465c-b9a1-743fa4eb97d5_1060x655.png 848w, https://substackcdn.com/image/fetch/$s_!bkLW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0422109-57ac-465c-b9a1-743fa4eb97d5_1060x655.png 1272w, https://substackcdn.com/image/fetch/$s_!bkLW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0422109-57ac-465c-b9a1-743fa4eb97d5_1060x655.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2.9: Hybrid RAG architecture &#8211; combining vector search with knowledge graphs</figcaption></figure></div><p><span>The RAG orchestrator then queries both systems to build a much richer, more accurate context, as a customer support agent system demonstrates well.</span></p><h3><span>Pattern: function calling (tool usage)</span></h3><p><span>To overcome the problem that LLMs are generally bad at (for example) math and cannot interact with the outside world, we ask it to decide which tool to use. We provide a schema of tools (e.g. </span><span data-color="#e06666" style="color: rgb(224, 102, 102);">get_weather(city)</span><span>), and the LLM outputs a structured JSON object requesting that function. The application layer executes the code and feeds the result back to the LLM.</span></p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption"><strong>Subscribe for free</strong> to get expert-led deep dives, system design breakdowns, and full book chapters like this one every week.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><span>Designing for testability and observability</span></h2><p><span>Testability for AI-powered systems is crucial in order to maintain the quality of response expected from the system, but for non-deterministic systems it is not trivial to write unit tests that would assert on a given known output.</span></p><p><span>We use a pattern of using an LLM as a judge by creating a golden dataset, which has input with their ideal responses, and using that dataset, the LLM judge is trained. This LLM is later used to evaluate the quality of response returned by the system against the ideal response.</span></p><p><span>Problem: How do you write a unit test for a system that is non-deterministic?</span></p><p><span>Something like assert(response) == &#8220;expected_string&#8221; will fail constantly.</span></p><h3><span>Pattern: golden datasets</span></h3><p><span>We need a reliable way to catch regressions in AI quality, and one way is to create a &#8216;golden dataset&#8217; of 50&#8211;100 representative inputs and their ideal outputs. In your CI/CD pipeline, run your system against this set to ensure that prompt changes or model upgrades haven&#8217;t broken core functionality.</span></p><h3><span>Pattern: LLM-as-a-Judge</span></h3><p><span>How do you assert the quality of a golden set test at scale? You can&#8217;t manually review 100 responses on every build. What you can do, however, is use a powerful LLM like GPT-5 as an evaluator or judge. You feed the judge the original prompt, the golden answer, and your system&#8217;s actual response, and then ask the Judge to score the actual response from 1 to 5 on metrics like accuracy, groundedness, and tone. This gives you a quantifiable quality metric you can track over time.</span></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mox2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0036a75-f6a1-46db-a9b2-0970a5def762_1020x221.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mox2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0036a75-f6a1-46db-a9b2-0970a5def762_1020x221.png 424w, https://substackcdn.com/image/fetch/$s_!mox2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0036a75-f6a1-46db-a9b2-0970a5def762_1020x221.png 848w, https://substackcdn.com/image/fetch/$s_!mox2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0036a75-f6a1-46db-a9b2-0970a5def762_1020x221.png 1272w, https://substackcdn.com/image/fetch/$s_!mox2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0036a75-f6a1-46db-a9b2-0970a5def762_1020x221.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mox2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0036a75-f6a1-46db-a9b2-0970a5def762_1020x221.png" width="1020" height="221" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d0036a75-f6a1-46db-a9b2-0970a5def762_1020x221.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:221,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.10: The LLM-as-a-Judge evaluation pipeline for CI/CD&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.10: The LLM-as-a-Judge evaluation pipeline for CI/CD" title="Figure 2.10: The LLM-as-a-Judge evaluation pipeline for CI/CD" srcset="https://substackcdn.com/image/fetch/$s_!mox2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0036a75-f6a1-46db-a9b2-0970a5def762_1020x221.png 424w, https://substackcdn.com/image/fetch/$s_!mox2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0036a75-f6a1-46db-a9b2-0970a5def762_1020x221.png 848w, https://substackcdn.com/image/fetch/$s_!mox2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0036a75-f6a1-46db-a9b2-0970a5def762_1020x221.png 1272w, https://substackcdn.com/image/fetch/$s_!mox2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0036a75-f6a1-46db-a9b2-0970a5def762_1020x221.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Figure 2.10: The LLM-as-a-Judge evaluation pipeline for CI/CD</figcaption></figure></div><h3><span>Pattern: new observability metrics</span></h3><p><span>Existing performance dashboards (CPU, RAM, 5xx errors, etc.) are insufficient in the LLM era. We need to add a new layer of monitoring focused on the LLM itself, for example:</span></p><p><span>Cost: Track Cost_Per_Query and Total_Cost_Per_User. Set alerts for cost spikes.</span></p><p><span>Performance: Monitor P99 time to first token (TTFT) and tokens per second (TPS).</span></p><p><span>Provider health: Monitor 429_Rate_Limit_Errors and 5xx_Server_Errors per provider to feed your circuit breakers.</span></p><p><span>Quality: Track your LLM-as-a-Judge scores. Monitor the </span><strong><span>escalation rate</span></strong><span> &#8211; this measures the percentage of conversations where the AI fails to resolve the issue, forcing the system to transfer the work to a human (for example in case of customer support agents). A spike in this metric indicates a drop in model quality.</span></p><div><hr></div><h2><span>Designing for security and trust</span></h2><p><span>Treating an LLM as a simple API call is naive and dangerous. It&#8217;s a non-deterministic component that you are inviting inside your trusted system. We must architect our systems to defend against a new class of vulnerabilities. A variety of patterns can help us in this, including the following. In this section we summarize the main security patterns, the threats they address and the mitigations they provide.</span></p><h3><span>Pattern: mitigating prompt injection</span></h3><p><span>Threat: Attackers trick an LLM into ignoring its original instructions by embedding malicious commands in prompts, causing it to perform unintended actions.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tcKA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7558f49f-335a-4b63-87e7-5900b5950958_802x870.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tcKA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7558f49f-335a-4b63-87e7-5900b5950958_802x870.png 424w, https://substackcdn.com/image/fetch/$s_!tcKA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7558f49f-335a-4b63-87e7-5900b5950958_802x870.png 848w, https://substackcdn.com/image/fetch/$s_!tcKA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7558f49f-335a-4b63-87e7-5900b5950958_802x870.png 1272w, https://substackcdn.com/image/fetch/$s_!tcKA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7558f49f-335a-4b63-87e7-5900b5950958_802x870.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tcKA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7558f49f-335a-4b63-87e7-5900b5950958_802x870.png" width="802" height="870" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7558f49f-335a-4b63-87e7-5900b5950958_802x870.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:870,&quot;width&quot;:802,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.11: The firewall pattern &#8211; using a lightweight model to filter malicious intent before it reaches the core model&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.11: The firewall pattern &#8211; using a lightweight model to filter malicious intent before it reaches the core model" title="Figure 2.11: The firewall pattern &#8211; using a lightweight model to filter malicious intent before it reaches the core model" srcset="https://substackcdn.com/image/fetch/$s_!tcKA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7558f49f-335a-4b63-87e7-5900b5950958_802x870.png 424w, https://substackcdn.com/image/fetch/$s_!tcKA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7558f49f-335a-4b63-87e7-5900b5950958_802x870.png 848w, https://substackcdn.com/image/fetch/$s_!tcKA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7558f49f-335a-4b63-87e7-5900b5950958_802x870.png 1272w, https://substackcdn.com/image/fetch/$s_!tcKA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7558f49f-335a-4b63-87e7-5900b5950958_802x870.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2.11: The firewall pattern &#8211; using a lightweight model to filter malicious intent before it reaches the core model</figcaption></figure></div><h4><span>Mitigation</span></h4><p><span>Instruction/data separation: Clearly separate trusted instructions from untrusted data from the user. Use role-based API structures (system, user) and wrap all user input in clear delimiters like &lt;&gt;.</span></p><p><span>Input filtering: Use a second, simpler, faster LLM to classify the intent of a user&#8217;s prompt. If it detects a likely attack, reject it before it reaches your primary model.</span></p><p><span>Output filtering: Always validate the LLM&#8217;s response. If it contains any system prompt text or suspicious keywords, block it.</span></p><h3><span>Pattern: secure output handling</span></h3><p><span>Threat: Insecure output handling occurs when an LLM&#8217;s outputs are not properly sanitized before being used, potentially leading to attacks like </span><strong><span>cross-site scripting</span></strong><span> (</span><strong><span>XSS</span></strong><span>).</span></p><h4><span>Mitigation</span></h4><p><span>Treat as untrusted: Never eval() code output.</span></p><p><span>Sanitize and encode: If the output is HTML, sanitize it. If it&#8217;s text for a web page, encode it to prevent XSS.</span></p><p><span>Validate: If you expect JSON, parse it in a try/catch block and validate its schema.</span></p><p><span>Parameterize: If the LLM helps build a SQL query, have it generate the parameters for a pre-defined, parameterized query you control. Never execute a raw SQL string from an LLM.</span></p><h3><span>Pattern: mitigating excessive agency</span></h3><p><span>Threat: This happens when an LLM is given too much control and makes unauthorized decisions or actions without human oversight.</span></p><h4><span>Mitigation</span></h4><p><span>Dynamic permissions: Do not give the LLM agent a static set of all possible tools.</span></p><p><span>Plan-approve-execute: Implement a multi-step loop. The LLM proposes a plan. Your code approves this plan. Only then do you execute it with a scoped-down client.</span></p><p><span>Human-in-the-loop (HITL): For high-impact actions (e.g. &#8216;delete database&#8217;, &#8216;refund customer&#8217;), always require explicit human approval.</span></p><h3><span>Pattern: mitigating sensitive information disclosure</span></h3><p><span>Threat: The model may inadvertently reveal confidential data or </span><strong><span>personally identifiable information</span></strong><span> (</span><strong><span>PII</span></strong><span>) that was present in its training data.</span></p><h4><span>Mitigation (data hygiene)</span></h4><p><span>PII/data scrubbing: Aggressively sanitize all data before it is used for training or RAG ingestion.</span></p><p><span>Zero-retention policies: For ultra-sensitive data (like user code), process it in-memory and discard it immediately. Do not store it.</span></p><p><span>Tenant-level RAG filtering: This is a critical architectural pattern. All RAG queries must include a filter for tenant_id or user_id. Never perform a vector search on the entire database and hope the LLM picks the right data.</span></p><h3><span>Pattern: preventing model denial of service (MDoS)</span></h3><p><span>Threat: Attackers overload the LLM with resource-intensive requests, slowing it down or making it unavailable.</span></p><h4><span>Mitigation</span></h4><p><span>API rate limiting: Enforce strict per-user and per-IP rate limits at the API gateway</span></p><p><span>Input validation: Reject queries that are obviously abusive (e.g. a 50,000-token prompt)</span></p><p><span>Cost-based throttling: Implement logic to monitor the cost of a user&#8217;s queries in real-time. If a single user is incurring high costs, temporarily throttle their access. We would want to return a 429 in case of user spam/abuse or redirect to a lower model in case user hit their budget.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5be2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55606989-d179-4cf1-8df9-cb4889d961ab_620x917.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5be2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55606989-d179-4cf1-8df9-cb4889d961ab_620x917.png 424w, https://substackcdn.com/image/fetch/$s_!5be2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55606989-d179-4cf1-8df9-cb4889d961ab_620x917.png 848w, https://substackcdn.com/image/fetch/$s_!5be2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55606989-d179-4cf1-8df9-cb4889d961ab_620x917.png 1272w, https://substackcdn.com/image/fetch/$s_!5be2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55606989-d179-4cf1-8df9-cb4889d961ab_620x917.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5be2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55606989-d179-4cf1-8df9-cb4889d961ab_620x917.png" width="620" height="917" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55606989-d179-4cf1-8df9-cb4889d961ab_620x917.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:917,&quot;width&quot;:620,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.12: Throttling to prevent DoS attacks on LLM&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.12: Throttling to prevent DoS attacks on LLM" title="Figure 2.12: Throttling to prevent DoS attacks on LLM" srcset="https://substackcdn.com/image/fetch/$s_!5be2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55606989-d179-4cf1-8df9-cb4889d961ab_620x917.png 424w, https://substackcdn.com/image/fetch/$s_!5be2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55606989-d179-4cf1-8df9-cb4889d961ab_620x917.png 848w, https://substackcdn.com/image/fetch/$s_!5be2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55606989-d179-4cf1-8df9-cb4889d961ab_620x917.png 1272w, https://substackcdn.com/image/fetch/$s_!5be2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55606989-d179-4cf1-8df9-cb4889d961ab_620x917.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2.12: Throttling to prevent DoS attacks on LLM</figcaption></figure></div><p><span>Here&#8217;s an example implementation:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;482c59db-8b95-45a5-8663-05a39f0fa72f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from fastapi import HTTPException

def route_request(user, prompt):
    # 1. HARD LIMIT CHECK (Redis Counter)
    current_rate = redis.get(f"rate:{user.id}")
    if current_rate &gt; 50:
        # Scenario A: Abuse -&gt; Hard Stop
        raise HTTPException(status_code=429, detail="Rate limit exceeded.")

    # 2. SOFT BUDGET CHECK (DB Query)
    daily_spend = db.get_spend(user.id)
    budget_limit = 10.00  # $10 limit
    if daily_spend &gt; budget_limit:
        # Scenario B: Over Budget -&gt; Downgrade (Soft Throttle)
        # We don't fail; we just swap the model
        print(f"User {user.id} over budget. Downgrading to Tier 3.")
        return call_llm(model="llama-3-8b", prompt=prompt)

    # 3. NORMAL PATH
    return call_llm(model="gpt-4", prompt=prompt)</code></pre></div><h3><span>Pattern: preventing data poisoning</span></h3><p><span>Threat: Malicious actors tamper with an LLM&#8217;s training data to corrupt its behavior, leading to biased or incorrect outputs.</span></p><p><span>Mitigation (ingestion control):</span></p><p><span>Trusted sources: Only use and ingest data from known, trusted sources.</span></p><p><span>Data lineage: Track the origin of all data used for training or RAG.</span></p><p><span>HITL review: For fine-tuning data, use human experts to review and validate the datasets before training.</span></p><h3><span>Pattern: mitigating supply chain and insecure plugin vulnerabilities</span></h3><p><span>Threat: The security of an LLM can be compromised through third-party services, plugins, or datasets that are themselves vulnerable. This includes insecure plugin design, which can be exploited for attacks like SQL injection.</span></p><h4><span>Mitigation</span></h4><p><span>Minimize functionality: Any plugin or tool given to the LLM should have the absolute minimal functionality needed (e.g. read_email, not delete_email).</span></p><p><span>Validate plugin inputs: Treat all data passed to a plugin as untrusted. Sanitize it to prevent injection attacks within the plugin itself.</span></p><p><span>Vulnerability scanning: Regularly scan all third-party libraries, containers, and models for known vulnerabilities.</span></p><p><span>Use the genAI service / LLM gateway: Your gateway allows you to quickly replace a provider or model that is found to be compromised.</span></p><div><hr></div><h2><span>Engineering for production</span></h2><p><span>We cannot manage what we do not track. Beyond standard APM (application performance monitoring), we must track:</span></p><p><span>TTFT (time to first token): The perceived latency. How long until the user sees the first character? High TTFT kills engagement.</span></p><p><strong><span>TPOT</span></strong><span> (</span><strong><span>time per output token</span></strong><span>): The generation speed. If this is high, the model is too heavy or the provider is overloaded.</span></p><p><span>Context utilization: Is the context window filled? Use the model optimized for token usage for the use case for cost effectiveness.</span></p><h2><span>Training with test data</span></h2><p><span>In earlier sections we have discussed golden datasets &#8211; inputs with their ideal outputs used for training the LLM as a judge. Let&#8217;s now look at how we can procure this ideal data for different use cases.</span></p><h3><span>Sourcing datasets</span></h3><div class="callout-block" data-callout="true"><ul><li><p>Curate diverse set of open source projects with varied languages, sizes and domains</p></li><li><p>Refresh the dataset regularly to incorporate up to date coding style and practices</p></li><li><p>Create custom open-source repos with intentional gaps and edge cases to stress test behaviors</p></li></ul></div><h3><span>Testing and training for code complete</span></h3><p><span>Randomly remove code blocks, functions, classes from the code. Start typing signatures of missing classes and functions. Compare the IDE code complete suggestions with the actual classes and functions to measure accuracy and semantic correctness. Iteratively tune the LLM model prompts based on error cases and coverage gaps identified.</span></p><h3><span>Testing for chat responses</span></h3><p><span>Clone the repo removing all the comments and docs. Ask the system to explain code blocks, functions, classes and compare against the original repo&#8217;s docs, README files or comments.</span></p><h3><span>Testing for agentic workflow and tasks</span></h3><p><span>Assign real world tasks like adding documentation, writing test cases or refactoring to the agent. Combine multiple tasks in scenarios.</span></p><p><span>Automatically check the code and tests and documentation for correctness and style.</span></p><p><span>Integrating these tests into CI/CDs helps keep models and prompts updated with evolving repos.</span></p><h4><span>Pattern: quantitative evaluation testing</span></h4><p><span>The goal of this pattern is to move beyond vibes and qualitative observations to a measurable correctness percentage.</span></p><p><span>The problem is that agentic flows are multi-step and non-deterministic. Traditional pass/fail unit tests often fail to capture the nuance of a complex task that is mostly correct but slightly off in tone or style.</span></p><p><span>To tackle this distinctive character we assign a numerical score to every agentic run by breaking the output into weighted criteria. This allows us to track an evaluation success rate, the percentage of tasks successfully completed to the golden standard.</span></p><p><span>To calculate the success rate we define weights to the different output facets that reflect their relative importance (e.g. logic: 50%, syntax: 30%, documentation: 20%).</span></p><p><span>Then we execute the agent across a golden dataset of at least 50 representative tasks, and score according to the following metric:</span></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3WNA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5751aa4e-7a98-46e0-ba13-26b404e4a499_830x73.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3WNA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5751aa4e-7a98-46e0-ba13-26b404e4a499_830x73.png 424w, https://substackcdn.com/image/fetch/$s_!3WNA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5751aa4e-7a98-46e0-ba13-26b404e4a499_830x73.png 848w, https://substackcdn.com/image/fetch/$s_!3WNA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5751aa4e-7a98-46e0-ba13-26b404e4a499_830x73.png 1272w, https://substackcdn.com/image/fetch/$s_!3WNA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5751aa4e-7a98-46e0-ba13-26b404e4a499_830x73.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3WNA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5751aa4e-7a98-46e0-ba13-26b404e4a499_830x73.png" width="830" height="73" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5751aa4e-7a98-46e0-ba13-26b404e4a499_830x73.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:73,&quot;width&quot;:830,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Eq&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Eq" title="Eq" srcset="https://substackcdn.com/image/fetch/$s_!3WNA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5751aa4e-7a98-46e0-ba13-26b404e4a499_830x73.png 424w, https://substackcdn.com/image/fetch/$s_!3WNA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5751aa4e-7a98-46e0-ba13-26b404e4a499_830x73.png 848w, https://substackcdn.com/image/fetch/$s_!3WNA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5751aa4e-7a98-46e0-ba13-26b404e4a499_830x73.png 1272w, https://substackcdn.com/image/fetch/$s_!3WNA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5751aa4e-7a98-46e0-ba13-26b404e4a499_830x73.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><span>Figure 2.13 shows the overall evaluation process:</span></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!htsq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7d0950b-bd89-429c-bee5-1b2b565c81f9_1338x312.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!htsq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7d0950b-bd89-429c-bee5-1b2b565c81f9_1338x312.png 424w, https://substackcdn.com/image/fetch/$s_!htsq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7d0950b-bd89-429c-bee5-1b2b565c81f9_1338x312.png 848w, https://substackcdn.com/image/fetch/$s_!htsq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7d0950b-bd89-429c-bee5-1b2b565c81f9_1338x312.png 1272w, https://substackcdn.com/image/fetch/$s_!htsq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7d0950b-bd89-429c-bee5-1b2b565c81f9_1338x312.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!htsq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7d0950b-bd89-429c-bee5-1b2b565c81f9_1338x312.png" width="1338" height="312" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d7d0950b-bd89-429c-bee5-1b2b565c81f9_1338x312.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:312,&quot;width&quot;:1338,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 2.13: Weighted agentic evaluation&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 2.13: Weighted agentic evaluation" title="Figure 2.13: Weighted agentic evaluation" srcset="https://substackcdn.com/image/fetch/$s_!htsq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7d0950b-bd89-429c-bee5-1b2b565c81f9_1338x312.png 424w, https://substackcdn.com/image/fetch/$s_!htsq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7d0950b-bd89-429c-bee5-1b2b565c81f9_1338x312.png 848w, https://substackcdn.com/image/fetch/$s_!htsq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7d0950b-bd89-429c-bee5-1b2b565c81f9_1338x312.png 1272w, https://substackcdn.com/image/fetch/$s_!htsq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7d0950b-bd89-429c-bee5-1b2b565c81f9_1338x312.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Figure 2.13: Weighted agentic evaluation</figcaption></figure></div><p><span>When to use it: Use this in your CI/CD pipeline. If a prompt change or model upgrade causes the correctness percentage to drop below a defined threshold (e.g. 90%) then the deployment should be automatically blocked to prevent quality regression.</span></p><h3><span>Mutation testing</span></h3><p><span>Add logic and syntax errors to your code. Evaluate whether the system can identify and fix these errors.</span></p><h3><span>Negative prompt testing</span></h3><p><span>Give confusing or risky actions like &#8216;Delete all databases&#8217;. Evaluate whether the system is able to flag it as a risk and ignore/refuse or ask the user for further clarification.</span></p><h2><span>Respecting user privacy</span></h2><p><span>We want to be able to use an LLM-powered system for code completions, reviews, generation etc. without leaking our codebase to the model. Techniques for ensuring that user privacy is maintained while the data is still served from the model include:</span></p><div class="callout-block" data-callout="true"><ul><li><p>Ephemeral data handling: Code snippets and related info, like filenames, should never be saved to disk or databases. Encrypted code should only be decrypted in the computer&#8217;s live memory for processing and deleted the moment it&#8217;s no longer needed.</p></li><li><p>Embedding-only search: We don&#8217;t store the actual code. Instead, code snippets are converted into a mathematical format (vectors) for searching. This process is irreversible, so the original code cannot be reconstructed from the database. (We also scramble all metadata. Real file and function names are replaced with anonymous, hashed IDs.)</p></li><li><p>Strict access control</p><ul><li><p>Minimized code transfer: Only code context required for a query or code complete is sent to the server, not the entire codebase.</p></li><li><p>Encryption policies: All requests sent to/from the client are encrypted, and all data stored on server side is encrypted.</p></li></ul></li></ul></div><h2><span>Summary</span></h2><p><span>The patterns we&#8217;ve just looked at, from the GenAI service and async queues to RAG and LLM-as-a-Judge, form an architect&#8217;s playbook for building production-grade, AI-powered systems. In the rest of the book we examine four case studies that utilize this playbook.</span></p><div><hr></div><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption"><strong>Subscribe for free</strong> to get expert-led deep dives, system design breakdowns, and full book chapters like this one every week.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="pullquote"><p>This practical deep-dive is an excerpt, Chapter 2, from <strong><a href="https://www.packtpub.com/en-us/product/system-design-for-the-llm-era-9781807789923">System Design for the LLM Era: Patterns and principles for production-grade AI architecture</a></strong> by <a href="https://in.linkedin.com/in/sampritimitra">Sampriti Mitra</a>, published by Packt. It is shared here with the publisher&#8217;s permission for knowledge sharing with the Deep Engineering community. All rights remain with Packt Publishing. This content may not be reproduced, redistributed, or remixed in any form without the publisher&#8217;s written consent. You can get the full book <a href="https://www.packtpub.com/en-us/product/system-design-for-the-llm-era-9781807789923">here</a>.</p></div>]]></content:encoded></item><item><title><![CDATA[Autonomic Governance for Agentic Systems]]></title><description><![CDATA[A discipline for bounding, auditing, and reversing agentic operational decisions so automation dampens incidents instead of amplifying them.]]></description><link>https://deepengineering.net/p/autonomic-governance-for-agentic-systems</link><guid isPermaLink="false">https://deepengineering.net/p/autonomic-governance-for-agentic-systems</guid><dc:creator><![CDATA[Sibasis Padhi]]></dc:creator><pubDate>Wed, 01 Jul 2026 18:00:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d182b3af-ffd3-4493-aa7b-1a141df2846d_1200x460.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The incident often starts small. Usually one dependency slows, tail latency grows, and a few requests time out. And then the platform does what we trained it to do. All the while, customers automatically retry their failed requests, and very quickly there are far more retry attempts than real new customers trying to use the service. But the autoscaling system sees the growing queue and reads it as exploding traffic, so it adds more servers, often in the wrong place, which sends even more work to the spot that is already overloaded.</p><p>This eats into the bottom line and the return on engineering investment as the bill keeps climbing while the system handles less real work. Circuit breakers finally trip and try to move traffic elsewhere, but those other paths were never built to absorb a sudden flood.</p><p>Everything might still look like it is working on paper, but the whole situation keeps getting worse. Now compound that by adding agentic AI to operations, systems that recommend or execute operational actions, which increases the speed and breadth of change under conditions that are already chaotic.</p><p>AI-driven operational automation can increase fragility when it increases the rate, scope, or coupling of production actions without bounded actuation, explicit constraints, and auditability. Under governance, the same automation reduces fragility by enforcing safety envelopes and reversible decision paths.</p><p>This is not an argument against automation or AI. It is an argument that reliability engineering must evolve from managing distributed systems to governing the automation that manages them, especially under simultaneous SLO, cost, and compliance constraints. I call this discipline <strong>Autonomic Reliability Governance (ARG)</strong>. ARG is not more automation. But governed automation that is policy-constrained, auditable, and rollback-capable by design. It ensures that AI-assisted or agentic operational decisions cannot escalate into amplification engines under stress.</p><h2>Why reliability automation amplifies in microservice ecosystems</h2><p>In microservice systems, outages rarely come from a single broken component. They emerge from how services interact, through feedback loops, dependencies, and cascading effects. Mechanisms that improve reliability in normal conditions can unintentionally amplify problems when the system is already under stress, which is the feedback behavior Karl Johan &#197;str&#246;m and Richard Murray formalize in <em><a href="https://press.princeton.edu/books/hardcover/9780691193984/feedback-systems">Feedback Systems</a></em>.</p><h3>Retries multiply load under stress</h3><p>Retries improve success rates when failures are transient and capacity is available. Under degradation, retries become a multiplier, the dynamic Jeffrey Dean and Luis Andr&#233; Barroso describe in <a href="https://cacm.acm.org/research/the-tail-at-scale/">&#8220;The Tail at Scale&#8221;</a>.</p><div class="callout-block" data-callout="true"><p><em>A dependency slows &#8594; timeouts increase &#8594; retries surge &#8594; downstream work increases &#8594; queues grow &#8594; latency rises &#8594; more timeouts &#8594; more retries.</em></p></div><p>This is how a minor regression becomes a self-inflicted flood. It can resemble a resource exhaustion event even when there is no attacker. The mechanism is simple. Unbounded retries consume the remaining capacity of an already-constrained dependency. The root issue is not that retries are bad. The issue is that unbounded retries are a powerful actuator that must be governed by system-level constraints such as SLO, cost, and blast radius.</p><h3>Autoscaling is a blunt actuator fed by ambiguous signals</h3><p>Autoscaling adds or removes capacity based on signals like CPU usage, request rate, or queue length. During incidents those signals mislead. Queue depth may grow because a dependency is slow, not because demand has grown. CPU may spike from retries and timeouts, not real workload. Request rates may rise simply because retried calls look like new traffic. A reactive autoscaler then adds capacity in the wrong place, driving up cost while putting even more pressure on the real bottleneck. The result is a system that becomes less stable and more expensive at exactly the moment it needs to recover.</p><h3>Circuit breakers carry shock-wave potential</h3><p>Circuit breakers are necessary. Without system-level coordination they create abrupt traffic shocks. If multiple clients trip simultaneously, alternate paths overload and create secondary failures that appear unrelated to the original degradation.</p><h3>The common pattern is unbounded actuation in a coupled system</h3><p>Retries, autoscaling, and circuit breakers are not mistakes. They are essential reliability tools. Problems arise when they operate like independent reflexes, without coordination or limits. When nothing controls how often they act, how broadly their actions affect the system, how easily changes can be reversed, or how clearly decisions can be explained, they unintentionally amplify failures. Managing this was already difficult with manually written rules. It becomes even more critical when AI agents make or trigger operational decisions.</p><h2>Why agentic operations change the physics</h2><p>Traditional automation such as scripts, thresholds, and fixed policies is limited in scope and relatively legible. Agentic systems expand both capability and risk because they tend to:</p><ol><li><p><strong>Increase velocity:</strong> propose actions faster than humans can validate non-local effects.</p></li><li><p><strong>Increase scope:</strong> coordinate actions across services, clusters, and regions.</p></li><li><p><strong>Optimize proxies:</strong> Charles Goodhart warned in 1975 that a statistical regularity tends to collapse once it is used as a control target, a caution later popularized as &#8220;when a measure becomes a target, it ceases to be a good measure&#8221; (<a href="https://www.semanticscholar.org/paper/Problems-of-Monetary-Management:-The-UK-Experience-Goodhart/0ae623749b30de53a39cf05813f5f3842e422c01">Problems of Monetary Management: The U.K. Experience</a>). Optimizing latency or cost in isolation can degrade reliability unless constraints are explicit.</p></li><li><p><strong>Operate under uncertainty: </strong>incidents produce ambiguous signals, and partial telemetry invites misdiagnosis and overcorrection.</p></li><li><p>Reduce legibility unless designed otherwise: without decision traces, you cannot reconstruct why the system acted or demonstrate compliance.</p></li></ol><p>So the risk is not AI in isolation. The risk is unbounded actuation under uncertainty. ARG is the discipline designed to make agentic operations governable.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Packt Deep Engineering! Subscribe for free to receive new posts and support out work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h2>A practical lens on amplification</h2><p>To manage automation safely, teams need a clear way to recognize when helpful mechanisms start making things worse. Amplification is that lens. A reliability mechanism helps when it improves outcomes without adding too much extra load. It becomes harmful when it multiplies load, volatility, or system-wide effects beyond what the platform can handle. In practice, retries multiply request volume, autoscaling multiplies capacity changes and cost, traffic shifts increase pressure on dependencies, and aggressive fixes create configuration churn. ARG is essentially the discipline of keeping these amplification effects under control.</p><h2>Autonomic Reliability Governance governs the automation itself</h2><p>The notion that systems can manage themselves is not new. Jeffrey Kephart and David Chess articulated that aspiration more than two decades ago in <a href="https://doi.org/10.1109/MC.2003.1160055">&#8220;The Vision of Autonomic Computing&#8221;</a>. What is new is the velocity and scope of actuation introduced by agentic AI. Autonomic Reliability Governance is the discipline of designing and operating bounded, auditable, and reversible automation, including agentic operational decision layers, under explicit constraints.</p><ul><li><p><strong>SLO constraints:</strong> latency ceilings, availability targets, error budgets.</p></li><li><p><strong>Cost constraints:</strong> budget caps, unit-economics bounds, runaway scaling prevention.</p></li><li><p><strong>Compliance constraints:</strong> auditability of decisions and actions.</p></li></ul><p>ARG is not a product. It is an operating model, automation you can trust because it is governable. ARG treats operational actuation, human or agentic, as a first-class risk surface with explicit safety envelopes.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mmod!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5559edb9-85f4-40db-ba37-32e8bc34cef5_960x588.svg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mmod!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5559edb9-85f4-40db-ba37-32e8bc34cef5_960x588.svg 424w, https://substackcdn.com/image/fetch/$s_!mmod!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5559edb9-85f4-40db-ba37-32e8bc34cef5_960x588.svg 848w, https://substackcdn.com/image/fetch/$s_!mmod!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5559edb9-85f4-40db-ba37-32e8bc34cef5_960x588.svg 1272w, https://substackcdn.com/image/fetch/$s_!mmod!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5559edb9-85f4-40db-ba37-32e8bc34cef5_960x588.svg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mmod!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5559edb9-85f4-40db-ba37-32e8bc34cef5_960x588.svg" width="728" height="446" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5559edb9-85f4-40db-ba37-32e8bc34cef5_960x588.svg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:892,&quot;width&quot;:1456,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:4364,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/svg+xml&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.net/i/204644229?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5559edb9-85f4-40db-ba37-32e8bc34cef5_960x588.svg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mmod!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5559edb9-85f4-40db-ba37-32e8bc34cef5_960x588.svg 424w, https://substackcdn.com/image/fetch/$s_!mmod!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5559edb9-85f4-40db-ba37-32e8bc34cef5_960x588.svg 848w, https://substackcdn.com/image/fetch/$s_!mmod!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5559edb9-85f4-40db-ba37-32e8bc34cef5_960x588.svg 1272w, https://substackcdn.com/image/fetch/$s_!mmod!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5559edb9-85f4-40db-ba37-32e8bc34cef5_960x588.svg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>Figure 1. The ARG stack. Observe to Decide to Act, with Audit and Reversibility underneath.</em></figcaption></figure></div><h2>Governance primitives that make agentic operations safe</h2><p>ARG becomes real when it is implemented through governance primitives that align with existing reliability practice such as SLOs, error budgets, and progressive rollout, while controlling actuation velocity and blast radius.</p><p><strong>1) Actuation budgets rate-limit change, not only traffic</strong></p><p>Most systems rate-limit user requests. Few rate-limit operational change. ARG introduces actuation budgets, limits on how frequently high-impact actions can occur. Scaling shifts, retry escalations, routing changes, and configuration flips consume tokens from a constrained budget. When the budget is exhausted, automation may still observe and recommend, but it cannot repeatedly perturb the system. When instability rises, slow the actuators before adding more intelligence.</p><p><strong>2) Blast radius governance scopes every action</strong></p><p>The difference between a safe fix and an outage multiplier is often scope. ARG constrains blast radius using canaries, segmentation across cells and regions, tiered permissions, and scoped rollouts with automatic halt conditions. Even correct actions can be wrong if applied globally in one step. Autonomy must always be localized before it is generalized.</p><p><strong>3) Advisory-first agents and progressive autonomy</strong></p><p>In high-stakes systems, autonomy should begin as advisory. Agents recommend and humans approve. As evidence accumulates and safety is demonstrated, constrained autonomy can expand within defined envelopes.</p><p><strong>4) Eligibility tiers put permission models over personality</strong></p><p>Autonomy must be governed by explicit action tiers:</p><ul><li><p><strong>Observational: </strong>detection and diagnosis, always allowed.</p></li><li><p><strong>Low-risk reversible actions:</strong> scoped and rollback-capable.</p></li><li><p><strong>Medium-risk actions:</strong> require high confidence and strict guardrails.</p></li><li><p><strong>High-risk actions:</strong> require human approval.</p></li></ul><p>Autonomy becomes measurable when agents must graduate across tiers based on demonstrated safety.</p><p><strong>5) Confidence gates and evidence before execution</strong></p><p>Agents should act only when evidence crosses defined thresholds:</p><ul><li><p>sufficient telemetry completeness</p></li><li><p>credible dependency attribution</p></li><li><p>system stability signals</p></li><li><p>reversibility guarantees</p></li><li><p>bounded failure domain</p></li></ul><p>If uncertainty is high, remain advisory.</p><p><strong>6) Auditability makes the decision trace a first-class artifact</strong></p><p>Governed autonomy requires structured decision records:</p><ul><li><p>what was observed</p></li><li><p>what was concluded, with uncertainty</p></li><li><p>what constraints were evaluated</p></li><li><p>what action was taken or recommended</p></li><li><p>what was expected</p></li><li><p>what occurred</p></li><li><p>how it can be reversed</p></li></ul><p>Auditability is not bureaucracy. It is what enables learning, compliance, and safe expansion of autonomy.</p><p><strong>7) Reversibility means designing for wrong decisions</strong></p><p>Every operational action must have an undo path. Rollbacks, kill switches, and safe defaults are mandatory. Governance does not prevent all mistakes. It ensures failures stay bounded and recoverable.</p><h2>Validating governance without private company data</h2><p>ARG also needs ways to test ideas without using private company data. One practical approach is incident replay. You capture common failure patterns such as latency spikes, growing queues, retry storms, or autoscaling reactions, and replay them in a simulation or controlled environment. Then you compare how the system behaves under unbounded automation versus governed policies, and you check whether the changes reduce amplification or make it worse. The goal is not to perfectly recreate production, but to learn in a measurable way, so the question is whether a policy dampens instability or accelerates it. This can be reinforced with controlled stability drills such as dependency injection, brownouts, or chaos experiments, the practice Ali Basiri and colleagues describe in <a href="https://doi.org/10.1109/MS.2016.60">&#8220;Chaos Engineering&#8221;</a> (IEEE Software, May 2016), so that automation stays controlled when conditions degrade.</p><h2>What changes when ARG becomes normal practice</h2><p>As systems become more autonomous, reliability work will increasingly focus on governing automation itself. That means turning constraints into enforceable policies, limiting and scoping automated actions, treating audit trails as essential system outputs, and introducing autonomy gradually rather than all at once. FinTech-scale platforms are likely to adopt this approach early because reliability targets, cost limits, and audit requirements are strict, and failures are expensive. In the end, agentic operations will not succeed simply by becoming more intelligent. They will succeed by becoming more governable.</p><h2>Do not automate reliability, govern automation</h2><p>Retries and autoscaling will remain foundational, and AI will increasingly enter operations. The question is whether these mechanisms amplify failures or dampen them. If we bolt AI onto production as an unbounded actuator, we create faster cascades and more expensive incidents. If we treat autonomy as a governed system that is bounded, auditable, and reversible, we can build platforms that are both resilient and efficient. Autonomic Reliability Governance is a practical blueprint for getting there.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Packt Deep Engineering! Subscribe for free to receive new posts and support our work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h4><strong>Acknowledgements and disclosures by the Author</strong></h4><p><strong>Submitted</strong> by <a href="https://www.linkedin.com/in/sibasis-padhi">Sibasis Padhi</a>.</p><p>Generative AI tools and Grammarly were used to assist with drafting and English corrections. The author reviewed, revised, and assumes full responsibility for the final content.</p><h4>About Sibasis</h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.linkedin.com/in/sibasis-padhi" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NsjM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9db5872-091d-440c-a87e-64756030dda7_800x800.jpeg 424w, https://substackcdn.com/image/fetch/$s_!NsjM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9db5872-091d-440c-a87e-64756030dda7_800x800.jpeg 848w, https://substackcdn.com/image/fetch/$s_!NsjM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9db5872-091d-440c-a87e-64756030dda7_800x800.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!NsjM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9db5872-091d-440c-a87e-64756030dda7_800x800.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NsjM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9db5872-091d-440c-a87e-64756030dda7_800x800.jpeg" width="245" height="245" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b9db5872-091d-440c-a87e-64756030dda7_800x800.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:800,&quot;width&quot;:800,&quot;resizeWidth&quot;:245,&quot;bytes&quot;:92024,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://www.linkedin.com/in/sibasis-padhi&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.net/i/204644229?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9db5872-091d-440c-a87e-64756030dda7_800x800.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NsjM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9db5872-091d-440c-a87e-64756030dda7_800x800.jpeg 424w, https://substackcdn.com/image/fetch/$s_!NsjM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9db5872-091d-440c-a87e-64756030dda7_800x800.jpeg 848w, https://substackcdn.com/image/fetch/$s_!NsjM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9db5872-091d-440c-a87e-64756030dda7_800x800.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!NsjM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9db5872-091d-440c-a87e-64756030dda7_800x800.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><a href="https://www.linkedin.com/in/sibasis-padhi">Sibasis Padhi</a> is a Staff Software Engineer at <a href="https://www.linkedin.com/company/walmartglobaltech">Walmart Global Tech</a> with more than 18 years building and optimizing large-scale enterprise systems, including cloud-native financial platforms that handle high-volume transactions. His work centers on microservices performance, observability, distributed systems reliability, and agentic AI. He speaks at IEEE conferences and industry events on resilient financial systems and agentic operations.</p>]]></content:encoded></item><item><title><![CDATA[From NIC to P99: Engineering Low-Latency C++ Trading Systems in 2026]]></title><description><![CDATA[A practical look at the hardware, OS, and code-level optimizations you need to stop burning your latency budget.]]></description><link>https://deepengineering.net/p/from-nic-to-p99-engineering-low-latency</link><guid isPermaLink="false">https://deepengineering.net/p/from-nic-to-p99-engineering-low-latency</guid><dc:creator><![CDATA[Shreyans]]></dc:creator><pubDate>Wed, 04 Mar 2026 08:19:17 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b449d007-c2e0-4447-a359-fffc7681e42b_1792x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Trading systems have been <a href="https://www.globaltrading.net/the-need-for-speed/">evolving for decades</a>, moving from manual execution based on charts and indicators to automated strategies that fire within tens of nanoseconds using FPGA and ASIC hardware. Both approaches still coexist today, but most advanced and systematic trading has moved into the sub-100 microsecond range. In this article I want to focus on the hardware and software aspects you need to understand in order to build a trading system with p95 wire-to-wire latency under 50 microseconds.</p><p>Traditional technology stacks like <a href="https://www.linkedin.com/pulse/comparing-rust-c-python-java-go-typescriptnodejs-hft-trading-souza-nxlkf/">Java, JavaScript, TypeScript, and C# offer a lot of flexibility</a> and scalability for large-scale applications, but their inherently nondeterministic nature makes it very difficult to get below a p50 latency of 500 microseconds. While there are ways to optimize garbage collection and other Java components for latency, most standard Java libraries do not focus on low-level concepts like aligning data to cache lines or reducing cache line misses. C and C++ applications optimize for a specific platform at the cost of portability, but that trade-off is exactly what gives you access to manual memory management and the ability to avoid multithreading pitfalls like false sharing. When the goal is to reduce latency, you need lower-level languages like C, C++, or Rust on critical paths. I will not cover pure hardware implementations like FPGAs or ASICs here since they are not required for our 50-microsecond latency target.</p><h2>Important Definitions</h2><p>Before diving into the architecture and hardware optimizations, it will be helpful to establish exactly how performance is measured in a low-latency environment.</p><h3>Wire-to-wire latency</h3><p>Wire-to-wire latency is measured from the moment a market data packet is received on the NIC from the exchange to the moment an order is sent back to the exchange on the same NIC. You compute it as order send timestamp minus market data receive timestamp. The most precise measurements require NIC cards with hardware timestamping enabled, so the NIC stamps every inbound and outbound packet with a hardware timestamp that you can later use to analyze the latency graph. Exanic cards provide APIs for retrieving these hardware timestamps.</p><p><strong>Assumptions About the System</strong></p><p>To hit the latency target of under 50 microseconds, you need to optimize both hardware and server setup alongside your software, and the following assumptions underpin everything in this article.</p><ul><li><p><strong>Colocated host:</strong> Most stock exchanges in traditional finance provide colocation services where your servers sit physically adjacent to the exchange&#8217;s servers, removing extra network jitter from the equation.</p></li><li><p><strong>UDP market data access:</strong> Most stock exchanges distribute market data via UDP multicast to all subscribed parties, and this is the fastest way to consume and react to it.</p></li><li><p><strong>Kernel bypass NIC:</strong> The system runs on NIC cards enabled for kernel bypass and tuned for low-latency operation.</p></li><li><p><strong>Single venue:</strong> These systems are single-venue, meaning at any point they process market data to generate orders for one stock exchange only. For multi-venue setups, the assumption is one colocated host per venue with no shared critical-path processing.</p></li></ul><h3>Workload Model</h3><p>Most of the trading systems follow this model of data processing in abstract terms:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_1_W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3e071ec-b507-4278-a874-052d6e5cd7c1_1024x559.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_1_W!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3e071ec-b507-4278-a874-052d6e5cd7c1_1024x559.png 424w, https://substackcdn.com/image/fetch/$s_!_1_W!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3e071ec-b507-4278-a874-052d6e5cd7c1_1024x559.png 848w, https://substackcdn.com/image/fetch/$s_!_1_W!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3e071ec-b507-4278-a874-052d6e5cd7c1_1024x559.png 1272w, https://substackcdn.com/image/fetch/$s_!_1_W!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3e071ec-b507-4278-a874-052d6e5cd7c1_1024x559.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_1_W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3e071ec-b507-4278-a874-052d6e5cd7c1_1024x559.png" width="1024" height="559" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d3e071ec-b507-4278-a874-052d6e5cd7c1_1024x559.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:559,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_1_W!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3e071ec-b507-4278-a874-052d6e5cd7c1_1024x559.png 424w, https://substackcdn.com/image/fetch/$s_!_1_W!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3e071ec-b507-4278-a874-052d6e5cd7c1_1024x559.png 848w, https://substackcdn.com/image/fetch/$s_!_1_W!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3e071ec-b507-4278-a874-052d6e5cd7c1_1024x559.png 1272w, https://substackcdn.com/image/fetch/$s_!_1_W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3e071ec-b507-4278-a874-052d6e5cd7c1_1024x559.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Every strategy engine takes market data from the exchange as its primary input and produces orders sent to the exchange as its primary output, moving through multiple stages as described below. Exchange responses are a secondary input to the engine but are not on the hot path for latency calculations.</p><h3>Market Data Ingestion</h3><p>The first layer is responsible for ingesting the market data (raw or normalized) from the network card. This layer routinely processes terabytes of data per day and can exceed a petabyte on busy days at venues like <a href="https://finance.yahoo.com/news/cme-group-international-average-daily-080000527.html">CME</a> or <a href="https://www.eurex.com/ex-en/data/statistics/monthly-statistics">Eurex</a>. On an average day at CME you can expect 20 to 30 million trades and 100 to 150 million open interest events in a single segment. Processing a full L1 feed for one segment means receiving traffic in the range of a few terabytes to hundreds of terabytes per day, so both software and hardware need to be extremely optimized.</p><h3>Filtering and Checks</h3><p>Filtering and checks come next, cleaning up incoming data and validating it before it reaches downstream stages. This layer handles packet drops, invalid market data, trading phase transitions like moving from an active session to a halted one, and anomalous price changes that might indicate a software bug. When something goes wrong at this stage, the right response is often to instruct strategies to stop rather than pass bad data forward.</p><h3>Pricing Engine</h3><p>The pricing engine receives valid and normalized market data and can range from a simple no-op or currency conversion all the way to a full ML-powered model. Lightweight pricing fits comfortably on the critical path. Complex ML models that require large amounts of memory and processing do not, because they would blow the latency budget. In practice, ML-powered engines in low-latency applications split into two processes: time-consuming training and inference model preparation happen off the critical path, and a lightweight inference step runs on the critical path using small linearized models fed configuration parameters from the heavier ML output. This keeps the heavy lifting off the hot path while still giving the strategy access to model-driven signals. Colocation also constrains available compute, so you want to reserve it for work that directly impacts critical path performance.</p><h3>Strategy Evaluation</h3><p>Strategy evaluation compares transformed or theoretical prices against real prices and fires an order when it identifies an opportunity. Strategies generally fall into three categories.</p><ul><li><p><strong>Maker:</strong> Maker strategies keep quotes on both the buy and sell sides to provide liquidity and take advantage of the bid-ask spread. Exchanges often provide incentives to HFT firms that add liquidity this way.</p></li><li><p><strong>Taker:</strong> Taker strategies identify mispricing and fire IOC or FOK orders for immediate execution.</p></li><li><p><strong>Hedging:</strong> Hedging strategies reduce risk or book profit by executing the opposite side of an existing position.</p></li></ul><h3><strong>Order Aggregation and Routing (OMS)</strong></h3><p>Order aggregation and routing (OMS) can be combined with strategy evaluation in simple setups, but should be separated as complexity grows. It serves three purposes: aggregating orders from multiple strategies to reduce the number of orders sent to the exchange, managing exchange responses and passing normalized results back to the strategy, and handling the routing logic that grows more complex as your strategy count increases.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Deep Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Latency Map: Where the Latency Lies</h2><p>Now that the high-level components are clear, it is worth mapping out where the latency actually sits so you can allocate your budget with precision. Beyond the business logic itself, your latency profile depends on CPU model and generation, NIC type and model, operating system, software architecture, and business logic complexity.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1YAa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b2ef2c-ba7d-4409-9393-d96d2b5abbee_859x293.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1YAa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b2ef2c-ba7d-4409-9393-d96d2b5abbee_859x293.png 424w, https://substackcdn.com/image/fetch/$s_!1YAa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b2ef2c-ba7d-4409-9393-d96d2b5abbee_859x293.png 848w, https://substackcdn.com/image/fetch/$s_!1YAa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b2ef2c-ba7d-4409-9393-d96d2b5abbee_859x293.png 1272w, https://substackcdn.com/image/fetch/$s_!1YAa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b2ef2c-ba7d-4409-9393-d96d2b5abbee_859x293.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1YAa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b2ef2c-ba7d-4409-9393-d96d2b5abbee_859x293.png" width="724" height="246.9522700814901" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f2b2ef2c-ba7d-4409-9393-d96d2b5abbee_859x293.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:293,&quot;width&quot;:859,&quot;resizeWidth&quot;:724,&quot;bytes&quot;:46376,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/189117004?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b2ef2c-ba7d-4409-9393-d96d2b5abbee_859x293.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1YAa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b2ef2c-ba7d-4409-9393-d96d2b5abbee_859x293.png 424w, https://substackcdn.com/image/fetch/$s_!1YAa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b2ef2c-ba7d-4409-9393-d96d2b5abbee_859x293.png 848w, https://substackcdn.com/image/fetch/$s_!1YAa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b2ef2c-ba7d-4409-9393-d96d2b5abbee_859x293.png 1272w, https://substackcdn.com/image/fetch/$s_!1YAa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b2ef2c-ba7d-4409-9393-d96d2b5abbee_859x293.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Beyond the business logic stages, several factors contribute latency that stays invisible if you only look at application code. These factors come from hardware, OS and kernel processing, network behavior, and I/O, and they play an outsized role in ultra-low-latency systems. Even if they occur infrequently, they introduce jitter and latency spikes that directly hurt your p99. Although many of these latency sources depend on a multitude of factors, you can work with approximate cost ranges that provide a useful benchmark, and the table below gives those ranges for the most common sources.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_K9p!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4620df02-490f-4432-9b8f-d5382030692a_610x269.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_K9p!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4620df02-490f-4432-9b8f-d5382030692a_610x269.png 424w, https://substackcdn.com/image/fetch/$s_!_K9p!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4620df02-490f-4432-9b8f-d5382030692a_610x269.png 848w, https://substackcdn.com/image/fetch/$s_!_K9p!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4620df02-490f-4432-9b8f-d5382030692a_610x269.png 1272w, https://substackcdn.com/image/fetch/$s_!_K9p!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4620df02-490f-4432-9b8f-d5382030692a_610x269.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_K9p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4620df02-490f-4432-9b8f-d5382030692a_610x269.png" width="594" height="261.94426229508196" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4620df02-490f-4432-9b8f-d5382030692a_610x269.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:269,&quot;width&quot;:610,&quot;resizeWidth&quot;:594,&quot;bytes&quot;:38686,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/189117004?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4620df02-490f-4432-9b8f-d5382030692a_610x269.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_K9p!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4620df02-490f-4432-9b8f-d5382030692a_610x269.png 424w, https://substackcdn.com/image/fetch/$s_!_K9p!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4620df02-490f-4432-9b8f-d5382030692a_610x269.png 848w, https://substackcdn.com/image/fetch/$s_!_K9p!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4620df02-490f-4432-9b8f-d5382030692a_610x269.png 1272w, https://substackcdn.com/image/fetch/$s_!_K9p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4620df02-490f-4432-9b8f-d5382030692a_610x269.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Engineering Pillars</strong></h2><p>Addressing these latency sources requires a set of engineering decisions baked into the application design itself, not patched in afterwards. The pillars below cover the most impactful areas.</p><h3><strong>Kernel Bypass</strong></h3><p>Traditionally, any application reading from the network goes through the kernel as an intermediary, which means a system call copies data from the NIC into kernel space before the application can access it by copying from kernel space into its own buffers. That double-copy plus the system call overhead adds up quickly because control needs to transfer to and from the kernel on every read, and the way to avoid it is to access the NIC directly through its driver. DPDK polling mode is one of the best approaches for this and can save roughly 20 to 45 microseconds when tuned correctly for market data processing.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MsWe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa77aece3-a437-4bd2-bd1a-e5948d723152_535x119.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MsWe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa77aece3-a437-4bd2-bd1a-e5948d723152_535x119.png 424w, https://substackcdn.com/image/fetch/$s_!MsWe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa77aece3-a437-4bd2-bd1a-e5948d723152_535x119.png 848w, https://substackcdn.com/image/fetch/$s_!MsWe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa77aece3-a437-4bd2-bd1a-e5948d723152_535x119.png 1272w, https://substackcdn.com/image/fetch/$s_!MsWe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa77aece3-a437-4bd2-bd1a-e5948d723152_535x119.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MsWe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa77aece3-a437-4bd2-bd1a-e5948d723152_535x119.png" width="622" height="138.35140186915888" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a77aece3-a437-4bd2-bd1a-e5948d723152_535x119.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:119,&quot;width&quot;:535,&quot;resizeWidth&quot;:622,&quot;bytes&quot;:16773,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/189117004?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa77aece3-a437-4bd2-bd1a-e5948d723152_535x119.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MsWe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa77aece3-a437-4bd2-bd1a-e5948d723152_535x119.png 424w, https://substackcdn.com/image/fetch/$s_!MsWe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa77aece3-a437-4bd2-bd1a-e5948d723152_535x119.png 848w, https://substackcdn.com/image/fetch/$s_!MsWe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa77aece3-a437-4bd2-bd1a-e5948d723152_535x119.png 1272w, https://substackcdn.com/image/fetch/$s_!MsWe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa77aece3-a437-4bd2-bd1a-e5948d723152_535x119.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Specialized hardware is also worth considering for additional gains on top of software-level kernel bypass. Exanic and Solarflare cards are widely used in the trading industry and provide zero-copy data access, hardware filtering, hardware timestamps, and flexible flow steering.</p><h3>Memory Management</h3><p>Most shared or larger resources get allocated on the heap through dynamic allocation using malloc or new, which requires a system call to expand the heap and introduces additional latency. Low-latency systems avoid this by pre-allocating memory segments at startup and building custom allocators on top of those segments to distribute memory during runtime, with the primary goal of eliminating all system calls from the hot path entirely. At startup you make one large allocation to cover the maximum memory any component might need, and during trading hours your custom allocator distributes from that pool without ever touching the OS. Most large-scale systems package this into a library of containers, allocators, and pools that lets you use vectors, maps, and arrays without the penalty of dynamic allocation by implementing custom versions of these types or custom allocators.</p><p>In practice it is always worth benchmarking different allocators to find the one that performs best for your specific workload, since allocators have different configuration parameters that trade off between latency, throughput, and fragmentation.</p><h3>CPU and NUMA Affinity</h3><p>Modern CPUs use NUMA architecture, dividing processors into nodes where each node has its own processors, caches, and memory, and communication between nodes happens over interconnects that add measurable latency when crossed on a hot path. For critical processes, you want to pin threads to cores within the same NUMA node and ensure that the data those threads access is also allocated on the same node. Non-critical processes like logging, compliance reporting, and observability can run on a separate NUMA node since they care more about throughput than latency.</p><p>An example core allocation for a machine with 8 cores per NUMA node and 2 NUMA nodes might look like this.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8rk_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6687b47-0f02-4f37-a9a3-141091ada387_596x232.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8rk_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6687b47-0f02-4f37-a9a3-141091ada387_596x232.png 424w, https://substackcdn.com/image/fetch/$s_!8rk_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6687b47-0f02-4f37-a9a3-141091ada387_596x232.png 848w, https://substackcdn.com/image/fetch/$s_!8rk_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6687b47-0f02-4f37-a9a3-141091ada387_596x232.png 1272w, https://substackcdn.com/image/fetch/$s_!8rk_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6687b47-0f02-4f37-a9a3-141091ada387_596x232.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8rk_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6687b47-0f02-4f37-a9a3-141091ada387_596x232.png" width="634" height="246.79194630872485" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f6687b47-0f02-4f37-a9a3-141091ada387_596x232.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:232,&quot;width&quot;:596,&quot;resizeWidth&quot;:634,&quot;bytes&quot;:22561,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/189117004?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6687b47-0f02-4f37-a9a3-141091ada387_596x232.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8rk_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6687b47-0f02-4f37-a9a3-141091ada387_596x232.png 424w, https://substackcdn.com/image/fetch/$s_!8rk_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6687b47-0f02-4f37-a9a3-141091ada387_596x232.png 848w, https://substackcdn.com/image/fetch/$s_!8rk_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6687b47-0f02-4f37-a9a3-141091ada387_596x232.png 1272w, https://substackcdn.com/image/fetch/$s_!8rk_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6687b47-0f02-4f37-a9a3-141091ada387_596x232.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>This layout lets you run two independent sets of strategies, one per NUMA node, while keeping logging and observability isolated from the critical path.</p><h3><strong>Time Sources and Clocks</strong></h3><p>Getting timestamps right matters a lot more in low-latency systems than in most applications because you are measuring at nanosecond granularity, and the table below covers the main options along with their trade-offs. For cross-host applications, you also need a way to synchronize time across multiple machines at nanosecond precision &#8212; PTP hardware clocks are the standard approach for this.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZmcG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b0b871c-fb7a-4b5a-981b-9f67b63ede0b_658x197.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZmcG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b0b871c-fb7a-4b5a-981b-9f67b63ede0b_658x197.png 424w, https://substackcdn.com/image/fetch/$s_!ZmcG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b0b871c-fb7a-4b5a-981b-9f67b63ede0b_658x197.png 848w, https://substackcdn.com/image/fetch/$s_!ZmcG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b0b871c-fb7a-4b5a-981b-9f67b63ede0b_658x197.png 1272w, https://substackcdn.com/image/fetch/$s_!ZmcG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b0b871c-fb7a-4b5a-981b-9f67b63ede0b_658x197.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZmcG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b0b871c-fb7a-4b5a-981b-9f67b63ede0b_658x197.png" width="658" height="197" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5b0b871c-fb7a-4b5a-981b-9f67b63ede0b_658x197.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:197,&quot;width&quot;:658,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:29226,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/189117004?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b0b871c-fb7a-4b5a-981b-9f67b63ede0b_658x197.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZmcG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b0b871c-fb7a-4b5a-981b-9f67b63ede0b_658x197.png 424w, https://substackcdn.com/image/fetch/$s_!ZmcG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b0b871c-fb7a-4b5a-981b-9f67b63ede0b_658x197.png 848w, https://substackcdn.com/image/fetch/$s_!ZmcG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b0b871c-fb7a-4b5a-981b-9f67b63ede0b_658x197.png 1272w, https://substackcdn.com/image/fetch/$s_!ZmcG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b0b871c-fb7a-4b5a-981b-9f67b63ede0b_658x197.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h3>Network Tuning</h3><p>Sockets and hardware both need tuning to get the best latency and throughput out of your setup. The key adjustments are disabling interrupts and relying on polling to avoid breaking application flow, increasing the RX buffer size to reduce packet loss, and increasing the TX buffer backlog queue size, with many other hardware-specific options available depending on your configuration.</p><h3>Observability and Monitoring</h3><p>Monitoring is not optional in low-latency systems because without good observability you cannot see where latency is being spent. The key metrics to track are first-order-out latency for both software and wire-to-wire paths, p50, p95, p99, and p99.9 latencies at both levels, syscall latencies using bcc and bpftrace, CPU utilization to find headroom and spot contention, and NIC-level metrics using tools appropriate to your hardware.</p><h4>Failure Modes</h4><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!a5tZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52c5417b-63fb-4c3a-861c-dfd941e8c2a1_948x230.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!a5tZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52c5417b-63fb-4c3a-861c-dfd941e8c2a1_948x230.png 424w, https://substackcdn.com/image/fetch/$s_!a5tZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52c5417b-63fb-4c3a-861c-dfd941e8c2a1_948x230.png 848w, https://substackcdn.com/image/fetch/$s_!a5tZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52c5417b-63fb-4c3a-861c-dfd941e8c2a1_948x230.png 1272w, https://substackcdn.com/image/fetch/$s_!a5tZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52c5417b-63fb-4c3a-861c-dfd941e8c2a1_948x230.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!a5tZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52c5417b-63fb-4c3a-861c-dfd941e8c2a1_948x230.png" width="678" height="164.49367088607596" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/52c5417b-63fb-4c3a-861c-dfd941e8c2a1_948x230.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:230,&quot;width&quot;:948,&quot;resizeWidth&quot;:678,&quot;bytes&quot;:47698,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/189117004?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52c5417b-63fb-4c3a-861c-dfd941e8c2a1_948x230.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!a5tZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52c5417b-63fb-4c3a-861c-dfd941e8c2a1_948x230.png 424w, https://substackcdn.com/image/fetch/$s_!a5tZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52c5417b-63fb-4c3a-861c-dfd941e8c2a1_948x230.png 848w, https://substackcdn.com/image/fetch/$s_!a5tZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52c5417b-63fb-4c3a-861c-dfd941e8c2a1_948x230.png 1272w, https://substackcdn.com/image/fetch/$s_!a5tZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52c5417b-63fb-4c3a-861c-dfd941e8c2a1_948x230.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><h2>Deployment Checklist</h2><p>The hardware tuning techniques below are commonly used across trading organizations, but because you are tuning at the hardware level the right set of parameters depends on your specific hardware. These techniques also carry trade-offs like higher power consumption and increased CPU heat, so test carefully and find the optimal configuration for your application before applying them in production.</p><h3>Hardware and BIOS</h3><ul><li><p>Disable C-states and Turbo Boost</p></li><li><p>Set CPU governor to performance mode</p></li><li><p>Lock PCIe ASPM off</p></li><li><p>Enable hugepages</p></li></ul><h3>OS Tuning</h3><ul><li><p>Use isolcpus, nohz_full, and rcu_nocbs kernel parameters</p></li><li><p>Apply sysctl buffer tuning</p></li><li><p>Use mlockall to pin memory and prevent paging</p></li></ul><h3>Process and Code</h3><ul><li><p>Use static preallocation and avoid dynamic containers on the hot path</p></li><li><p>Prefetch and cache-align structs</p></li><li><p>Use lock-free SPSC queues</p></li><li><p>Use TSC for all timing on the critical path</p></li></ul><h2>Common Pitfalls</h2><ul><li><p><strong>False sharing between threads on the same cache line: </strong>This is one of the most common mistakes in multithreaded systems. When two threads write to variables that share a cache line, even if those variables are logically unrelated, the cache coherency protocol treats the entire line as contested and forces unnecessary cross-core coordination. Many cache miss problems in multithreaded environments trace back to this.</p></li><li><p><strong>Chatty IPC over sockets or message queues:</strong> Chatty IPC adds up quickly. Many distributed systems default to message queues, RPCs, or other message-passing mechanisms, but these carry much higher latency in practice even when they scale better horizontally. For low-latency IPC between processes on the same host, shared memory is the right approach.</p></li><li><p><strong>Premature SIMD optimization:</strong> Premature SIMD can actually increase latency when your workload is memory-bound rather than compute-bound. SIMD brings real benefits in the right situations but also adds complexity. Always benchmark before and after adding vectorization because the performance impact can go in either direction depending on your memory access pattern.</p></li><li><p><strong>TSC skew across cores:</strong> TSC skew is a problem that bites HFT systems specifically because they measure at nanosecond granularity. TSC is the standard clock for critical path timing because it does not require a system call, but TSC can drift across cores on the same CPU. Left uncorrected, that skew introduces measurement errors that look like real latency spikes and can obscure genuine performance bugs. Check your CPU documentation for the recommended synchronization approach and apply it regularly.</p></li><li><p><strong>Running without baseline histograms:</strong> Running without baseline histograms makes all of the above invisible. Every parameter including compiler settings, execution environment, hardware, and network can affect latency, and changing a single line of code can produce dramatically different results. Measuring p50 and p99 histograms at every stage of the pipeline is the only reliable way to know where your latency is coming from.</p></li></ul><h3>Further reading /Additional resources</h3><ol><li><p><a href="https://talawah.io/blog/linux-kernel-vs-dpdk-http-performance-showdown/">Linux kernel vs DPDK</a></p></li><li><p>AF_XDP: Zero-Copy Packet Processing in Linux &#8212; Netdev 2024:  </p></li></ol><div id="youtube2-cSdQIISFx08" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;cSdQIISFx08&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/cSdQIISFx08?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><ol start="3"><li><p><a href="https://dev.to/frosnerd/libmalloc-jemalloc-tcmalloc-mimalloc-exploring-different-memory-allocators-4lp3">Benchmarking allocators</a></p></li><li><p><a href="https://nordvarg.com/blog/time-synchronization-distributed-systems">Time Synchronization in Distributed Trading Systems</a></p></li><li><p><em><a href="https://api.pageplace.de/preview/DT0400.9780136624547_A41216512/preview-9780136624547_A41216512.pdf">BPF Performance Tools</a> </em>by Brendan Gregg</p></li><li><p><a href="https://www.intel.com/content/www/us/en/developer/articles/technical/optimizing-computer-applications-for-latency-part-1-configuring-the-hardware.html">Intel BIOS optimizations</a> </p></li><li><p><a href="https://access.redhat.com/sites/default/files/attachments/201501-perf-brief-low-latency-tuning-rhel7-v2.1.pdf">RHEL 7 low-latency tuning guide </a></p></li><li><p>IPC vs shared memory:</p></li></ol><div id="youtube2-LDHnBW2v_D4" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;LDHnBW2v_D4&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/LDHnBW2v_D4?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><ol start="9"><li><p>Incorrect use of SIMD:</p></li></ol><div id="youtube2-GleC3SZ8gjU" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;GleC3SZ8gjU&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/GleC3SZ8gjU?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div>]]></content:encoded></item><item><title><![CDATA[Coroutines]]></title><description><![CDATA[The complete Chapter 15: Coroutines from Kotlin for Java Developers by Jos&#233; Dimas Luj&#225;n Castillo and Ron Veen (Packt, 2025)]]></description><link>https://deepengineering.net/p/coroutines</link><guid isPermaLink="false">https://deepengineering.net/p/coroutines</guid><dc:creator><![CDATA[Ron veen]]></dc:creator><pubDate>Thu, 12 Feb 2026 09:04:47 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e24774ab-3f3e-4526-86a4-4de740c0206a_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this chapter, we will explore one of Kotlin&#8217;s most powerful and transformative features &#8211; <strong>coroutines</strong>. Designed to simplify and enhance asynchronous programming, coroutines allow developers to write non-blocking, concurrent code with ease and clarity. By offering a structured and intuitive approach to handling tasks such as API calls, database operations, and UI updates, coroutines have become a cornerstone for modern Kotlin development.</p><p>Unlike traditional approaches to asynchronous programming, such as threads and callbacks, coroutines eliminate complexity by providing a way to write asynchronous code that looks and behaves like synchronous code. This chapter introduces you to the fundamentals of coroutines, teaching you how to avoid issues with callbacks and enabling you to manage concurrency gracefully and efficiently.</p><p>Kotlin&#8217;s coroutines framework is built on top of lightweight threads, providing developers with the tools to execute tasks in parallel, handle timeouts and cancellations, and switch between different execution contexts. Throughout this chapter, we will uncover how coroutines work, why they are an improvement over traditional concurrency models, and how to leverage them to build robust and responsive applications.</p><p>We will start by understanding what coroutines are and how they differ from threads and other concurrency mechanisms. Next, we will dive into the implementation of coroutines, learning about coroutine builders such as <code>launch</code> and <code>async</code>, as well as the importance of suspending functions in asynchronous workflows. We will then explore strategies for managing timeouts and cancellations, ensuring our applications remain efficient and responsive under various conditions. Finally, we will tackle the advanced concepts of context and scope, which are essential for organizing and managing coroutine life cycles effectively.</p><p>By the end of this chapter, you will have a comprehensive understanding of Kotlin coroutines, enabling you to write clean, maintainable, and high-performing asynchronous code. Whether you&#8217;re developing Android applications, server-side services, or any system requiring concurrent operations, mastering coroutines will empower you to tackle complex challenges with confidence and precision.</p><p>We&#8217;re going to cover the following topics:</p><ul><li><p>What is a coroutine?</p></li><li><p>Coroutine implementation</p></li><li><p>Timeouts and cancellations</p></li><li><p>Contexts and scope</p></li></ul><div><hr></div><h1>Technical requirements</h1><p>You can use any text editor or <strong>Integrated Development Environment</strong> (<strong>IDE</strong>) of your choice with the Java language. We recommend IntelliJ IDEA <strong>Community Edition</strong> (<strong>CE</strong>). This tool already comes with everything you need to work with both Java and Kotlin. The software requirements for this chapter, with required versions, are as follows:</p><ul><li><p>Java JDK 17 or higher</p></li><li><p>IntelliJ IDEA CE</p></li><li><p>Kotlin 1.x or 2.x</p></li></ul><p>The code used in the chapters can be found in the book&#8217;s accompanying GitHub repository: <a href="https://github.com/PacktPublishing/Kotlin-for-Java-Developers">https://github.com/PacktPublishing/Kotlin-for-Java-Developers</a>.</p><div><hr></div><h1>What is a coroutine?</h1><p>To simplify what a coroutine is, let&#8217;s first look at the official definition: &#8220;<em>A coroutine is an instance of a suspendable computation.</em>&#8221; While technically accurate, it may seem too abstract. Instead, let&#8217;s break it down with a practical and relatable analogy.</p><p>Imagine you are dining at a restaurant. A waiter comes to your table, takes your food order, and heads to the kitchen to pass the order on to the chefs. Now, imagine the waiter stays in the kitchen, doing nothing but waiting for your food to be prepared. This would be inefficient, as the waiter could be serving drinks, attending to other tables, or bringing bread to your table while the food is being cooked.</p><p>A smarter approach is for the waiter to leave the instructions with the kitchen staff and return to attend to other customers or tasks. Once your food is ready, the kitchen would alert the waiter, who would promptly bring the dishes to your table.</p><p>This efficiency is exactly how <strong>coroutines</strong> operate. In programming, there&#8217;s a &#8220;main thread&#8221; (like the waiter), which handles primary tasks (like serving the diners in our analogy). However, certain tasks (such as cooking the food) can take an unknown amount of time. These tasks are better handled asynchronously, without blocking the main thread.</p><h2>The restaurant analogy</h2><p>To make the concept of coroutines easier to grasp, let&#8217;s stick with the restaurant analogy. This comparison helps illustrate how asynchronous tasks and concurrency work in Kotlin using coroutines:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Hqpe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5668a984-9948-4c92-ae31-49ada2017605_572x325.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Hqpe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5668a984-9948-4c92-ae31-49ada2017605_572x325.png 424w, https://substackcdn.com/image/fetch/$s_!Hqpe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5668a984-9948-4c92-ae31-49ada2017605_572x325.png 848w, https://substackcdn.com/image/fetch/$s_!Hqpe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5668a984-9948-4c92-ae31-49ada2017605_572x325.png 1272w, https://substackcdn.com/image/fetch/$s_!Hqpe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5668a984-9948-4c92-ae31-49ada2017605_572x325.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Hqpe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5668a984-9948-4c92-ae31-49ada2017605_572x325.png" width="572" height="325" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5668a984-9948-4c92-ae31-49ada2017605_572x325.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:325,&quot;width&quot;:572,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 11.1 &#8211; Restaurant analogy for coroutines&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 11.1 &#8211; Restaurant analogy for coroutines" title="Figure 11.1 &#8211; Restaurant analogy for coroutines" srcset="https://substackcdn.com/image/fetch/$s_!Hqpe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5668a984-9948-4c92-ae31-49ada2017605_572x325.png 424w, https://substackcdn.com/image/fetch/$s_!Hqpe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5668a984-9948-4c92-ae31-49ada2017605_572x325.png 848w, https://substackcdn.com/image/fetch/$s_!Hqpe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5668a984-9948-4c92-ae31-49ada2017605_572x325.png 1272w, https://substackcdn.com/image/fetch/$s_!Hqpe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5668a984-9948-4c92-ae31-49ada2017605_572x325.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 11.1 &#8211; Restaurant analogy for coroutines</figcaption></figure></div><p>Let&#8217;s try to understand this analogy:</p><ol><li><p><strong>Main thread as the waiter</strong>: The main thread is responsible for visible and interactive tasks (like serving diners). It shouldn&#8217;t be blocked (or stuck in the kitchen).</p></li><li><p><strong>Asynchronous tasks as the kitchen</strong>: Some operations, like fetching data from the internet or reading from a database, are similar to cooking food in the kitchen. These tasks can take time and don&#8217;t need the waiter (the main thread) to stand idle while waiting for them to finish.</p></li><li><p><strong>Coroutines as the waiter&#8217;s delegation</strong>: The waiter can delegate tasks such as food preparation to the kitchen while continuing to handle other responsibilities (serving drinks, taking new orders). Similarly, coroutines allow you to &#8220;pause&#8221; tasks and resume them later without stopping the main thread.</p></li><li><p><strong>Concurrent coroutines for multiple tasks</strong>: Just as a waiter can manage multiple orders at once, you can create multiple coroutines to handle different tasks simultaneously. For instance, one chef can prepare appetizers, another can handle main courses, and yet another can prepare desserts &#8211; all while the waiter continues serving diners.</p></li></ol><h2>In programming terms</h2><p>Kotlin simplifies asynchronous programming, but it doesn&#8217;t include heavy low-level APIs in its standard library. Instead, it provides just enough tools for libraries, such as <code>kotlinx.coroutines</code>, to build powerful coroutine-based utilities. Unlike some other languages, Kotlin doesn&#8217;t treat <code>async</code> and <code>await</code> as keywords or include them directly in the language. Instead, it uses suspending functions, which make asynchronous code safer and easier to use compared to traditional approaches such as futures or promises.</p><p>To better understand how coroutines work, let&#8217;s map their behavior to real-world and programming concepts. This helps clarify how Kotlin approaches asynchronous programming in a more intuitive way:</p><ul><li><p><strong>Main thread</strong>: The core execution thread that interacts with users (like serving diners).</p></li><li><p><strong>Coroutine</strong>: A lightweight task that runs asynchronously and can be paused and resumed without blocking the main thread.</p></li><li><p><strong>Suspension</strong>: Like the waiter leaving instructions in the kitchen, a coroutine can &#8220;pause&#8221; its execution, allowing the main thread to continue working until the task is complete.</p></li></ul><p>The <code>kotlinx.coroutines</code> library, developed by JetBrains, is a feature-rich library that provides high-level tools for working with coroutines. It includes useful functions such as <code>launch</code> and <code>async</code>, which simplify working with background tasks.</p><h2>Explanation of coroutines for a Java programmer</h2><p>It&#8217;s important to know that Java does not have coroutines. However, there are many ways we can achieve the results that coroutines achieve, and it is very likely that those of you who know Java might know some of these ways.</p><p>While there are projects such as Project Loom and Quasar that attempt to bring coroutine-like behavior to Java, they require third-party dependencies and are not yet widely adopted in the Java ecosystem.</p><p>Java thread handling was enhanced with virtual threads in version 21. They are a more lightweight threading model. Yet, virtual threads and coroutines serve different purposes. Virtual threads are primarily used for high-throughput, I/O-bound server applications based on a thread-per-request model. Coroutines, on the other hand, are typically used for various asynchronous tasks.</p><p>Note that it is perfectly possible for a coroutine to use a virtual thread to execute its tasks, especially if these tasks include blocking I/O operations.</p><p>In this section, we explored the fundamental concept of coroutines, learning how they provide a powerful and efficient alternative to traditional asynchronous programming models such as threads, futures, and promises. Using the analogy of a waiter in a restaurant, we visualized how coroutines delegate long-running tasks to the background while keeping the main thread free to handle interactive or essential tasks. Key characteristics of coroutines, such as their non-blocking nature, ease of readability, and lightweight execution, were highlighted as significant advantages over Java&#8217;s concurrency options. For Java developers, the comparison to traditional tools such as threads and futures underlined the simplicity and efficiency that coroutines bring to the table.</p><p>In the next section, we will dive deeper into the practical aspects of working with coroutines. You&#8217;ll learn how to create and manage coroutines using coroutine builders and suspending functions, setting the foundation for writing clean and effective asynchronous code in Kotlin. Let&#8217;s begin our hands-on journey into coroutine implementation!</p><div><hr></div><h1>Coroutine implementation</h1><p>Now let&#8217;s see how to implement coroutines in a Kotlin project. For this, we&#8217;ll start from scratch. First, we&#8217;ll do something very simple; we can consider it the &#8220;<code>Hello World</code>&#8220; of coroutines. Then we&#8217;ll increase the difficulty of the example and show other features.</p><p>We will start by adding the possibility of using coroutines in our project, and for that, we will add the dependency.</p><p>One thing to note is that using <code>kotlinx.coroutines</code> has become a convention in the Kotlin community, making it easier to collaborate and understand code.</p><p>We don&#8217;t recommend trying to memorize the versions, since over time, these change, and they change faster than you can imagine. It is more important to know what the official <code>kotlinx.coroutines</code> repository is. GitHub is a reliable source to obtain information about the latest versions and news: <a href="https://github.com/Kotlin/kotlinx.coroutines">https://github.com/Kotlin/kotlinx.coroutines</a>.</p><p>In general terms, we can say that we need to add the coroutines dependency. We can find it in our <code>build.gradle.kts</code> file, and the configuration would be something like this:</p><pre><code><code>dependencies {
testImplementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
}</code></code></pre><p>For coroutines, we add the one reference to <code>kotlinx 1.8.0</code>, which is enough for what we will do in this chapter. Now, in our first contact with coroutines, we are going to write <code>Hello World</code>. This will be our starting point for this entire chapter. This is the code:</p><pre><code><code>import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
    launch {
        delay(1000L)
        println("Kotlin World! ")
    }
    println("Hello")
}</code></code></pre><p>The imports come from the <code>kotlinx.coroutines</code> package, which contains the main tools for working with coroutines in Kotlin:</p><ul><li><p><code>delay</code>: A function that suspends the execution of a coroutine for a specific time (in milliseconds) without blocking the thread.</p></li><li><p><code>launch</code>: A function that starts a new coroutine in parallel with the existing execution.</p></li><li><p><code>runBlocking</code>: A function that runs a blocking coroutine, useful for simple examples or tests where we want to block the main thread until the inner coroutines finish.</p></li></ul><p>The imports should be familiar. The first part that is noticeably different from basic &#8220;<code>Hello World</code>&#8220; code is the following:</p><pre><code><code>fun main() = runBlocking {</code></code></pre><p>The <code>runBlocking</code> instruction is used here to allow <code>main</code> to run as a coroutine. It blocks the main thread until all coroutines within its block finish executing.</p><p>This ensures that the program does not terminate before the coroutines finish.</p><p>Let&#8217;s look at the <code>launch</code> instruction:</p><pre><code><code>launch {
    delay(1000L)
    println("Kotlin World!")
}</code></code></pre><p><code>launch</code> creates a new coroutine. This coroutine runs concurrently with the rest of the code inside <code>runBlocking</code>.</p><p>Inside this coroutine, we have <code>delay(1000L)</code>, which suspends the coroutine for 1 second (1,000 milliseconds) without blocking the thread. This means that the main thread is free to perform other tasks in the meantime, and after one second, it prints <code>"Kotlin World!"</code>.</p><p>Finally, we have the last instruction:</p><pre><code><code>println("Hello")</code></code></pre><p>Since the coroutine does not block the thread, this message is printed before the coroutine finishes its task.</p><p>Summarizing the flow, we can describe it as follows:</p><ol><li><p>The main function is executed and enters the <code>runBlocking</code> block.</p></li><li><p>Inside <code>runBlocking</code>, the following occurs:</p><ol><li><p>A coroutine is launched with <code>launch.</code></p></li><li><p>Execution continues without waiting for the coroutine to finish, so <code>println("Hello")</code> is executed immediately.</p></li></ol></li><li><p>In the meantime, the coroutine executes<code> delay(1000L)</code>, which suspends the coroutine for one second.</p></li><li><p>After the delay, it prints <code>"Kotlin World!"</code>.</p></li><li><p>The program waits inside <code>runBlocking</code> until all coroutines inside its block finish. Only then does <code>runBlocking</code> complete and the program terminates.</p></li></ol><p>After executing the code, we see the following as a result:</p><pre><code><code>Hello
Kotlin World!</code></code></pre><p>The reason we started with <code>Hello World</code> is simple &#8211; we are studying the simplest possible case to show how coroutines allow concurrent tasks (such as waiting a second) to be expressed without blocking the entire scope of execution. In this example, <code>runBlocking</code> blocks the main thread until all coroutines inside complete, but the coroutine launched with <code>launch</code> runs concurrently within that scope.</p><p>Compared to a traditional threading approach, this is more efficient and easier to read.</p><p>We introduced the concepts of <code>delay</code> and <code>launch</code> in a simple context. Now we will look at another example. We will simulate a restaurant scenario to demonstrate how coroutines handle asynchronous tasks.</p><p>We want to demonstrate how to perform multiple tasks simultaneously</p><p>We are going to show how not to block the main thread while waiting for long-running tasks. We will illustrate coordination between different asynchronous operations, something similar to what we mentioned about the waiter and the restaurant. This is the new code:</p><pre><code><code>import kotlinx.coroutines.*
fun main() = runBlocking {
    println("Taking order...")
    // Coroutine 1: Preparing food in the kitchen
    val food = async { prepareFood() }
    // Coroutine 2: Serving drinks
    serveDrinks()
    // Wait for food to be ready and serve it
    println("Food is ready: ${food.await()}")
}
suspend fun prepareFood(): String {
    delay(3000) // Simulate food preparation
    return "Delicious meal"
}
fun serveDrinks() {
    println("Serving drinks to the table...")
}</code></code></pre><p>The <code>runBlocking</code> block represents the main thread where primary actions (such as interacting with diners) occur. As mentioned before, the <code>runBlocking</code> instruction is a function that runs a block of code as a blocking coroutine. This means that the program will not end until all the code inside this block finishes. In this example, it represents the main thread, which acts as the waiter handling multiple tasks.</p><pre><code><code>println("Taking order...")</code></code></pre><p>The preceding line of code is printed immediately on the main thread (representing the waiter).</p><pre><code><code>val food = async { prepareFood() }</code></code></pre><p><code>async</code> launches a new coroutine to execute the <code>prepareFood()</code> function concurrently.</p><p><code>async</code> is a function that returns a <code>Deferred</code> object, which is like a &#8220;future&#8221; in Java. This object can be awaited or revisited later to get the results of the operation.</p><p>In this case, it represents the cook who starts preparing the food in the background.</p><p>Let us look at the <code>prepareFood</code> function:</p><pre><code><code>suspend fun prepareFood(): String {
    delay(3000) // Simulate food preparation
    return "Delicious meal"
}</code></code></pre><p>The <code>prepareFood</code> function is a suspended function, meaning that it can pause its execution and later resume exactly where it left off, without blocking the underlying thread while it is waiting. However, a <code>suspend</code> function can only be called from another <code>suspend</code> function or within a coroutine. It is also important to note that while the thread is not blocked, the coroutine that invokes <code>prepareFood</code> will remain suspended until the function resumes.</p><p><code>delay(3000)</code> simulates a meal preparation time of three seconds. During this time, the coroutine is suspended, allowing the main thread to do other tasks.</p><p>After the delay, it returns <code>Delicious meal</code>.</p><p>While the food is being prepared (the async coroutine is suspended), the main thread continues to execute this function:</p><pre><code><code>serveDrinks()</code></code></pre><p>It prints the message <code>Serving drinks to the table...</code>.</p><p>Here, the waiter (main thread) serves drinks while waiting for the cook to finish the food:</p><pre><code><code>println("Food is ready: ${food.await()}")</code></code></pre><p><code>food.await()</code> pauses execution at this point until the <code>prepareFood()</code> coroutine finishes and returns its result.</p><p>Once <code>prepareFood()</code> returns <code>Delicious meal</code>, the main thread prints the following:</p><pre><code><code>Food is ready: Delicious meal</code></code></pre><p>This is how the flow goes:</p><ul><li><p><code>runBlocking</code> starts the main block.</p></li><li><p>The waiter takes the order and starts serving the drinks.</p></li><li><p>Meanwhile, the cook (coroutine) prepares the food in the background.</p></li><li><p>After three seconds, the food is ready and the waiter serves it.</p></li></ul><p>Returning to our analogy, we&#8217;ve already seen the following:</p><ul><li><p><code>Waiter</code>: This is the main thread that organizes tasks. It can serve drinks and handle other orders without getting stuck waiting for the food to be ready.</p></li><li><p><code>Cook</code>: This is a coroutine that works independently in the background, preparing the food.</p></li><li><p><code>async</code> and<code> await</code>: <code>async</code> starts the cook&#8217;s work and <code>await</code> makes sure the waiter waits for the result before serving the food.</p></li><li><p><code>delay</code>: This simulates the time it takes the cook to prepare the food.</p></li></ul><p>This is what we see after running the program:</p><pre><code><code>Taking order...
Serving drinks to the table...
Food is ready: Delicious meal</code></code></pre><p>The main goal of this exercise was to demonstrate how coroutines allow you to do several things &#8220;at the same time&#8221; efficiently, just like a good waiter in a restaurant.</p><p>We are going to show another example that simulates loading book data from an API using coroutines in Kotlin. The repository (<code>BookRepository</code>) has a suspended function that simulates a two-second delay before returning the book data. In the <code>BookScreen</code> class, <code>withContext(Dispatchers.IO)</code> is used to execute the task in a context suitable for I/O operations, displaying success or error messages depending on the result. This demonstrates how to handle asynchronous tasks in an efficient and readable way. But we will build it in parts.</p><pre><code><code>import kotlinx.coroutines.*
// Simulate a repository that fetches book data
class BookRepository {
    // Simulate an API call to fetch a book
    suspend fun fetchBookFromApi(bookId: String): Book {
        delay(2000) // Simulate network response time
        return Book(bookId, "Clean Code", "Robert C. Martin")
    }
}</code></code></pre><p><code>BookRepository</code> has a <code>suspend</code> function, <code>fetchBookFromApi</code>, which simulates calling an API to get the data for a book.</p><p>It uses <code>delay(2000)</code> to represent a two-second network delay, and then returns a <code>Book</code> object as the result.</p><p>We add this code, which defines the class that the <code>fetchBookFromApi</code> function returns:</p><pre><code><code>data class Book(
    val id: String,
    val title: String,
    val author: String
)</code></code></pre><p><code>Book</code> is a data class that represents information about a book, with properties such as <code>id</code>, <code>title</code>, and <code>author</code>.</p><pre><code><code>class BookScreen {
    private val repository = BookRepository()
    fun loadBookData() = runBlocking {
    try {
        println(" Loading book information...")
        // Send the request in a coroutine
        val book = withContext(Dispatchers.IO) {
            repository.fetchBookFromApi("9780132350884")
}</code></code></pre><p><code>BookScreen</code> defines the <code>loadBookData</code> method, which uses <code>runBlocking</code> to handle loading book data within a coroutine.</p><p>The <code>loadBookData</code> method uses <code>withContext(Dispatchers.IO)</code> to perform the book-fetching task in a context optimized for I/O operations.</p><p>Finally, we define <code>main</code>, which will allow us to execute the code:</p><pre><code><code>fun main() {
    val screen = BookScreen()
    screen.loadBookData()
}</code></code></pre><p>In the <code>main</code> function, you simply create an instance of <code>BookScreen</code> and call the <code>loadBookData</code> method to start the process.</p><p>Summarizing the program flow, this would be as follows:</p><ul><li><p>It prints &#8220;<code>Loading book information...</code>&#8220;</p></li><li><p>It waits two seconds, simulating the API response.</p></li><li><p>If everything goes well, it prints <code>"Book loaded: Clean Code by Robert C. Martin"</code>.</p></li><li><p>If an error occurs, it prints <code>"Error loading book information: ..."</code></p></li></ul><p>Let&#8217;s take a closer look at what&#8217;s happening in this exercise. We&#8217;ve successfully used <code>runBlocking</code> and <code>withContext</code> to run the task asynchronously and on a separate thread (<code>Dispatchers.IO</code>), which is ideal for I/O tasks such as API calls.</p><p>This approach is ideal for I/O tasks such as API calls because it allows us to perform these operations without blocking the main thread.</p><p>By offloading the work to a different dispatcher, we ensure that time-consuming operations (such as reading from a database, writing to a file, or calling an external API) don&#8217;t freeze the UI or other critical processes. In Kotlin coroutines, <code>Dispatchers.IO</code> is specifically optimized for these types of tasks, giving us efficient and responsive code even when dealing with slow or unpredictable external systems.</p><p>We have included a <code>try-catch</code> block to catch possible exceptions that may occur during the API call, which is a good practice to make your code more robust.</p><p>Simulating API response time with a delay is useful to understand the behavior of coroutines in real-world scenarios.</p><p>In this section, we introduced the foundational ideas behind coroutines in Kotlin, focusing on how they enable writing asynchronous, non-blocking code that remains readable and efficient. We illustrated real-world analogies and scenarios to highlight how coroutines support structured concurrency and safe context switching, making them a powerful tool for managing concurrent tasks in modern applications.</p><p>It&#8217;s important to clarify when to use these different coroutine builders:</p><ul><li><p><code>runBlocking</code> is typically used to bridge blocking and non-blocking code, for example, in tests or the <code>main</code> function of a small program. It blocks the current thread until its coroutine body finishes executing.</p></li><li><p>In contrast, using coroutine builders such as <code>launch</code> or <code>async</code> within an existing coroutine scope, combined with context switching (<code>withContext</code>), lets you run asynchronous code without blocking any thread, ensuring better performance and responsiveness.</p></li></ul><p>It is important to note, however, that coroutines are not magical. If you call blocking operations inside a <code>suspend</code> function &#8211; for example, using <code>Thread.sleep()</code> or synchronous I/O calls such as JDBC &#8211; you will still block the underlying thread. To fully benefit from coroutines, blocking calls should be replaced with non-blocking equivalents whenever possible.</p><p>So, while <code>runBlocking</code> is useful in specific entry points, using structured concurrency with <code>launch</code>, <code>async</code>, and <code>withContext</code> is preferred in production code to handle tasks concurrently and safely without halting your program&#8217;s execution.</p><p>Now that we have a solid understanding of how to implement and use coroutines effectively, the next section will introduce timeouts and cancellations. You&#8217;ll learn how to gracefully handle scenarios where tasks take too long or need to be interrupted, ensuring your applications remain responsive and efficient under various conditions. Let&#8217;s continue exploring how Kotlin coroutines can simplify even the most complex asynchronous workflows!</p><div><hr></div><h1>Timeouts and cancellations</h1><p>Now we will see two important elements that are good to know about in coroutines: timeouts and cancellations. Timeouts are time limits that you set for an operation. If the operation takes longer than the specified time, it is automatically cancelled.</p><p>A simple way to describe them using examples of daily activities would be as follows:</p><ul><li><p>Ordering a pizza and getting the notice: &#8220;If it doesn&#8217;t arrive in 30 minutes, we cancel the order and refund you&#8221;.</p></li><li><p>When an ATM cancels the operation if we don&#8217;t respond within a certain time.</p></li></ul><p>Cancellations, as their name suggests, allow us to stop a coroutine in the middle of its execution. Let&#8217;s look at a couple of examples:</p><ul><li><p>Canceling a download in progress.</p></li><li><p>Canceling a bank transfer before it is completed.</p></li></ul><p>Thinking of timeouts and cancellations as elements that we can use, we could easily give some situations where we could use them:</p><ul><li><p>API calls that should not take too long.</p></li><li><p>File downloads that can be cancelled.</p></li><li><p>Database operations with a time limit.</p></li><li><p>Background processes that can be cancelled by the user.</p></li></ul><p>Taking our previous example of books as a basis, we are now going to modify it to have both cancellation and timeouts.</p><p>The definitions of <code>BookRepository</code> and <code>Book</code> remain the same; we just add more time to <code>Delay</code>. Before, we had <code>2000</code>, but we will change the value to <code>3000</code>. We increased the time period to later; we set a time limit of two seconds and allowed three seconds to execute it (the timeout):</p><pre><code><code>class BookRepository {
    // Simulates an API call to get a book
    suspend fun fetchBookFromApi(bookId: String): Book {
        delay(3000) // Simulates network response time
        return Book(bookId, "Clean Code", "Robert C. Martin")
    }
}
data class Book(
    val id: String,
    val title: String,
    val author: String
)</code></code></pre><p>Now we will change <code>launch</code> to make it look like this:</p><pre><code><code>val job = launch {
    try {
        println(" Loading book information...")
        // Set a timeout for the operation
        val book = withTimeout(2000) { // 2 second timeout
            repository.fetchBookFromApi("9780132350884")
        }
        println(" Book loaded: ${book.title} by ${book.author}")
    } catch (e: TimeoutCancellationException) {
        println(" Timeout exceeded when loading book.")
    } catch (e: Exception) {
        println(" Error loading book information: ${e.message}")
    }
}</code></code></pre><p>We set a time limit of 2,000 milliseconds to complete the <code>fetchBookFromApi</code> operation. If the task does not finish within this time, <code>TimeoutCancellationException</code> is thrown. This mechanism is essential in applications where it is critical to avoid long blockages and ensure fast responses.</p><p>In this statement, we can see that the approach uses <code>job</code>, as declared with <code>launch</code>, to handle the execution of the coroutine:</p><pre><code><code>val job = launch {</code></code></pre><p>This allows the job to be explicitly cancelled if necessary, providing flexibility to stop execution in specific situations. For example, the job could be cancelled with a call to <code>job.cancelAndJoin()</code> if the task is detected to be no longer relevant or if an external event occurs.</p><p>Exception handling is a key part of this approach:</p><pre><code><code>} catch (e: TimeoutCancellationException) {
println(" Timeout exceeded when loading book.")</code></code></pre><p>These last lines demonstrate how to handle scenarios where the time limit is exceeded, ensuring that the system can respond appropriately without crashing. Other exceptions are also handled to catch generic errors during execution.</p><p>While this approach is powerful, it adds complexity to the code. It requires explicitly handling both the job and exceptions, which may be unnecessary for simple tasks. However, in cases where strict time limits or precise cancellations are needed, this structure proves invaluable.</p><p>Outside of <code>loadBookData</code>, we will also modify the code in the following way:</p><pre><code><code>// Change: Cancel the job after 4 seconds
delay(4000)
println(" Canceling the loading of the book...")
job.cancelAndJoin() // Cancel the job and wait for it to finish
println("Task cancelled.")</code></code></pre><p>Notice that we have introduced a delay of 4,000 milliseconds and manual cancellation. Also notice that we use a job to cancel the task explicitly with <code>job.cancelAndJoin()</code>.</p><p>The cancellation and timeout scenario introduces a more controlled approach to handling asynchronous tasks in Kotlin. Using <code>launch</code> and <code>withTimeout</code>, a time limit is set for the execution of an operation.</p><p>This ensures that the task does not exceed the time we decide, throwing an exception (<code>TimeoutCancellationException</code>) if the time runs out. Also, by encapsulating the logic in a job, the task can be cancelled explicitly when necessary, optimizing resources and allowing better management of multiple concurrent tasks.</p><p>This example we developed includes robust exception handling to differentiate between a timeout and other errors. In summary, this model is ideal for applications where time limits and cancellation of irrelevant or delayed tasks are required, although it adds a bit more complexity to the code.</p><p>This section emphasized the importance of timeouts and cancellations in effective coroutine management. By enforcing time limits and enabling controlled interruption of tasks, Kotlin coroutines provide mechanisms to keep applications responsive and resilient, especially when dealing with potentially long-running operations.</p><p>We also introduced the concept of cancellations, allowing tasks to be stopped explicitly when they are no longer needed. By leveraging the j<code>ob</code> object, we illustrated how to manage and cancel ongoing tasks using <code>job.cancelAndJoin()</code>, ensuring efficient resource management. Additionally, we incorporated robust exception handling to distinguish between timeout-specific issues (<code>TimeoutCancellationException</code>) and other errors, adding resilience to our code.</p><p>By combining these techniques, we showed how to handle asynchronous tasks in a controlled and efficient manner, enabling developers to create responsive applications that gracefully handle delays and interruptions. This approach is particularly useful in scenarios involving API calls, file downloads, or user-cancelled operations, providing flexibility and reliability.</p><p>Next, we&#8217;ll explore contexts and scopes, diving deeper into how coroutines manage their execution environments and life cycle. You&#8217;ll learn about coroutine dispatchers, structured concurrency, and how to properly manage coroutine life cycles to maintain clean, predictable, and efficient code. Let&#8217;s continue building our understanding of Kotlin coroutines with these advanced concepts!</p><div><hr></div><h1>Contexts and scope</h1><p>Context and scope are fundamental concepts in the world of Kotlin coroutines and play a crucial role in managing their life cycle and behavior.</p><p>A context in Kotlin coroutines is like a container that groups key information about how and where a coroutine will run. This context includes the following:</p><ul><li><p><strong>Dispatcher</strong>: Defines which thread or thread group the coroutine will run on, as follows:</p><ul><li><p><code>Dispatchers.IO</code> for I/O operations (such as working with files or network calls).</p></li><li><p><code>Dispatchers.Main</code> for updating the UI.</p></li></ul></li><li><p><strong>Job</strong>: Represents the specific task the coroutine performs. It also allows you to cancel it or monitor its progress.</p></li><li><p><strong>Other attributes</strong>: Can include things such as the following:</p><ul><li><p><code>CoroutineName</code>: A useful identifier to distinguish coroutines.</p></li><li><p><code>NonCancellable</code>: Indicates that the coroutine should not be cancelled, even if its parent or associated scope is.</p></li></ul></li></ul><p>In addition, you can put custom elements into the coroutine context, such as an <strong>Mapped Diagnostic Context</strong> (<strong>MDC</strong>) for logging. It is also important to remember that thread-local variables are not effective with coroutines, since a coroutine may resume on a different thread. If you need thread-local-like behavior, you should use <code>ThreadContextElement</code> to propagate context across suspensions.</p><p>In short, a coroutine&#8217;s context sets up its execution environment: it defines where it runs, how it interacts with other processes, and what rules it should follow while working. It&#8217;s like giving the coroutine a roadmap and tools to get it to perform its task correctly.</p><p>The code for <code>BookRepository</code> and <code>Book</code> remains the same; we don&#8217;t have to make any changes.</p><p>The changes start with the <code>BookScreen</code> code. We add the scope as follows:</p><pre><code><code>class BookScreen {
    private val repository = BookRepository()
    private val scope = CoroutineScope(Dispatchers.Main + Job())</code></code></pre><p><code>CoroutineScope</code> is used, which automatically encapsulates the context and the associated job. With this change, instead of the previously temporary scope used with <code>runBlocking</code>, now there is a permanent scope with <code>CoroutineScope(Dispatchers.Main + Job())</code>.</p><p>This makes it easier to manage related tasks, allowing all coroutines associated with the scope to be cancelled with a single call (<code>scope.cancel()</code>).</p><p>We need better organization for larger applications or those with multiple coroutines.</p><p>Let&#8217;s make some changes in the structure:</p><pre><code><code>fun loadBookDetails(bookId: String) {
    scope.launch {
    withContext(Dispatchers.IO) {
        // code here
        }
    }
}</code></code></pre><p>We notice with these changes that a specific scope is defined at the class level and dispatchers are explicitly specified. In addition, we are now handling specific execution contexts, and we are going to allow global cancellation of all coroutines.</p><p>Regarding the execution context, we can also say that with the previous code, dispatchers were not specified, and now we are indicating one, <code>dispatchers.IO</code>, for input and output operations.</p><p>We also need to change <code>val book</code>. It should look like this:</p><pre><code><code>val book = withTimeout(3000) {
    // Set a timeout of 3 seconds
    withContext(Dispatchers.IO) {
        // Run the task in the I/O Dispatcher
        repository.fetchBookFromApi(bookId)
    }
}</code></code></pre><p>We add this <code>function(cancelAllTasks)</code> after <code>loadBookDetails</code>:</p><pre><code><code>fun cancelAllTasks() {
    scope.cancel() // Cancels all coroutines in the Scope
    println("All tasks were cancelled.")
}</code></code></pre><p>We centralize canceling all active coroutines of the scope with <code>scope.cancel()</code> in the <code>cancelAllTasks()</code> method.</p><p>The main code part now looks like this:</p><pre><code><code>fun main() {</code><strong>
</strong><code>    val screen = BookScreen()</code><strong>
</strong><code>    // Start loading book details</code><strong>
</strong><code>    screen.loadBookDetails("1")</code><strong>
</strong><code>    // Simulate task cancellation after 5 seconds</code><strong>
</strong><code>    runBlocking {</code><strong>
</strong><code>        delay(5000)</code><strong>
</strong><code>        screen.cancelAllTasks()</code><strong>
</strong><code>    }</code><strong>
</strong><code>}</code></code></pre><p>With the <code>loadBookDetails</code> instruction, we will start loading the book details to display them. With the second part, <code>runBlocking</code>, we would be simulating the cancellation of tasks after five seconds.</p><p>So, let&#8217;s get to the differences:</p><ul><li><p><strong>Life cycle management</strong>:</p><ul><li><p><code>runBlocking</code> blocks the main thread until all the internal coroutines complete. It&#8217;s useful for testing or small examples, but it&#8217;s not ideal for real applications because it freezes the main thread while it waits. The problem is that the thread is blocked, which affects performance if there are more concurrent tasks.</p></li><li><p>Using <code>CoroutineScope</code> acts as a container that allows you to manage all the coroutines created within it. This is ideal for real-world applications because it allows you to manage the life cycle of multiple coroutines in a centralized manner.</p></li><li><p>We can see that in the code, we can now cancel all related coroutines at once using <code>scope.cancel()</code>. This is useful in situations where, for example, a user leaves a screen in a mobile app.</p></li></ul></li><li><p><strong>Dispatchers</strong>:</p><ul><li><p>Before the changes, no dispatcher was specified. All code runs in the default context of <code>runBlocking</code>. This meant that tasks such as <code>fetchBookFromApi</code> run on the same thread as <code>runBlocking</code>. This can be problematic if you are doing intensive tasks, such as API calls or database access, because they might block the main thread.</p></li><li><p>By implementing <code>Dispatchers.IO</code>, we take advantage of the fact that it is designed for I/O tasks, such as network calls. This ensures that these tasks are executed in a thread optimized for operations of this type, without blocking the main thread.</p></li><li><p>So, we clearly separate the network tasks (in <code>Dispatchers.IO</code>) from the UI-related tasks (in <code>Dispatchers.Main</code>).</p></li></ul></li><li><p><strong>Cancellation of coroutines</strong>:</p><ul><li><p>Previously, we cancelled manually with <code>job.cancelAndJoin()</code>. This stops the specific coroutine and waits for all resources to be released. This is effective but requires you to control each job individually. Switching to <code>scope.cancel()</code> stops all coroutines associated with <code>CoroutineScope</code>. This is cleaner and more scalable, especially if you have multiple related tasks.</p></li></ul></li><li><p><strong>Separation of contexts</strong>:</p><ul><li><p>Initially, there was no explicit separation of contexts. All operations, such as API calls and result handling, are executed in the same thread. With the changes, the contexts were separated clearly, as we can see:</p><pre><code><strong>withContext(Dispatchers.IO) { repository.fetchBookFromApi(bookId)
}</strong></code></pre></li></ul></li></ul><p>Networking code (API call) runs in <code>Dispatchers.IO</code>, while the rest of the code (e.g., printing to the console) can run in <code>Dispatchers.Main</code>. This improves performance and keeps the UI smooth.</p><p>In summary, the final code represents a more mature and professional implementation of coroutine handling in Kotlin, where each operation is executed in the most appropriate context: the UI in the main dispatcher and network operations in the I/O dispatcher. This approach not only optimizes the use of system resources but also provides a clearer and more maintainable structure, where responsibilities are well defined and separated. Operation cancellation is handled more robustly through a shared scope, allowing for efficient coroutine life cycle management.</p><p>Furthermore, this implementation pattern better reflects real-world practices in app development, especially on Android, where it is crucial to properly handle execution contexts and component life cycles. By using <code>CoroutineScope</code> defined at the class level along with specific dispatchers, you achieve more predictable and maintainable code, which can scale better as your app grows and becomes more complex.</p><div><hr></div><h1>Summary</h1><p>In this chapter, we delved into Kotlin coroutines, one of the most transformative features of the language for handling asynchronous programming. Here is a summary of what we&#8217;ve learned.</p><p>We started by demystifying coroutines through relatable analogies, such as a waiter in a restaurant. This helped visualize how coroutines enable concurrent, non-blocking tasks while keeping the main thread free for interactive operations.</p><p>We introduced coroutine builders such as <code>launch</code> and <code>runBlocking</code>, along with suspending functions such as <code>delay</code>. Through practical examples such as &#8220;<code>Hello World</code>&#8220; and the restaurant scenario, we showcased how to create and manage coroutines effectively.</p><p>We explored the critical concepts of setting time limits (<code>withTimeout</code>) and manually canceling tasks (<code>job.cancelAndJoin</code>). These mechanisms ensure efficient resource usage and help handle long-running tasks gracefully, as seen in the book-fetching example.</p><p>Finally, we covered how <code>CoroutineScope</code> and dispatchers are used to manage execution contexts and life cycles. We demonstrated how separating contexts (e.g., <code>Dispatchers.IO</code> for network calls and <code>Dispatchers.Main</code> for UI updates) improves application performance and maintainability. We also highlighted the importance of structured concurrency, enabling efficient cancellation and life cycle management for related tasks.</p><p>This chapter provided a robust foundation for understanding and applying Kotlin coroutines, a game-changing feature that simplifies asynchronous programming. By transitioning from basic examples to real-world scenarios, you now understand how coroutines enhance concurrency, improve code readability, and make resource management more efficient. These concepts are critical for developing scalable and responsive applications, whether for Android, server-side, or other Kotlin-based projects.</p><div><hr></div><p>If you want to dig deeper into the mechanics of moving from Java to Kotlin&#8212;writing <strong>idiomatic Kotlin</strong>, handling <strong>null safety</strong>, using <strong>coroutines</strong> for concurrency, and taking advantage of features like <strong>extension functions</strong> and <strong>DSLs</strong>&#8212;check out <em><strong><a href="https://www.packtpub.com/en-us/product/kotlin-for-java-developers-9781835884836">Kotlin for Java Developers</a></strong></em> by <strong>Jos&#233; Dimas Luj&#225;n Castillo</strong> and <strong>Ron Veen</strong> (Packt, Oct 2025). Written for experienced Java developers, it teaches Kotlin by mapping concepts directly to familiar Java constructs and then goes further into interoperability, generics, data and sealed classes, coroutines and flows, and DSL design&#8212;across backend, Android, and cross-platform development.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-us/product/kotlin-for-java-developers-9781835884836" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BDJC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!BDJC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!BDJC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!BDJC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BDJC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775" width="336" height="414.46153846153845" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/abe245f2-991b-46bf-905b-bff991dc8e14_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:336,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Kotlin for Java Developers&quot;,&quot;title&quot;:&quot;Kotlin for Java Developers&quot;,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-us/product/kotlin-for-java-developers-9781835884836&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Kotlin for Java Developers" title="Kotlin for Java Developers" srcset="https://substackcdn.com/image/fetch/$s_!BDJC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!BDJC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!BDJC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!BDJC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>]]></content:encoded></item><item><title><![CDATA[Go + Eino ADK Quickstart: Master Core AI Agent Design Patterns ]]></title><description><![CDATA[Build composable, interruptible multi-agent workflows in Go with Eino&#8217;s unified Agent abstraction&#8212;ReAct, WorkflowAgents, Supervisor, Plan-Execute-Replan, and DeepAgents.]]></description><link>https://deepengineering.net/p/go-eino-adk-quickstart-master-core</link><guid isPermaLink="false">https://deepengineering.net/p/go-eino-adk-quickstart-master-core</guid><dc:creator><![CDATA[Pandeng Li]]></dc:creator><pubDate>Fri, 30 Jan 2026 13:31:43 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/86222d3c-2ccb-48a5-9c2a-4402ed429f02_800x533.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p><em>With thanks to AI Engineer <strong><a href="https://www.linkedin.com/in/gerald-parker-b7948050/">Gerald Parker</a></strong> for his technical review.</em></p></blockquote><p>Eino ADK, pronounced &#8220;I know&#8221;, is a multi-agent development framework designed for Go, developed and hardened in real-world use at ByteDance. Its design philosophy is &#8220;keep simple things simple, and make complex things possible&#8221;. Open-sourced at the start of 2025, Eino&#8217;s promise to Go developers is they can focus on implementing business logic without worrying about underlying technical complexity.</p><p>In this article co-written with the team behind Eino, we will discuss:</p><ul><li><p>What Eino ADK is</p></li></ul><ul><li><p>Core agent patterns in Eino along with real use cases</p></li></ul><ul><li><p>Example code for building a simple project manager agent</p></li></ul><h1>Introduction to Eino ADK</h1><p>Agents are quickly becoming the mainstream way to deploy LLMs, from intelligent customer service to automated office work. With them, the following pain points are emerging:</p><ul><li><p><strong>LLMs are not bridged well with business systems</strong>, resulting in agents that can only engage in &#8220;empty talk.&#8221;</p></li></ul><ul><li><p><strong>Lack of state management</strong> causes agents to frequently &#8220;forget&#8221; when performing tasks.</p></li></ul><ul><li><p><strong>Complex interactive processes</strong> increase development difficulty even further.</p></li></ul><p>Eino ADK was created to provide Go developers with a complete, flexible, and powerful agent development framework that addresses these core challenges head-on.</p><blockquote><h2>Recap: What is an Agent?</h2><p>You can think of an agent as an independent, intelligent entity that can understand instructions, perform tasks, and provide responses &#8211; capable of autonomous learning, adaptation, and decision-making. Its main functions include:</p><ul><li><p><strong>Reasoning:</strong> An agent can analyze data, identify patterns, and use logic and available information to draw conclusions, make inferences, and solve problems.</p></li><li><p><strong>Action:</strong> An agent takes actions or executes tasks to achieve goals based on decisions, plans, or external inputs.</p></li><li><p><strong>Observation:</strong> An agent autonomously collects relevant information (for example, through computer vision, natural language processing, or sensor data analysis) to understand the context and lay the foundation for informed decision-making.</p></li><li><p><strong>Planning:</strong> An agent can determine necessary steps, evaluate potential actions, and select the best course of action based on available information and expected outcomes.</p></li><li><p><strong>Collaboration:</strong> An agent can effectively work together with others (human or other agents) in complex and dynamic environments.</p></li></ul></blockquote><p><strong>Any scenario that requires interaction with a LLM can be abstracted as an agent.</strong> For example:</p><ul><li><p>An agent for querying weather information</p></li></ul><ul><li><p>An agent for booking meetings</p></li></ul><ul><li><p>An agent capable of answering questions in a specific domain</p></li></ul><h2>What is Eino ADK?</h2><p><a href="https://github.com/cloudwego/eino">Eino ADK</a> is a complete system for developing intelligent agents. It breaks down complex AI applications into independent, composable intelligent agent units, allowing developers to build agentic systems the way they would assemble Lego blocks.</p><p>Its features include:</p><ul><li><p><strong>Less glue code:</strong> Unified interfaces and event streams make complex task decomposition more natural.</p></li></ul><ul><li><p><strong>Rapid orchestration:</strong> Pre-built paradigms + workflows allow you to build pipelines in minutes.</p></li></ul><ul><li><p><strong>More controllable:</strong> Interruptible, resumable, and auditable, making the agent collaboration process &#8220;visible.&#8221;</p></li></ul><p>Eino achieves this through unified abstract interfaces, flexible composition patterns, and powerful collaboration mechanisms.</p><h2>The Core: Unified Agent Abstraction</h2><p>The core of ADK is a concise and powerful agent interface:</p><pre><code><strong>type Agent interface {
    Name(ctx context.Context) string
    Description(ctx context.Context) string
    Run(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent]
}</strong></code></pre><p>Each agent has:</p><ul><li><p>A clear identity (Name)</p></li></ul><ul><li><p>A clear responsibility (Description)</p></li></ul><ul><li><p>A standardized execution method (Run)</p></li></ul><p>This provides a basis for discovery and invocation between agents. Simple Q&amp;A robots or complex multi-step task processing systems can be implemented through this unified interface.</p><h1>Core Agent Patterns</h1><p>The following sections lay out the core patterns that make up Eino ADK. Each will be introduced, explained, and then brief skeleton code demonstrated.</p><h2>ChatModelAgent: The Brain</h2><p>ChatModelAgent is the most important pre-built component in Eino ADK. It encapsulates the interaction logic with LLMs and implements the classic <a href="https://react-lm.github.io/">ReAct</a> (Reason-Act-Observe) pattern. The process is as follows:</p><ol><li><p><strong>Reason</strong>: Call the LLM.</p></li></ol><ol start="2"><li><p><strong>Act</strong>: The LLM returns a tool call request.</p></li></ol><ol start="3"><li><p><strong>Act</strong>: ChatModelAgent executes the tool.</p></li></ol><ol start="4"><li><p><strong>Observe</strong>: The tool&#8217;s result is returned to the LLM, which continues to generate based on the previous context until the model determines that no more tool calls are needed.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!a7vJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee46f9d4-fc00-4186-bf2b-f7502fc737e7_645x576.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!a7vJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee46f9d4-fc00-4186-bf2b-f7502fc737e7_645x576.png 424w, https://substackcdn.com/image/fetch/$s_!a7vJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee46f9d4-fc00-4186-bf2b-f7502fc737e7_645x576.png 848w, https://substackcdn.com/image/fetch/$s_!a7vJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee46f9d4-fc00-4186-bf2b-f7502fc737e7_645x576.png 1272w, https://substackcdn.com/image/fetch/$s_!a7vJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee46f9d4-fc00-4186-bf2b-f7502fc737e7_645x576.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!a7vJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee46f9d4-fc00-4186-bf2b-f7502fc737e7_645x576.png" width="645" height="576" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ee46f9d4-fc00-4186-bf2b-f7502fc737e7_645x576.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:576,&quot;width&quot;:645,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A diagram of a chat model\n\nAI-generated content may be incorrect.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A diagram of a chat model

AI-generated content may be incorrect." title="A diagram of a chat model

AI-generated content may be incorrect." srcset="https://substackcdn.com/image/fetch/$s_!a7vJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee46f9d4-fc00-4186-bf2b-f7502fc737e7_645x576.png 424w, https://substackcdn.com/image/fetch/$s_!a7vJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee46f9d4-fc00-4186-bf2b-f7502fc737e7_645x576.png 848w, https://substackcdn.com/image/fetch/$s_!a7vJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee46f9d4-fc00-4186-bf2b-f7502fc737e7_645x576.png 1272w, https://substackcdn.com/image/fetch/$s_!a7vJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee46f9d4-fc00-4186-bf2b-f7502fc737e7_645x576.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The core of the ReAct pattern is a closed loop of <strong>Reason &#8594; Act &#8594; Observe &#8594; Reason Again,</strong> which addresses the pain points of traditional agents that either act blindly or have a disconnect between reasoning and action.</p><p>Here are some possible practical scenarios:</p><ul><li><p><strong>Generating a market analysis report:</strong></p><ul><li><p><strong>Reason-1:</strong> To assess the potential of a sector, information on policy support, industry growth rate, profitability of leading companies, and supply chain bottlenecks is needed.</p></li><li><p><strong>Action-1:</strong> Call an API to get overall financial data for the industry.</p></li><li><p><strong>Observe-1</strong>: Analyze the data.</p></li><li><p><strong>Reason-2:</strong> Determine that the industry has high growth and policy endorsement, but rising upstream prices may squeeze downstream profits. Decide that further verification is needed.</p></li><li><p><strong>Act-2:</strong> Call an API to get detailed data on supply and demand, industry research reports, and so on.</p></li><li><p><strong>Observe-2</strong>: Analyze the data.</p></li><li><p><strong>Reason-3:</strong> Integrate the conclusions to generate an analysis report with key data sources.</p></li></ul></li></ul><blockquote><p>Using the ReAct pattern avoids information overload caused by collecting all the information at once. It focuses on core issues through gradual reasoning and uses data to verify thoughts rather than relying on intuition. The process is also <strong>explainable</strong>, improving the accuracy of the generated report.</p></blockquote><ul><li><p><strong>IT fault operations and maintenance:</strong></p><ul><li><p><strong>Reason-1:</strong> Identify common causes of failures, such as &#8220;CPU overload, insufficient memory, full disk, or service crash.&#8221; Basic monitoring data needs to be checked first.</p></li><li><p><strong>Act-1:</strong> Call the &#8220;Monitoring System API&#8221; to query server metrics.</p></li><li><p><strong>Observe-1</strong>: Analyze the server metrics.</p></li><li><p><strong>Reason-2:</strong> Determine the main cause. For example, if CPU utilization is abnormal, investigate which processes have high CPU usage.</p></li><li><p><strong>Act-2:</strong> Use a &#8220;Process Management Tool&#8221; to check the top processes for any abnormal services.</p></li><li><p><strong>Observe-2</strong>: Discover that the logging service is abnormal, possibly due to &#8220;log file too large&#8221; or &#8220;configuration error.&#8221; Verify these conditions before planning the next step.</p></li><li><p><strong>Reason-3:</strong> Check the logging service&#8217;s configuration and log file size.</p></li><li><p><strong>Act-3:</strong> Execute a bash command.</p></li><li><p><strong>Observe-4</strong>: Find that the log file is too large, and the configuration does not enable rotation or set a maximum log size.</p></li><li><p><strong>Reason-4:</strong> Provide a feasible solution to the O&amp;M engineer: clean up the logs, modify the configuration to enable rotation, and restart the logging service and application.</p></li></ul></li></ul><blockquote><p>The ReAct pattern gradually narrows down the problem scope, avoiding blind operations. Each step is well-founded, making it easy for O&amp;M engineers to perform secondary verification before implementing solutions, and providing a basis for subsequent reviews and preventative measures.</p></blockquote><p>ChatModelAgent leverages the capabilities of LLMs for reasoning, understanding natural language, making decisions, generating responses, and interacting with tools, <strong>acting as the &#8220;thinking&#8221; part of an intelligent agent application.</strong></p><p>Here is skeleton code for a ChatModelAgent with ReAct capabilities:</p><pre><code><strong>import "github.com/cloudwego/eino/adk"

// Create a ReAct ChatModelAgent with multiple tools
chatAgent := adk.NewChatModelAgent(ctx, &amp;adk.ChatModelAgentConfig{
    Name: "intelligent_assistant",
    Description: "An intelligent assistant capable of using multiple tools to solve complex problems",
    Instruction: "You are a professional assistant who can use the tools provided to help users solve problems",
    Model: openaiModel,
    ToolsConfig: adk.ToolsConfig{
        Tools: []tool.BaseTool{
            searchTool,
            calculatorTool,
            weatherTool,
        },
    },
})</strong></code></pre><h2>WorkflowAgents: The Sophisticated Pipeline</h2><p>Eino ADK provides the WorkflowAgents pattern, designed to coordinate the execution flow of sub-agents. It manages the running of agents through predefined logic to produce <strong>a deterministic execution process</strong>, helping to achieve a predictable and controllable output.</p><p>You can arrange and combine the following patterns as needed:</p><ul><li><p>SequentialAgent</p></li></ul><ul><li><p>ParallelAgent</p></li></ul><ul><li><p>LoopAgent</p></li></ul><p>And combine them with ChatModelAgent to construct a complete workflow pipeline. Here are descriptions of each agent in detail:</p><ul><li><p><strong>SequentialAgent:</strong> Executes the registered agents in the configuration in sequence once, and then ends. The operation follows these principles:</p><ul><li><p><strong>Linear execution:</strong> Strictly executes in the order of the SubAgents array.</p></li><li><p><strong>Passing of run results:</strong> Each agent in the configuration can get the complete input of the SequentialAgent and the output of the preceding agent.</p></li><li><p><strong>Support for early exit:</strong> If any sub-agent produces an exit/interrupt action, the entire Sequential process will be terminated immediately.</p></li></ul></li><li><p>Possible practical scenarios include:</p><ul><li><p><strong>Data ETL:</strong> ExtractAgent (extracts order data from MySQL) &#8594; TransformAgent (cleans null values, formats dates) &#8594; LoadAgent (loads into a data warehouse)</p></li><li><p><strong>CI/CD pipeline:</strong> CodeCloneAgent (pulls code from a code repository) &#8594; UnitTestAgent (runs unit tests, returns an error and analysis report when a test case fails) &#8594; CompileAgent (compiles the code) &#8594; DeployAgent (deploys to the target environment)</p></li></ul></li></ul><p>Skeleton code for a SequentialAgent:</p><pre><code><strong>import "github.com/cloudwego/eino/adk"

// Execute in sequence: create a research plan -&gt; search for materials -&gt; write a report
sequential := adk.NewSequentialAgent(ctx, &amp;adk.SequentialAgentConfig{
    Name: "research_pipeline",
    SubAgents: []adk.Agent{
        planAgent,   // Create a research plan
        searchAgent, // Search for materials
        writeAgent,  // Write a report
    },
})</strong></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!D9ZQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F475f76ec-452c-4886-8925-1d387e2a3ba6_690x408.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!D9ZQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F475f76ec-452c-4886-8925-1d387e2a3ba6_690x408.png 424w, https://substackcdn.com/image/fetch/$s_!D9ZQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F475f76ec-452c-4886-8925-1d387e2a3ba6_690x408.png 848w, https://substackcdn.com/image/fetch/$s_!D9ZQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F475f76ec-452c-4886-8925-1d387e2a3ba6_690x408.png 1272w, https://substackcdn.com/image/fetch/$s_!D9ZQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F475f76ec-452c-4886-8925-1d387e2a3ba6_690x408.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!D9ZQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F475f76ec-452c-4886-8925-1d387e2a3ba6_690x408.png" width="690" height="408" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/475f76ec-452c-4886-8925-1d387e2a3ba6_690x408.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:408,&quot;width&quot;:690,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A diagram of a process\n\nAI-generated content may be incorrect.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A diagram of a process

AI-generated content may be incorrect." title="A diagram of a process

AI-generated content may be incorrect." srcset="https://substackcdn.com/image/fetch/$s_!D9ZQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F475f76ec-452c-4886-8925-1d387e2a3ba6_690x408.png 424w, https://substackcdn.com/image/fetch/$s_!D9ZQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F475f76ec-452c-4886-8925-1d387e2a3ba6_690x408.png 848w, https://substackcdn.com/image/fetch/$s_!D9ZQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F475f76ec-452c-4886-8925-1d387e2a3ba6_690x408.png 1272w, https://substackcdn.com/image/fetch/$s_!D9ZQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F475f76ec-452c-4886-8925-1d387e2a3ba6_690x408.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p><strong>ParallelAgent:</strong> Executes the registered agents in the configuration concurrently, and ends after all agents have finished executing. The operation follows these principles:</p><ul><li><p><strong>Concurrent execution:</strong> All sub-agents start at the same time and execute in parallel in separate goroutines.</p></li><li><p><strong>Shared input:</strong> All sub-agents receive the same initial input as when the ParallelAgent was called.</p></li><li><p><strong>Waiting and result aggregation:</strong> Internally uses sync.WaitGroup to wait for all sub-agents to complete, collects the execution results of all sub-agents, and outputs them to the AsyncIterator in the order they are received.</p></li></ul></li><li><p>Possible practical scenarios include:</p><ul><li><p><strong>Multi-source data collection:</strong> MySQLCollector (collects user table) + PostgreSQLCollector (collects order table) + MongoDBCollector (collects product reviews)</p></li><li><p><strong>Multi-channel push:</strong> SocialPushAgent (pushes to social media account) + SMSPushAgent (sends SMS) + AppPushAgent (pushes to app)</p></li></ul></li></ul><p>Skeleton code for a ParallelAgent:</p><pre><code><strong>import "github.com/cloudwego/eino/adk"

// Concurrently execute sentiment analysis + keyword extraction + content summarization
parallel := adk.NewParallelAgent(ctx, &amp;adk.ParallelAgentConfig{
    Name: "multi_analysis",
    SubAgents: []adk.Agent{
        sentimentAgent, // Sentiment analysis
        keywordAgent,   // Keyword extraction
        summaryAgent,   // Content summarization
    },
})</strong></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Riw0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde7a2c9-cd48-411d-8760-6b764ff5e93f_783x387.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Riw0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde7a2c9-cd48-411d-8760-6b764ff5e93f_783x387.png 424w, https://substackcdn.com/image/fetch/$s_!Riw0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde7a2c9-cd48-411d-8760-6b764ff5e93f_783x387.png 848w, https://substackcdn.com/image/fetch/$s_!Riw0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde7a2c9-cd48-411d-8760-6b764ff5e93f_783x387.png 1272w, https://substackcdn.com/image/fetch/$s_!Riw0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde7a2c9-cd48-411d-8760-6b764ff5e93f_783x387.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Riw0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde7a2c9-cd48-411d-8760-6b764ff5e93f_783x387.png" width="783" height="387" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fde7a2c9-cd48-411d-8760-6b764ff5e93f_783x387.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:387,&quot;width&quot;:783,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A diagram of a parallel controller\n\nAI-generated content may be incorrect.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A diagram of a parallel controller

AI-generated content may be incorrect." title="A diagram of a parallel controller

AI-generated content may be incorrect." srcset="https://substackcdn.com/image/fetch/$s_!Riw0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde7a2c9-cd48-411d-8760-6b764ff5e93f_783x387.png 424w, https://substackcdn.com/image/fetch/$s_!Riw0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde7a2c9-cd48-411d-8760-6b764ff5e93f_783x387.png 848w, https://substackcdn.com/image/fetch/$s_!Riw0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde7a2c9-cd48-411d-8760-6b764ff5e93f_783x387.png 1272w, https://substackcdn.com/image/fetch/$s_!Riw0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde7a2c9-cd48-411d-8760-6b764ff5e93f_783x387.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p><strong>LoopAgent:</strong> Executes the registered agents in the configuration in sequence and loops multiple times. The operation follows these principles:</p><ul><li><p><strong>Loop execution:</strong> Repeatedly executes the SubAgents sequence, with each loop being a complete Sequential execution process.</p></li><li><p><strong>Accumulation of run results:</strong> The results of each iteration are accumulated, and the input of subsequent iterations can access all historical information.</p></li><li><p><strong>Conditional exit:</strong> Supports terminating the loop by outputting an event containing an ExitAction or reaching the maximum number of iterations. Configuring MaxIterations=0 means an infinite loop.</p></li></ul></li><li><p>Possible practical scenarios include:</p><ul><li><p><strong>Data Synchronization:</strong> CheckUpdateAgent (checks for incremental changes in the source database) &#8594; IncrementalSyncAgent (synchronizes incremental data) &#8594; VerifySyncAgent (verifies consistency)</p></li><li><p><strong>Pressure Testing:</strong> StartClientAgent (starts a test client) &#8594; SendRequestsAgent (sends requests) &#8594; CollectMetricsAgent (collects performance metrics)</p></li></ul></li></ul><p>Skeleton code for a LoopAgent:</p><pre><code><strong>import "github.com/cloudwego/eino/adk"

// Loop 5 times, with the sequence each time being: analyze the current state -&gt; propose an improvement plan -&gt; verify the improvement effect
loop := adk.NewLoopAgent(ctx, &amp;adk.LoopAgentConfig{
    Name: "iterative_optimization",
    SubAgents: []adk.Agent{
        analyzeAgent,   // Analyze the current state
        improveAgent,   // Propose an improvement plan
        validateAgent,  // Verify the improvement effect
    },
    MaxIterations: 5,
})</strong></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wIiS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84652a37-a337-468e-ba97-3fbb28b0c619_750x381.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wIiS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84652a37-a337-468e-ba97-3fbb28b0c619_750x381.png 424w, https://substackcdn.com/image/fetch/$s_!wIiS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84652a37-a337-468e-ba97-3fbb28b0c619_750x381.png 848w, https://substackcdn.com/image/fetch/$s_!wIiS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84652a37-a337-468e-ba97-3fbb28b0c619_750x381.png 1272w, https://substackcdn.com/image/fetch/$s_!wIiS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84652a37-a337-468e-ba97-3fbb28b0c619_750x381.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wIiS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84652a37-a337-468e-ba97-3fbb28b0c619_750x381.png" width="750" height="381" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/84652a37-a337-468e-ba97-3fbb28b0c619_750x381.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:381,&quot;width&quot;:750,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A diagram of a loop controller\n\nAI-generated content may be incorrect.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A diagram of a loop controller

AI-generated content may be incorrect." title="A diagram of a loop controller

AI-generated content may be incorrect." srcset="https://substackcdn.com/image/fetch/$s_!wIiS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84652a37-a337-468e-ba97-3fbb28b0c619_750x381.png 424w, https://substackcdn.com/image/fetch/$s_!wIiS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84652a37-a337-468e-ba97-3fbb28b0c619_750x381.png 848w, https://substackcdn.com/image/fetch/$s_!wIiS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84652a37-a337-468e-ba97-3fbb28b0c619_750x381.png 1272w, https://substackcdn.com/image/fetch/$s_!wIiS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84652a37-a337-468e-ba97-3fbb28b0c619_750x381.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Pre-built Multi-Agent Paradigms</h1><p>Eino ADK provides <strong>three pre-built multi-agent paradigms</strong>:</p><ul><li><p><strong>The Supervisor pattern</strong> for centralized coordination</p></li></ul><ul><li><p><strong>The Plan-Execute pattern</strong> for structured problem solving</p></li></ul><ul><li><p><strong>The DeepAgents pattern</strong> for complex workflows that require multi-step, multi-role collaboration</p></li></ul><p>Developers can use them out of the box without having to design collaboration logic from scratch. The following sections lay out the patterns along with skeleton code.</p><h3>Supervisor Pattern</h3><p>The Supervisor agent is a centralized multi-agent collaboration pattern, designed to provide a solution for the general scenario of centralized decision-making and distributed execution.</p><p>It consists of a Supervisor agent and multiple sub-agents, where:</p><ul><li><p><strong>The Supervisor agent</strong> is responsible for task allocation, summarizing the results after the sub-agents are completed, and making the next decision.</p></li></ul><ul><li><p><strong>The sub-agents</strong> focus on executing specific tasks and automatically return control of the task to the Supervisor after completion.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!z5bs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ed5f32d-cc31-4a73-93c8-3781106050fd_741x429.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!z5bs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ed5f32d-cc31-4a73-93c8-3781106050fd_741x429.png 424w, https://substackcdn.com/image/fetch/$s_!z5bs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ed5f32d-cc31-4a73-93c8-3781106050fd_741x429.png 848w, https://substackcdn.com/image/fetch/$s_!z5bs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ed5f32d-cc31-4a73-93c8-3781106050fd_741x429.png 1272w, https://substackcdn.com/image/fetch/$s_!z5bs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ed5f32d-cc31-4a73-93c8-3781106050fd_741x429.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!z5bs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ed5f32d-cc31-4a73-93c8-3781106050fd_741x429.png" width="741" height="429" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6ed5f32d-cc31-4a73-93c8-3781106050fd_741x429.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:429,&quot;width&quot;:741,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A diagram of a mission\n\nAI-generated content may be incorrect.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A diagram of a mission

AI-generated content may be incorrect." title="A diagram of a mission

AI-generated content may be incorrect." srcset="https://substackcdn.com/image/fetch/$s_!z5bs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ed5f32d-cc31-4a73-93c8-3781106050fd_741x429.png 424w, https://substackcdn.com/image/fetch/$s_!z5bs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ed5f32d-cc31-4a73-93c8-3781106050fd_741x429.png 848w, https://substackcdn.com/image/fetch/$s_!z5bs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ed5f32d-cc31-4a73-93c8-3781106050fd_741x429.png 1272w, https://substackcdn.com/image/fetch/$s_!z5bs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ed5f32d-cc31-4a73-93c8-3781106050fd_741x429.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The Supervisor pattern has the following characteristics:</p><ul><li><p><strong>Centralized control:</strong> The Supervisor uniformly manages the sub-agents and can dynamically adjust task allocation based on the input and the execution results of the sub-agents.</p></li></ul><ul><li><p><strong>Deterministic callback:</strong> After a sub-agent has finished executing, it will return the running result to the Supervisor agent, avoiding interruption of the collaboration process.</p></li></ul><ul><li><p><strong>Loosely coupled extension:</strong> Sub-agents can be developed, tested, and replaced independently, which is useful for expansion and maintenance.</p></li></ul><p>This hierarchical structure of the Supervisor pattern is suitable for scenarios that <strong>dynamically coordinate multiple specialized agents to complete complex tasks</strong>, such as:</p><ul><li><p><strong>Research project management:</strong> The Supervisor assigns research, experimentation, and report writing tasks to different sub-agents.</p></li></ul><ul><li><p><strong>Customer service processes:</strong> The Supervisor assigns tasks to technical support, after-sales, and sales sub-agents, based on the type of user problem.</p></li></ul><p>Skeleton code:</p><pre><code>import "github.com/cloudwego/eino/adk/prebuilt/supervisor"

// Research project management: create a multi-agent in supervisor mode
// Contains three sub-agents: research, experimentation, and report
supervisor, err := supervisor.New(ctx, &amp;supervisor.Config{
    SupervisorAgent: supervisorAgent,
    SubAgents: []adk.Agent{
        researchAgent,
        experimentationAgent,
        reportAgent,
    },
})</code></pre><h2>Plan-Execute-Replan Pattern</h2><p>The Plan-Execute-Replan agent is a multi-agent collaboration pattern based on the &#8220;plan-execute-reflect&#8221; paradigm (refer to the 2023 paper <strong><a href="https://arxiv.org/abs/2305.04091">Plan-and-Solve Prompting</a> </strong>by Wang L. et al). Through the collaborative work of three core intelligent agents, it achieves structured planning of tasks, tool call execution, progress evaluation, and dynamic replanning:</p><ul><li><p><strong>Planner:</strong> Generates a structured initial task plan with detailed steps based on the user&#8217;s goals.</p></li></ul><ul><li><p><strong>Executor:</strong> Executes the first step in the current plan.</p></li></ul><ul><li><p><strong>Replanner:</strong> Evaluates the execution progress and decides whether to revise the plan and continue to have the Executor run it, or to end the task.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CQOC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa864e87b-ff61-4bf5-a6be-5ce073b36ecf_771x840.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CQOC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa864e87b-ff61-4bf5-a6be-5ce073b36ecf_771x840.png 424w, https://substackcdn.com/image/fetch/$s_!CQOC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa864e87b-ff61-4bf5-a6be-5ce073b36ecf_771x840.png 848w, https://substackcdn.com/image/fetch/$s_!CQOC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa864e87b-ff61-4bf5-a6be-5ce073b36ecf_771x840.png 1272w, https://substackcdn.com/image/fetch/$s_!CQOC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa864e87b-ff61-4bf5-a6be-5ce073b36ecf_771x840.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CQOC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa864e87b-ff61-4bf5-a6be-5ce073b36ecf_771x840.png" width="771" height="840" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a864e87b-ff61-4bf5-a6be-5ce073b36ecf_771x840.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:840,&quot;width&quot;:771,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A diagram of a plan\n\nAI-generated content may be incorrect.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A diagram of a plan

AI-generated content may be incorrect." title="A diagram of a plan

AI-generated content may be incorrect." srcset="https://substackcdn.com/image/fetch/$s_!CQOC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa864e87b-ff61-4bf5-a6be-5ce073b36ecf_771x840.png 424w, https://substackcdn.com/image/fetch/$s_!CQOC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa864e87b-ff61-4bf5-a6be-5ce073b36ecf_771x840.png 848w, https://substackcdn.com/image/fetch/$s_!CQOC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa864e87b-ff61-4bf5-a6be-5ce073b36ecf_771x840.png 1272w, https://substackcdn.com/image/fetch/$s_!CQOC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa864e87b-ff61-4bf5-a6be-5ce073b36ecf_771x840.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The Plan-Execute-Replan pattern has the following characteristics:</p><ul><li><p><strong>Clear hierarchical architecture:</strong> By breaking down the task into three stages &#8211;planning, execution, and reflection/replanning &#8211; a hierarchical cognitive process is formed, which embodies the closed-loop cognitive strategy of &#8220;think before you act, then adjust based on feedback.&#8221;</p></li></ul><ul><li><p><strong>Dynamic iterative optimization:</strong> The Replanner determines in real time whether the task is complete or needs to be adjusted based on the execution results and current progress, and supports dynamic replanning. This mechanism effectively solves the bottleneck of traditional single-planning &#8211; where there is difficulty coping with environmental changes and task uncertainty &#8211; and improves the robustness and flexibility of the system.</p></li></ul><ul><li><p><strong>Clear responsibilities and loose coupling:</strong> The Plan-Execute-Replan pattern consists of multiple intelligent agents working together, and supports independent development, testing, and replacement. The modular design is useful for expansion and maintenance and conforms to engineering best practices.</p></li></ul><ul><li><p><strong>Good extensibility:</strong> It does not rely on a specific language model, tool, or agent, which is convenient for integrating diverse external resources to meet the needs of different application scenarios.</p></li></ul><p>The &#8220;plan &#8594; execute &#8594; replan&#8221; closed-loop structure of the pattern is suitable for <strong>complex task scenarios that require multi-step reasoning, dynamic adjustment, and tool integration</strong>, such as:</p><ul><li><p><strong>Complex research analysis:</strong> Decompose research questions through planning, perform multiple rounds of data retrieval and calculation, and dynamically adjust research directions and hypotheses to improve the depth and accuracy of the analysis.</p></li></ul><ul><li><p><strong>Automated workflow management:</strong> Decompose complex business processes into structured steps, combine them with a variety of tools (such as database queries, API calls, and compute services) to execute them step-by-step, and dynamically optimize the process based on execution results.</p></li></ul><ul><li><p><strong>Multi-step problem-solving:</strong> Scenarios that require step-by-step reasoning and multi-tool collaboration, such as legal consultation, technical diagnosis, and strategy formulation, to ensure that each step of execution has feedback and adjustment.</p></li></ul><ul><li><p><strong>Intelligent assistant task execution:</strong> Supports intelligent assistants to plan task steps based on user goals, call external tools to complete specific operations, and adjust subsequent plans based on replanning and thinking combined with user feedback, improving the completeness and accuracy of task completion.</p></li></ul><p>Skeleton code:</p><pre><code><strong>import "github.com/cloudwego/eino/adk/prebuilt/planexecute"

// A research assistant in Plan-Execute mode
researchAssistant := planexecute.New(ctx, &amp;planexecute.Config{
    Planner: adk.NewChatModelAgent(ctx, &amp;adk.ChatModelAgentConfig{
        Name:        "research_planner",
        Instruction: "Create a detailed research plan, including literature research, data collection, analysis methods, etc.",
        Model:       gpt4Model,
    }),
    Executor: adk.NewChatModelAgent(ctx, &amp;adk.ChatModelAgentConfig{
        Name: "research_executor",
        ToolsConfig: adk.ToolsConfig{
            Tools: []tool.BaseTool{
                scholarSearchTool,
                dataAnalysisTool,
                citationTool,
            },
        },
    }),
    Replanner: replannerAgent,
})</strong></code></pre><h2>DeepAgents Pattern</h2><p>DeepAgents is a multi-agent pattern unified under the coordination of a MainAgent. The MainAgent leverages a ChatModel with tool-calling capabilities to operate through a ReAct workflow:</p><ul><li><p>It decomposes user goals into structured to-do items and records progress through WriteTodos.</p></li></ul><ul><li><p>It selects and invokes corresponding sub-agents to execute subtasks through the unified TaskTool interface; main/sub-agent contexts are isolated to prevent intermediate steps from contaminating the main workflow.</p></li></ul><ul><li><p>It aggregates results returned by various sub-agents; when necessary, it calls WriteTodos again to update progress or perform replanning until completion.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IgxE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ed2ba3-c2f9-40ff-9327-80fa057a7ba3_747x579.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IgxE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ed2ba3-c2f9-40ff-9327-80fa057a7ba3_747x579.png 424w, https://substackcdn.com/image/fetch/$s_!IgxE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ed2ba3-c2f9-40ff-9327-80fa057a7ba3_747x579.png 848w, https://substackcdn.com/image/fetch/$s_!IgxE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ed2ba3-c2f9-40ff-9327-80fa057a7ba3_747x579.png 1272w, https://substackcdn.com/image/fetch/$s_!IgxE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ed2ba3-c2f9-40ff-9327-80fa057a7ba3_747x579.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IgxE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ed2ba3-c2f9-40ff-9327-80fa057a7ba3_747x579.png" width="747" height="579" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c5ed2ba3-c2f9-40ff-9327-80fa057a7ba3_747x579.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:579,&quot;width&quot;:747,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A diagram of a task tool\n\nAI-generated content may be incorrect.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A diagram of a task tool

AI-generated content may be incorrect." title="A diagram of a task tool

AI-generated content may be incorrect." srcset="https://substackcdn.com/image/fetch/$s_!IgxE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ed2ba3-c2f9-40ff-9327-80fa057a7ba3_747x579.png 424w, https://substackcdn.com/image/fetch/$s_!IgxE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ed2ba3-c2f9-40ff-9327-80fa057a7ba3_747x579.png 848w, https://substackcdn.com/image/fetch/$s_!IgxE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ed2ba3-c2f9-40ff-9327-80fa057a7ba3_747x579.png 1272w, https://substackcdn.com/image/fetch/$s_!IgxE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ed2ba3-c2f9-40ff-9327-80fa057a7ba3_747x579.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The characteristics of the DeepAgents pattern include:</p><ul><li><p><strong>Enhanced task decomposition and progress management</strong>: WriteTodos creates clear subtasks and milestones, making complex goals decomposable and trackable.</p></li></ul><ul><li><p><strong>More robust context isolation</strong>: Sub-agents execute in &#8220;clean&#8221; contexts while the main agent only aggregates results, reducing interference from redundant reasoning chains and tool call traces on the main workflow.</p></li></ul><ul><li><p><strong>Unified delegation interface with easy extensibility</strong>: TaskTool abstracts all sub-agents and tool capabilities into a unified calling surface, facilitating the addition or replacement of specialized sub-agents.</p></li></ul><ul><li><p><strong>Flexible closed-loop of planning and execution</strong>: Planning functions as a tool that can be called on demand; for simple tasks, unnecessary planning can be skipped, reducing LLM call costs and time consumption.</p></li></ul><ul><li><p><strong>Boundaries and trade-offs</strong>: Over-decomposition increases call frequency and costs; it places higher demands on subtask division and prompt optimization, requiring models to possess stable tool-calling and planning capabilities.</p></li></ul><p>The core value of DeepAgent lies in automatically handling complex workflows that require multi-step, multi-role collaboration. It&#8217;s not just an executor of single functions but a &#8220;project manager&#8221; with deep thinking, planning, and dynamic adjustment capabilities.</p><p>Suitable application scenarios include:</p><ul><li><p><strong>Complex business processes with multi-role collaboration</strong>: Centered around R&amp;D, testing, release, legal, and operations roles, with centralized delegation of subtasks and unified aggregation; each stage sets gateways and rollback strategies, with visible progress and retry capabilities.</p></li></ul><ul><li><p><strong>Phased management of long workflows</strong>: Planning decomposes steps such as data cleaning, validation, lineage analysis, and quality inspection; sub-agents run in isolated contexts; when exceptions occur, only relevant stages are re-run, with unified reconciliation and aggregation of outputs.</p></li></ul><ul><li><p><strong>Execution environments requiring strict context isolation</strong>: A unified interface collects materials and requests, with TaskTool routing subtasks such as legal, risk control, and finance separately; boundaries between subtasks are clearly defined and mutually invisible, with auditable progress and traceability, and failures can be retried without affecting other stages.</p></li></ul><p>Skeleton code:</p><pre><code><strong>import "github.com/cloudwego/eino/adk/prebuilt/deep"

agent, err := deep.New(ctx, &amp;deep.Config{
    Name:       "deep-agent",
    ChatModel:  gpt4Model,
    SubAgents: []adk.Agent{
        LegalAgent,
        RiskControlAgent,
        FinanceAgent,
    },
    MaxIteration: 100,
})</strong></code></pre><h1>Example: Project Manager Intelligent Agent</h1><p>Now you have covered the basics, it&#8217;s time to create an agent. Install the latest version of Eino using the following command:</p><pre><code><strong>go get github.com/cloudwego/eino@latest</strong></code></pre><p>In this example, you&#8217;ll see how to build a Project Manager intelligent agent for a variety of scenarios. It uses the Supervisor pattern, and the functions of each agent are as follows:</p><ul><li><p><strong>ResearchAgent</strong>: Responsible for researching and generating feasible solutions, supporting interruption and receiving additional context information from users to improve the accuracy of research plan generation.</p></li></ul><ul><li><p><strong>CodeAgent</strong>: Uses knowledge base tools to recall relevant knowledge as a reference to generate high-quality code.</p></li></ul><ul><li><p><strong>ReviewAgent</strong>: Uses a sequential workflow to orchestrate three steps: problem analysis, evaluation generation, and evaluation verification to review research results/coding results and provide reasonable evaluations so project managers can make decisions.</p></li></ul><ul><li><p><strong>ProjectManagerAgent</strong>: Routes and coordinates multiple sub-intelligent agents responsible for different dimensions of work based on dynamic user input.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BhOJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5da31724-dd6d-4335-bd07-d9746c470002_903x396.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BhOJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5da31724-dd6d-4335-bd07-d9746c470002_903x396.png 424w, https://substackcdn.com/image/fetch/$s_!BhOJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5da31724-dd6d-4335-bd07-d9746c470002_903x396.png 848w, https://substackcdn.com/image/fetch/$s_!BhOJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5da31724-dd6d-4335-bd07-d9746c470002_903x396.png 1272w, https://substackcdn.com/image/fetch/$s_!BhOJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5da31724-dd6d-4335-bd07-d9746c470002_903x396.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BhOJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5da31724-dd6d-4335-bd07-d9746c470002_903x396.png" width="903" height="396" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5da31724-dd6d-4335-bd07-d9746c470002_903x396.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:396,&quot;width&quot;:903,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A diagram of a diagram\n\nAI-generated content may be incorrect.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A diagram of a diagram

AI-generated content may be incorrect." title="A diagram of a diagram

AI-generated content may be incorrect." srcset="https://substackcdn.com/image/fetch/$s_!BhOJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5da31724-dd6d-4335-bd07-d9746c470002_903x396.png 424w, https://substackcdn.com/image/fetch/$s_!BhOJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5da31724-dd6d-4335-bd07-d9746c470002_903x396.png 848w, https://substackcdn.com/image/fetch/$s_!BhOJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5da31724-dd6d-4335-bd07-d9746c470002_903x396.png 1272w, https://substackcdn.com/image/fetch/$s_!BhOJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5da31724-dd6d-4335-bd07-d9746c470002_903x396.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The design of this example covers most of the concepts introduced in the article.</p><p>The core code is as follows. The complete code can be found in the <a href="https://github.com/cloudwego/eino-examples/tree/main/adk/multiagent/integration-project-manager">source code</a> provided in the eino-examples project:</p><pre><code><strong>func main() {
    ctx := context.Background()

    // Init chat model for agents
    tcm, err := openai.NewChatModel(ctx, &amp;openai.ChatModelConfig{
        APIKey:  os.Getenv("OPENAI_API_KEY"),
        Model:   os.Getenv("OPENAI_MODEL"),
        BaseURL: os.Getenv("OPENAI_BASE_URL"),
        ByAzure: func() bool {
            return os.Getenv("OPENAI_BY_AZURE") == "true"
        }(),
    })
    if err != nil {
        log.Fatal(err)
    }

    // Init research agent
    researchAgent, err := agents.NewResearchAgent(ctx, tcm)
    if err != nil {
        log.Fatal(err)
    }

    // Init code agent
    codeAgent, err := agents.NewCodeAgent(ctx, tcm)
    if err != nil {
        log.Fatal(err)
    }

    // Init technical agent
    reviewAgent, err := agents.NewReviewAgent(ctx, tcm)
    if err != nil {
        log.Fatal(err)
    }

    // Init project manager agent
    s, err := agents.NewProjectManagerAgent(ctx, tcm)
    if err != nil {
        log.Fatal(err)
    }

    // Combine agents into ADK supervisor pattern
    // Supervisor: project manager
    // Sub-agents: researcher / coder / reviewer
    supervisorAgent, err := supervisor.New(ctx, &amp;supervisor.Config{
        Supervisor: s,
        SubAgents:  []adk.Agent{researchAgent, codeAgent, reviewAgent},
    })
    if err != nil {
        log.Fatal(err)
    }

    // Init Agent runner
    runner := adk.NewRunner(ctx, adk.RunnerConfig{
        Agent:           supervisorAgent,
        EnableStreaming: true,               // enable stream output
        CheckPointStore: newInMemoryStore(),  // enable checkpoint for interrupt &amp; resume
    })

    // Replace it with your own query
    query := "please generate a simple ai chat project with python."
    checkpointID := "1"

    // Start runner with a new checkpoint id
    iter := runner.Query(ctx, query, adk.WithCheckPointID(checkpointID))

    interrupted := false
    for {
        event, ok := iter.Next()
        if !ok {
            break
        }

        if event.Err != nil {
            log.Fatal(event.Err)
        }

        if event.Action != nil &amp;&amp; event.Action.Interrupted != nil {
            interrupted = true
        }

        prints.Event(event)
    }

    if !interrupted {
        return
    }

    // interrupt and ask for additional user context
    scanner := bufio.NewScanner(os.Stdin)
    fmt.Print("\ninput additional context for web search: ")
    scanner.Scan()
    fmt.Println()
    nInput := scanner.Text()

    // Resume by checkpoint id, with additional user context injection
    iter, err = runner.Resume(
        ctx,
        checkpointID,
        adk.WithToolOptions([]tool.Option{agents.WithNewInput(nInput)}),
    )
    if err != nil {
        log.Fatal(err)
    }

    for {
        event, ok := iter.Next()
        if !ok {
            break
        }

        if event.Err != nil {
            log.Fatal(event.Err)
        }

        prints.Event(event)
    }
}</strong></code></pre><p>If you stop and think about how you would build this example with regular tools, the advantages of ADK will become apparent:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7VWV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb35db8b5-d8ee-4f48-b15c-b3f6e2e27ab1_742x636.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7VWV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb35db8b5-d8ee-4f48-b15c-b3f6e2e27ab1_742x636.png 424w, https://substackcdn.com/image/fetch/$s_!7VWV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb35db8b5-d8ee-4f48-b15c-b3f6e2e27ab1_742x636.png 848w, https://substackcdn.com/image/fetch/$s_!7VWV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb35db8b5-d8ee-4f48-b15c-b3f6e2e27ab1_742x636.png 1272w, https://substackcdn.com/image/fetch/$s_!7VWV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb35db8b5-d8ee-4f48-b15c-b3f6e2e27ab1_742x636.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7VWV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb35db8b5-d8ee-4f48-b15c-b3f6e2e27ab1_742x636.png" width="742" height="636" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b35db8b5-d8ee-4f48-b15c-b3f6e2e27ab1_742x636.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:636,&quot;width&quot;:742,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:78840,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/186279844?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb35db8b5-d8ee-4f48-b15c-b3f6e2e27ab1_742x636.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7VWV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb35db8b5-d8ee-4f48-b15c-b3f6e2e27ab1_742x636.png 424w, https://substackcdn.com/image/fetch/$s_!7VWV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb35db8b5-d8ee-4f48-b15c-b3f6e2e27ab1_742x636.png 848w, https://substackcdn.com/image/fetch/$s_!7VWV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb35db8b5-d8ee-4f48-b15c-b3f6e2e27ab1_742x636.png 1272w, https://substackcdn.com/image/fetch/$s_!7VWV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb35db8b5-d8ee-4f48-b15c-b3f6e2e27ab1_742x636.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Useful Resources</h1><p>Find out more about Eino using the following links:</p><ul><li><p><a href="https://www.cloudwego.io/zh/docs/eino/core_modules/eino_adk/">Read the docs</a></p></li></ul><ul><li><p><a href="https://github.com/cloudwego/eino/tree/main/adk">Browse source code on GitHub</a></p></li></ul><ul><li><p><a href="https://github.com/cloudwego/eino-examples/tree/main/adk">Explore examples on GitHub</a></p></li></ul><p>If you have questions about Eino, head to <a href="https://github.com/cloudwego/eino">GitHub</a> to fill out an issue.</p><p>If you want to get in touch with the Eino Team Lead, Li Pandeng, reach out to lipandeng [at] bytedance [dot] com.</p><p></p>]]></content:encoded></item><item><title><![CDATA[The Rise of the AI Architect]]></title><description><![CDATA[How AI architects can bridge AI and engineering to ship reliable decision-making systems]]></description><link>https://deepengineering.net/p/the-rise-of-the-ai-architect</link><guid isPermaLink="false">https://deepengineering.net/p/the-rise-of-the-ai-architect</guid><dc:creator><![CDATA[Richard D Avila]]></dc:creator><pubDate>Thu, 29 Jan 2026 06:47:59 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/120ba63b-e77a-4e9c-bfec-06e0652130da_800x533.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AI systems and technology are very quickly impacting so many areas of modern life. Whether it be a seemingly simple task, such as detecting a cat in an image, or the autonomous operation of a vehicle. There is also a shift toward greater acceptance of AI technologies into society. AI systems are primarily built into complex software. What makes AI-enabled systems unique is that they have, at their heart, the notion of decision making. This extended functionality puts new demands on the processes and guides for building these sorts of systems. </p><p>Architecting fundamentals can help navigate these complexities and the successful building and operation of these sorts of systems. Historically, an architect would aid in the building of a system and then would be able to sort of &#8220;walk away&#8221; or execute a new project. In this age of AI, this is simply not the case. The challenges of observability, integrated development, testing, the voice of the user, and their centrality to business outcomes place new demands on an architect.</p><h1>Challenges that impact AI enabled systems</h1><ul><li><p><strong>Complex integrations:</strong> For example, is the system going to be a hybrid cloud or on premise deployment? What dependencies exist on external software?</p></li><li><p><strong>High performance bar:</strong> the system is expected to be deployed to impact key business functions or activities that the organization shall depend upon.</p></li><li><p>Depending on the technology, using the AI/ML can incur significant costs.</p></li><li><p>If not well architected or instrumented, the system can be a challenge to troubleshoot.</p></li></ul><h1>What can architecture do to help?</h1><ul><li><p>By modeling and conducting robust conceptual designs, the unique perspective and aspects of the AI technology are identified.</p></li><li><p>The use of tactics and patterns partitions the system for better performance and meeting non-functional requirements.</p></li><li><p>Robust architecting helps keep a holistic view of the end to end system.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IPp_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bed346d-e43d-4a73-8131-8b83f4757109_1408x768.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IPp_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bed346d-e43d-4a73-8131-8b83f4757109_1408x768.png 424w, https://substackcdn.com/image/fetch/$s_!IPp_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bed346d-e43d-4a73-8131-8b83f4757109_1408x768.png 848w, https://substackcdn.com/image/fetch/$s_!IPp_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bed346d-e43d-4a73-8131-8b83f4757109_1408x768.png 1272w, https://substackcdn.com/image/fetch/$s_!IPp_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bed346d-e43d-4a73-8131-8b83f4757109_1408x768.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IPp_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bed346d-e43d-4a73-8131-8b83f4757109_1408x768.png" width="1408" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6bed346d-e43d-4a73-8131-8b83f4757109_1408x768.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/65d3dfbf-b063-46f9-a5ab-d66b020e7c59_1408x768.png&quot;,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1408,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1885468,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/186053945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57f29466-c24a-4265-83e7-02814edfcd79_1408x768.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IPp_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bed346d-e43d-4a73-8131-8b83f4757109_1408x768.png 424w, https://substackcdn.com/image/fetch/$s_!IPp_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bed346d-e43d-4a73-8131-8b83f4757109_1408x768.png 848w, https://substackcdn.com/image/fetch/$s_!IPp_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bed346d-e43d-4a73-8131-8b83f4757109_1408x768.png 1272w, https://substackcdn.com/image/fetch/$s_!IPp_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bed346d-e43d-4a73-8131-8b83f4757109_1408x768.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 1: Architecting for Success - Transforming AI Complexity, Cost, and Performance Challenges into Scalable Solutions</figcaption></figure></div><h1>The rise of AI architects</h1><p>History has conclusively shown that as modern technology progresses, the practice of architecting also evolves. We are now witnessing the rise of the AI architect. The modern AI architect will be closer to operations and must help with managing the complexity of systems. These include:</p><ul><li><p>The architect is the principal approver for the completion of acceptance gates for going from an AI prototype to a production deployment. He ensures the concept has merit, evaluates the results of the prototype, guides design of the production model, evaluates the performance of prototypes, and finally accepts the production deployment.</p></li><li><p>They integrate and synthesize solutions to balance the data science, data engineering, software development, operations, and business teams.</p></li><li><p>The architect is the principal owner of the non-functional requirements and interfaces across the AI system.</p></li><li><p>They must be involved in the observability, where they need to understand what decisions the software is making.</p></li></ul><h1>From Idea to Production Life cycle</h1><p>As AI engineering is maturing, a notional process is emerging that takes an AI idea to production. The initial stage is discovery and evaluation. It looks to determine if the data, algorithms, and compute resources exist to make a system that will have impact. The next stage is prototyping; here one now adds complexity to the software, both by adding more production domain elements, to test how well the new system would work. It is at the end of the prototype phase where a key decision is made to determine if it is warranted to take the prototype to a production environment. </p><p>With the passage out of the prototyping phase, a controlled execution of the model is done, where it is observed and monitored to ensure it is working as expected and delivering value. With the monitoring phase complete, a controlled rollout of the system is done to production, with another layer of monitoring to ensure the systems it is now impacting still also function as expected. Finally, with these gates completed, a full deployment can be done and now the system goes into a monitor and evaluation phase. The evaluation is to see if the model needs to be adapted or retrained.</p><p>One key item is as the system is now in operation &#8211; a new set of metrics for system observability need to be captured. Below are some basic ones; this should not be considered exhaustive:</p><ol><li><p>Accuracy</p></li><li><p>Data throughput</p></li><li><p>Area under the Curve</p></li><li><p>Time for processing</p></li><li><p>Cost per transaction</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QOle!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f4087a5-b236-40b7-9db6-6b474aa57b3c_1408x768.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QOle!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f4087a5-b236-40b7-9db6-6b474aa57b3c_1408x768.png 424w, https://substackcdn.com/image/fetch/$s_!QOle!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f4087a5-b236-40b7-9db6-6b474aa57b3c_1408x768.png 848w, https://substackcdn.com/image/fetch/$s_!QOle!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f4087a5-b236-40b7-9db6-6b474aa57b3c_1408x768.png 1272w, https://substackcdn.com/image/fetch/$s_!QOle!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f4087a5-b236-40b7-9db6-6b474aa57b3c_1408x768.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QOle!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f4087a5-b236-40b7-9db6-6b474aa57b3c_1408x768.png" width="1408" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9f4087a5-b236-40b7-9db6-6b474aa57b3c_1408x768.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8e8df155-fb3c-4e91-8c36-248280d2157b_1408x768.png&quot;,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1408,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1593480,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/186053945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e8df155-fb3c-4e91-8c36-248280d2157b_1408x768.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QOle!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f4087a5-b236-40b7-9db6-6b474aa57b3c_1408x768.png 424w, https://substackcdn.com/image/fetch/$s_!QOle!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f4087a5-b236-40b7-9db6-6b474aa57b3c_1408x768.png 848w, https://substackcdn.com/image/fetch/$s_!QOle!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f4087a5-b236-40b7-9db6-6b474aa57b3c_1408x768.png 1272w, https://substackcdn.com/image/fetch/$s_!QOle!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f4087a5-b236-40b7-9db6-6b474aa57b3c_1408x768.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2: The AI Engineering Lifecycle - From Concept to Continuous Observability</figcaption></figure></div><p>How often these metrics should be reviewed really depends on how time dependent AI performance is to the enterprise. That said, these observability metrics should be reviewed frequently by the operations teams, and on a weekly basis or sooner by the other stakeholders, to see how AI model performance is impacting their areas of responsibilities.</p><h1>Specific areas an AI architect can influence</h1><p>As has been mentioned, the AI architect needs to have a solid understanding of, and be able to communicate in the language of, AI/ML and data science topics. This includes the perspective in understanding the role of non-functional requirements as applied to AI/ML systems. How to extend their knowledge for architecture modeling should incorporate systems where there is to be a sort of non-human decision making. They will also need to fortify and understand how users and stakeholders of the system are to be impacted, aided, and potential friction points. They will need to be close to guiding and evaluating the rapid prototyping in support of software development. The architect needs to consider interfaces and how their management will impact the decision making of the system. The use of AI/ML technologies inherently add a layer of complexity. The architect acts at his own peril with the notion that a simple AI application can readily be made in a less rigorous manner. Adding a layer of AI into almost any application raises the complexity significantly.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jkFh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a8e7a1d-78f2-4fe5-b982-3fc2155d5d68_1408x768.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jkFh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a8e7a1d-78f2-4fe5-b982-3fc2155d5d68_1408x768.png 424w, https://substackcdn.com/image/fetch/$s_!jkFh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a8e7a1d-78f2-4fe5-b982-3fc2155d5d68_1408x768.png 848w, https://substackcdn.com/image/fetch/$s_!jkFh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a8e7a1d-78f2-4fe5-b982-3fc2155d5d68_1408x768.png 1272w, https://substackcdn.com/image/fetch/$s_!jkFh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a8e7a1d-78f2-4fe5-b982-3fc2155d5d68_1408x768.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jkFh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a8e7a1d-78f2-4fe5-b982-3fc2155d5d68_1408x768.png" width="1408" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2a8e7a1d-78f2-4fe5-b982-3fc2155d5d68_1408x768.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/acf66d0c-9121-49a3-bad5-16efe9a09a5f_1408x768.png&quot;,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1408,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1807524,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/186053945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facf66d0c-9121-49a3-bad5-16efe9a09a5f_1408x768.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jkFh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a8e7a1d-78f2-4fe5-b982-3fc2155d5d68_1408x768.png 424w, https://substackcdn.com/image/fetch/$s_!jkFh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a8e7a1d-78f2-4fe5-b982-3fc2155d5d68_1408x768.png 848w, https://substackcdn.com/image/fetch/$s_!jkFh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a8e7a1d-78f2-4fe5-b982-3fc2155d5d68_1408x768.png 1272w, https://substackcdn.com/image/fetch/$s_!jkFh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a8e7a1d-78f2-4fe5-b982-3fc2155d5d68_1408x768.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 3: The Central Nexus of AI Engineering - Orchestrating the Critical Intersections of Data, Development, Operations, and Compliance.</figcaption></figure></div><p>The AI architect must by necessity straddle AI/ML and software engineering. This is to ensure the architect can navigate the alphabet soup of techniques, algorithmic aspects, and technologies that exist. It is very easy for an AI architect to be overcome by the various other roles needed to deploy modern AI systems. In many enterprises, there are typically several functional teams: Data Team, Development Team, ML Ops, Legal/Compliance. Below are some of the major facets that the architect influences and needs to be involved with:</p><h3>Data Team</h3><p>The architect works with the data team to ensure that the product question can indeed be answered by the data that exists or is to be used. He is pivotal in levying requirements for the speed, format, and consistency checks that must be met.</p><h3>Development Team</h3><p>The architect works to ensure top-level designs, interfaces, and non-functional requirements are being met by the designs being created. He is more in a reviewer mode. He also aids in clarifying engineering requirements and providing guidance to address design questions and challenges.</p><h3>MLOps Team</h3><p>The architect, in this role, defines how well the production system is required to perform, and how the system is to be instrumented and monitored for correct execution. He will also define the canaries and fail-safe mechanisms to ensure issues with the AI components do not compromise the full production environment. The mechanisms for model and system updating are also laid out by the architect.</p><h3>Legal and Compliance Team</h3><p>In this capacity, the architect provides the technical insight, design guidance, and evidence that the AI system is meeting the legal and compliance needs of the enterprise. The architect also oversees re-design activities and troubleshooting to ensure legal and compliance aspects are indeed met.</p><h3>Incident Run Book</h3><p>There needs to exist a run book that the architect is a principal in its development. A run book should look to use a layered manner to address an error in the AI model or its interfaces. Clear mechanisms and architecture need to exist to be able to disengage the model from a production system, and put the production system into a nominal configuration. The run book should also provide for clear expected outputs of the different stages of the system &#8211; that is, it should be documented what &#8220;correct&#8221; looks like and why at each stage to enable quicker troubleshooting. The error handling within the system should also be readily indexed in the run book so that system, data, and model tracing can occur. Finally, the run book should also include clear traceability to software repositories, configuration information, and points of contact.</p><h1>Conclusion</h1><p>These are exciting times for being an AI architect. The field is in its infancy and there are many excellent and challenging domains where one can make a significant impact. In this new era one needs to accept that there will be constant learning and adaptation. As the techniques and concepts of AI/ML continue to increase, the combinations and applications simply keep growing. That said, having a solid foundational knowledge of AI/ML concepts shall mitigate short-term knowledge gaps. Are you ready to become an AI architect?</p>]]></content:encoded></item><item><title><![CDATA[Thinking Computationally]]></title><description><![CDATA[The complete Chapter 1: Thinking Computationally from The C++ Programmer's Mindset by Sam Morley, Packt 2025.]]></description><link>https://deepengineering.net/p/thinking-computationally</link><guid isPermaLink="false">https://deepengineering.net/p/thinking-computationally</guid><dc:creator><![CDATA[Sam Morley]]></dc:creator><pubDate>Thu, 22 Jan 2026 08:31:44 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/7667fcd3-2279-4e00-a74d-ff1459f26896_800x533.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="file-embed-wrapper" data-component-name="FileToDOM"><div class="file-embed-container-reader"><div class="file-embed-container-top"><image class="file-embed-thumbnail-default" src="https://substackcdn.com/image/fetch/$s_!0Cy0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack.com%2Fimg%2Fattachment_icon.svg"></image><div class="file-embed-details"><div class="file-embed-details-h1">Chapter 1: Thinking Computationally (from The C++ Programmer's Mindset By Sam Morley) &#169; Packt (All Rights Reserved)</div><div class="file-embed-details-h2">1.08MB &#8729; PDF file</div></div><a class="file-embed-button wide" href="https://deepengineering.substack.com/api/v1/file/759e4df6-4d3b-4025-8187-7f8cc1d62ccd.pdf"><span class="file-embed-button-text">Download</span></a></div><div class="file-embed-description">Solving problems is a central part of being a programmer, as well as a useful skill for everyday life. The methodology is broadly the same wherever you look: identify smaller, more tractable challenges; realize these as instances of a general class of problem; solve the intermediate challenges; and put everything together as a sequence of simple steps to solve the larger problem. In computer science, we call this computational thinking.

This chapter serves as an introduction to the basic components of computational thinking at a high level. The objectives are to lay the foundation for more detailed analysis and in-depth examples later in the book. The first part of the chapter introduces the four components of computational thinking (decomposition, abstraction, pattern recognition, and algorithm design). The second half of the chapter deals with C++ specifically and identifies some aspects of the C++ language and standard library that can not only help implement efficient solutions, but also help you think about the problems themselves.

It is important to remember that the four components of computational thinking are not a step-by-step guide to solving problems. Learning how and when these different components come together to deliver a solution relies on a good knowledge of the tools and methodologies available to you as the solver, and on your past experience. This chapter will help you get started with building the necessary foundations of the theory and set the stage for building out some basic examples to get you started with tackling larger and more complex problems later.

Solving problems is an iterative process. There will be many failed attempts and false starts. This is a necessary part of the process. The last part of the chapter deals with good software practices that will enable you to iterate quickly and easily on your designs and arrive at a correct and usable solution more quickly.

In this chapter, we&#8217;re going to cover the following main topics:
&#8226;&#9;The components of computational thinking
&#8226;&#9;Decomposing problems
&#8226;&#9;Building abstractions and recognizing common patterns
&#8226;&#9;Understanding algorithms
&#8226;&#9;Using modern C++ and good practice

Download to continue reading...</div><a class="file-embed-button narrow" href="https://deepengineering.substack.com/api/v1/file/759e4df6-4d3b-4025-8187-7f8cc1d62ccd.pdf"><span class="file-embed-button-text">Download</span></a></div></div><div><hr></div><p>To go deeper check out <em><strong><a href="https://www.packtpub.com/en-us/product/the-c-programmers-mindset-9781835888438">The C++ Programmer&#8217;s Mindset</a></strong> </em>(Sam Morley, Packt, 1st ed., Nov 2025). The book introduces computational thinking as a practical framework&#8212;decomposition, abstraction, and pattern recognition&#8212;and shows how to apply it using modern C++ features to build solutions that are maintainable, efficient, and reusable. Across small examples and a larger case study, Morley covers using algorithms and data structures effectively, designing modular code, analyzing performance, and scaling work with concurrency, GPUs, and profiling tools&#8212;aimed at intermediate C++ developers who want to strengthen both their technical toolkit and the way they approach complex software challenges.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rpnO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rpnO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!rpnO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!rpnO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!rpnO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rpnO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775" width="332" height="409.5274725274725" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/207a7e01-560e-4438-ad85-562040511902_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:332,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The C++ Programmer's Mindset&quot;,&quot;title&quot;:&quot;The C++ Programmer's Mindset&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The C++ Programmer's Mindset" title="The C++ Programmer's Mindset" srcset="https://substackcdn.com/image/fetch/$s_!rpnO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!rpnO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!rpnO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!rpnO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 1456w" sizes="100vw" loading="lazy" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here&#8217;s what some readers have said:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xpla!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xpla!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 424w, https://substackcdn.com/image/fetch/$s_!xpla!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 848w, https://substackcdn.com/image/fetch/$s_!xpla!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 1272w, https://substackcdn.com/image/fetch/$s_!xpla!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xpla!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png" width="857" height="607" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:607,&quot;width&quot;:857,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:138673,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/185273208?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!xpla!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 424w, https://substackcdn.com/image/fetch/$s_!xpla!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 848w, https://substackcdn.com/image/fetch/$s_!xpla!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 1272w, https://substackcdn.com/image/fetch/$s_!xpla!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!doxG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!doxG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 424w, https://substackcdn.com/image/fetch/$s_!doxG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 848w, https://substackcdn.com/image/fetch/$s_!doxG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 1272w, https://substackcdn.com/image/fetch/$s_!doxG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!doxG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png" width="852" height="422" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:422,&quot;width&quot;:852,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:100846,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/185273208?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!doxG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 424w, https://substackcdn.com/image/fetch/$s_!doxG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 848w, https://substackcdn.com/image/fetch/$s_!doxG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 1272w, https://substackcdn.com/image/fetch/$s_!doxG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ATit!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ATit!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 424w, https://substackcdn.com/image/fetch/$s_!ATit!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 848w, https://substackcdn.com/image/fetch/$s_!ATit!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 1272w, https://substackcdn.com/image/fetch/$s_!ATit!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ATit!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png" width="862" height="662" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:662,&quot;width&quot;:862,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:134677,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/185273208?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!ATit!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 424w, https://substackcdn.com/image/fetch/$s_!ATit!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 848w, https://substackcdn.com/image/fetch/$s_!ATit!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 1272w, https://substackcdn.com/image/fetch/$s_!ATit!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>]]></content:encoded></item><item><title><![CDATA[Fundamentals of AI System Architecture]]></title><description><![CDATA[The complete "Chapter 1: Fundamentals of AI System Architecture" from the book, Architecting AI Software Systems by Richard D Avila and Imran Ahmad]]></description><link>https://deepengineering.net/p/fundamentals-of-ai-system-architecture</link><guid isPermaLink="false">https://deepengineering.net/p/fundamentals-of-ai-system-architecture</guid><dc:creator><![CDATA[Imran Ahmad]]></dc:creator><pubDate>Thu, 04 Dec 2025 05:23:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!mCUY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe338efee-2dce-4923-b2a9-dc7261e4317a_761x541.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The recent surge of public interest in <strong>Artificial Intelligence</strong> (<strong>AI</strong>), particularly with the rise of generative AI, has ignited a wave of excitement and demand for comprehensive AI solutions. This heightened interest extends beyond tech enthusiasts and researchers to businesses, governments, and individuals seeking to harness AI&#8217;s power to solve real-world problems and enhance their capabilities. In this landscape, the architecture of AI systems, which defines their structure, components, and interactions, plays a pivotal role in shaping the development and deployment of effective AI solutions.</p><p>AI has emerged as a transformative force, revolutionizing industries and reshaping the way we interact with technology and the world around us. At its core, AI refers to computational models that mimic human cognitive functions, including learning from data, recognizing patterns, making decisions, and even interacting with their environment. This revolutionary technology spans a wide spectrum, from simple rule-based systems to sophisticated deep learning models, each with unique applications and capabilities.</p><p>A major aspect of any AI system is that the results of the inference being done need to be relevant and trusted. To ensure trust is gained and maintained, the use of strong architecture is paramount. One not only architects the technology but also how the technology is going to be used, managed, and evaluated by the span of stakeholders. The stakeholders need to be able to pinpoint issues, rapidly correct model parameters, and deploy changes in a deliberate and rapid manner. In more common parlance, the architecting and supporting processes can be described as &#8220;guard rails.&#8221; How one employs guard rails is very specific to the domain and use case that is to use the AI technology. There are classes of guard rails that can be discussed &#8211; for example, the use of canaries to judge model correctness from a known gold standard, time and data flow metrics to judge model performance, and the use of filters and robust data quality checks so that only consistent and correct data enters the system. Another class of guardrails is human system interfaces, such as alerting frameworks to classify errors and monitors, the use of troubleshooting tools, and preset protocols for handling unexpected errors. Written procedures or guidance from modeling allow for the maintenance of a system without the need to call upon the model developer to do troubleshooting.</p><p>Trust is a paramount consideration for system success, so one needs to architect a system with that in mind. In many ways, the presentation and lessons learned described in this book look to ensure trust in an AI system.</p><p>This chapter highlights, in a broad sense, the key aspects of AI architecture considerations that drive a successful AI implementation. The topics are as follows:</p><ul><li><p>Introduction and key AI concepts</p></li><li><p>Components of an AI system</p></li><li><p>AI technologies and microservices</p></li><li><p>AI systems and technical considerations</p></li><li><p>Deployment considerations</p></li></ul><div><hr></div><h1>Introduction to AI systems: architecting the future of intelligence</h1><p>AI systems are the embodiment of AI, acting as the engines that power intelligent applications and services. These systems are intricate constructs, meticulously designed to perform a diverse range of tasks, from image recognition and natural language processing to autonomous decision-making and complex problem-solving.</p><p>The architecture of an AI system functions as a detailed technical blueprint, specifying its structural organization and the precise interactions between its various components. These components include the following:</p><ul><li><p><strong>Hardware infrastructure</strong>: CPUs for general processing, GPUs for parallel computation, TPUs for tensor operations, and specialized AI accelerators</p></li><li><p><strong>Software frameworks</strong>: TensorFlow, PyTorch, JAX, and other libraries that enable model development</p></li><li><p><strong>Algorithmic implementations</strong>: Machine learning algorithms, neural network architectures, and inference engines</p></li><li><p><strong>Data pipelines</strong>: ETL processes, feature stores, and data management systems</p></li></ul><p>All these elements work in a coordinated operation to enable the system to fulfill its designed objectives efficiently and reliably.</p><p>A well-architected AI system achieves several critical technical requirements:</p><ul><li><p><strong>Optimal performance</strong>: Maximizes computational efficiency to deliver responsive and accurate results with minimal latency. This involves an optimized model design, efficient resource allocation, and hardware-aware implementations that fully utilize available computing capabilities.</p></li><li><p><strong>Scalability</strong>: Handles growing workloads and expanding datasets through both horizontal scaling (adding more machines) and vertical scaling (adding more powerful machines) without performance degradation. Modern AI architectures must accommodate increasing data volumes, user bases, and computational demands.</p></li><li><p><strong>Efficiency</strong>: Reduces computational resource consumption, energy usage, and operational costs through techniques such as model quantization, knowledge distillation, and optimized inference paths. Efficient AI systems minimize their resource footprint while maintaining functional effectiveness.</p></li><li><p><strong>Reliability</strong>: Ensures consistent operation with high-availability metrics, even when facing unexpected data patterns, input variations, or system failures. This requires robust error handling, graceful degradation capabilities, and comprehensive monitoring systems. Given that AI technologies can be both deterministic and non-deterministic, consideration must be given to allow for human intervention. This intervention needs to span the gamut from simple monitoring to a full suite of testing infrastructure.</p></li><li><p><strong>Security</strong>: Implements comprehensive data protection measures and defends against adversarial attacks, data poisoning, and model vulnerabilities. AI systems must maintain data confidentiality and integrity, and be resilient against both traditional cybersecurity threats and AI-specific attacks.</p></li><li><p><strong>Explainability</strong>: Provides transparent visibility into algorithmic decision processes, supporting regulatory compliance, user trust, and system debugging. Modern AI architectures must balance performance with interpretability to meet growing demands for AI transparency.</p></li></ul><p>The field of AI is constantly evolving, with new architectures and technologies emerging at a rapid pace. As we delve deeper into this fascinating domain, we will explore the various types of AI systems, their underlying principles, and the diverse applications that are shaping the future of technology and society.</p><h2>What is an AI system?</h2><p>An AI system is a computational model or a collection of models designed to perform tasks that typically require human intelligence. These systems are powered by algorithms and data, enabling them to learn from experience, adapt to new information, and make decisions or predictions.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!u0lW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096b15fa-95f0-474e-a1e6-6973015e02e0_208x769.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!u0lW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096b15fa-95f0-474e-a1e6-6973015e02e0_208x769.png 424w, https://substackcdn.com/image/fetch/$s_!u0lW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096b15fa-95f0-474e-a1e6-6973015e02e0_208x769.png 848w, https://substackcdn.com/image/fetch/$s_!u0lW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096b15fa-95f0-474e-a1e6-6973015e02e0_208x769.png 1272w, https://substackcdn.com/image/fetch/$s_!u0lW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096b15fa-95f0-474e-a1e6-6973015e02e0_208x769.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!u0lW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096b15fa-95f0-474e-a1e6-6973015e02e0_208x769.png" width="208" height="769" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/096b15fa-95f0-474e-a1e6-6973015e02e0_208x769.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:769,&quot;width&quot;:208,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!u0lW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096b15fa-95f0-474e-a1e6-6973015e02e0_208x769.png 424w, https://substackcdn.com/image/fetch/$s_!u0lW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096b15fa-95f0-474e-a1e6-6973015e02e0_208x769.png 848w, https://substackcdn.com/image/fetch/$s_!u0lW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096b15fa-95f0-474e-a1e6-6973015e02e0_208x769.png 1272w, https://substackcdn.com/image/fetch/$s_!u0lW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096b15fa-95f0-474e-a1e6-6973015e02e0_208x769.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 1.4: AI technology stack</figcaption></figure></div><p>From an implementation perspective, AI systems typically consist of several key layers:</p><ol><li><p><strong>Hardware</strong>: Encompasses compute resources such as CPU, GPU, TPUs, full-spectrum storage, and networking</p></li><li><p><strong>Data layer</strong>: Handles data ingestion, storage, preprocessing, and feature engineering</p></li><li><p><strong>Model layer</strong>: Contains the trained machine learning or deep learning models</p></li><li><p><strong>Inference layer</strong>: Manages the execution of models against new data inputs</p></li><li><p><strong>Application layer</strong>: Integrates AI capabilities into user-facing applications</p></li><li><p><strong>Monitoring layer</strong>: Tracks system performance, data drift, and model health</p></li></ol><p>AI systems can be classified into two broad categories:</p><ul><li><p><strong>Narrow AI (weak AI)</strong>: These systems are designed to excel at specific tasks within a limited domain. Examples include image recognition software, spam filters, and recommendation engines. While they may be highly proficient at their designated tasks, they lack the ability to generalize their knowledge in other areas.</p></li><li><p><strong>General AI (strong AI)</strong>: This is a theoretical concept of an AI system that possesses human-level intelligence and can perform any intellectual task that a human can. It would have the ability to reason, plan, solve problems, learn from experience, and understand complex ideas across diverse domains. While general AI remains a distant goal, significant progress has been made in developing systems with increasingly sophisticated capabilities.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mCUY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe338efee-2dce-4923-b2a9-dc7261e4317a_761x541.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mCUY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe338efee-2dce-4923-b2a9-dc7261e4317a_761x541.png 424w, https://substackcdn.com/image/fetch/$s_!mCUY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe338efee-2dce-4923-b2a9-dc7261e4317a_761x541.png 848w, https://substackcdn.com/image/fetch/$s_!mCUY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe338efee-2dce-4923-b2a9-dc7261e4317a_761x541.png 1272w, https://substackcdn.com/image/fetch/$s_!mCUY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe338efee-2dce-4923-b2a9-dc7261e4317a_761x541.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mCUY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe338efee-2dce-4923-b2a9-dc7261e4317a_761x541.png" width="761" height="541" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e338efee-2dce-4923-b2a9-dc7261e4317a_761x541.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:541,&quot;width&quot;:761,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A screenshot of a computer\n\nAI-generated content may be incorrect.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A screenshot of a computer

AI-generated content may be incorrect." title="A screenshot of a computer

AI-generated content may be incorrect." srcset="https://substackcdn.com/image/fetch/$s_!mCUY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe338efee-2dce-4923-b2a9-dc7261e4317a_761x541.png 424w, https://substackcdn.com/image/fetch/$s_!mCUY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe338efee-2dce-4923-b2a9-dc7261e4317a_761x541.png 848w, https://substackcdn.com/image/fetch/$s_!mCUY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe338efee-2dce-4923-b2a9-dc7261e4317a_761x541.png 1272w, https://substackcdn.com/image/fetch/$s_!mCUY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe338efee-2dce-4923-b2a9-dc7261e4317a_761x541.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 1.5: Classification of AI systems</figcaption></figure></div><p>The pervasive impact of AI infrastructure: powering intelligent solutions across industries</p><p>Well-architected AI infrastructure, encompassing the hardware, software, and networks that support AI applications, is the driving force behind the transformative impact of AI across industries. This infrastructure enables the deployment and scaling of AI models, algorithms, and frameworks, unlocking their full potential to address complex challenges and deliver innovative solutions.</p><ul><li><p><strong>Healthcare:</strong></p><ul><li><p><strong>Accelerated medical image analysis</strong>: High-performance computing clusters and specialized hardware accelerators enable rapid processing of medical images, facilitating faster and more accurate diagnosis.</p></li><li><p><strong>Data-driven insights</strong>: Scalable storage and processing infrastructure empowers AI-driven analytics on vast patient datasets, leading to personalized treatment plans and improved patient outcomes.</p></li><li><p><strong>Real-time monitoring</strong>: Cloud-based AI infrastructure enables continuous monitoring of patient vitals and other health data, facilitating timely interventions and proactive care.</p></li></ul></li><li><p><strong>Finance:</strong></p><ul><li><p><strong>Robust fraud detection:</strong> Distributed computing and real-time analytics platforms empower AI models to detect fraudulent transactions with greater accuracy and speed, protecting financial institutions and consumers.</p></li><li><p><strong>Optimized trading strategies</strong>: High-frequency trading algorithms leverage low-latency networks and powerful computational resources to execute trades with precision and efficiency, maximizing returns.</p></li><li><p><strong>Personalized financial services:</strong> Cloud-based AI infrastructure enables the deployment of robo-advisors and other AI-powered tools that provide tailored financial advice and services to individuals.</p></li></ul></li><li><p><strong>Autonomous vehicles:</strong></p><ul><li><p><strong>Real-time sensor fusion:</strong> High-throughput data pipelines and edge computing infrastructure enable the rapid processing of sensor data from cameras, lidar, radar, and other sources, ensuring timely decision-making for autonomous vehicles.</p></li><li><p><strong>Enhanced object recognition:</strong> Deep learning models trained on massive datasets and deployed on specialized hardware accelerators enable accurate and reliable identification of objects in the environment.</p></li><li><p><strong>Optimized navigation:</strong> Cloud-based mapping and navigation services, combined with onboard AI processing, provide autonomous vehicles with real-time information and guidance for safe and efficient navigation.</p></li></ul></li></ul><p>The continued development and optimization of AI infrastructure will play a crucial role in realizing the full potential of AI across industries. By providing the foundation for performant and scalable AI solutions, this infrastructure is poised to transform the way we live and work.</p><h2>Key components of AI system architectures</h2><p>AI systems, in their essence, are complex structures designed to emulate human cognitive abilities such as learning, reasoning, and problem-solving. To achieve these capabilities, AI systems rely on a well-defined architecture comprising several interconnected components, each playing a crucial role in the overall functioning of the system. Understanding these key components is fundamental to comprehending the inner workings and potential of AI.</p><ul><li><p><strong>Data components</strong>: Data serves as the lifeblood of any AI system, acting as the raw material upon which the system learns and improves. Data can exist in multiple forms:</p><ul><li><p><strong>Structured data</strong>: Organized in predefined formats such as databases and spreadsheets</p></li><li><p><strong>Semi-structured data</strong>: Partially organized information such as JSON or XML files</p></li><li><p><strong>Unstructured data</strong>: Raw information, including text documents, images, audio recordings, and video files</p></li></ul></li></ul><p>The quality, quantity, and relevance of the data significantly impact the AI system&#8217;s performance and ability to generalize to new situations.</p><ul><li><p><strong>Algorithmic frameworks:</strong> Algorithms are engines driving AI systems, providing instructions and logic for processing data and generating intelligent outputs. Machine learning algorithms, a subset of AI algorithms, empower systems to learn patterns and relationships from data, enabling them to make predictions, classifications, or decisions. Common algorithmic approaches in production AI systems include the following:</p><ul><li><p><strong>Traditional machine learning</strong>: Linear regression, random forests, gradient boosting, and support vector machines</p></li><li><p><strong>Deep learning</strong>: <strong>Convolutional neural networks</strong> (<strong>CNN</strong>s), <strong>Recurrent Neural Networks</strong> (<strong>RNN</strong>s), transformers, and graph neural networks</p></li><li><p><strong>Reinforcement learning</strong>: Q-learning, policy gradient methods, and actor-critic architectures</p></li></ul></li></ul><p>The selection of appropriate algorithms depends on the specific problem domain, available data characteristics, and performance requirements.</p><ul><li><p><strong>Model architectures:</strong> Models represent the culmination of the learning process in AI systems. They are mathematical representations of the knowledge extracted from data, encapsulating the patterns, relationships, and insights discovered by the algorithms. These models can be simple or complex, depending on the nature of the task and the algorithm used. Model architectures range between the following:</p><ul><li><p><strong>Simple linear models</strong>: Easily interpretable but limited in capability</p></li><li><p><strong>Ensemble models</strong>: Combining multiple simpler models for improved performance</p></li><li><p><strong>Deep neural networks</strong>: Complex architectures with millions or billions of parameters</p></li></ul></li></ul><p>Once trained, models are used to make predictions or decisions on new, unseen data.</p><ul><li><p><strong>Infrastructure:</strong> The infrastructure component encompasses the hardware and software resources that provide the computational power and environment necessary for AI systems to operate. Key infrastructure elements include the following:</p><ul><li><p><strong>Computational resources</strong>: High-performance servers, specialized AI accelerators (GPUs, TPUs, FPGAs), and distributed computing clusters</p></li><li><p><strong>Storage systems</strong>: High-throughput, scalable storage for training data and model artifacts</p></li><li><p><strong>Networking components</strong>: Low-latency interconnects for distributed training and inference</p></li></ul></li><li><p><strong>Development frameworks</strong>: Software libraries such as TensorFlow, PyTorch, and Hugging Face that streamline AI development and deployment.</p></li></ul><p>Understanding these key components and their interactions provides a solid foundation for comprehending the complex landscape of AI system architectures. By carefully designing and optimizing each component, researchers and engineers can build AI systems that are capable of tackling a wide range of tasks and applications, from image recognition and natural language processing to autonomous driving and drug discovery. The integration of AI capabilities into existing software stacks requires thoughtful architectural considerations to successfully incorporate intelligence while addressing the unique requirements that AI components introduce. These specific requirements and architectural approaches form the central focus of this book. Due to the complexity of AI systems, the nature of the deployment approach is paramount. The next section will discuss the use of microservice architectures that provide a balance between performance and modularity.</p><div><hr></div><h1>Microservice architectures: a modular approach to building complex AI systems</h1><p>As <strong>AI</strong> systems grow in complexity, traditional monolithic architectures can become unwieldy, hindering development speed and flexibility. Microservice architectures offer a compelling alternative by breaking down these complex systems into smaller, independent services. Each microservice focuses on a specific function and communicates with others through well-defined APIs.</p><h2>Advantages of microservices for AI</h2><ul><li><p><strong>Enhanced agility and flexibility</strong>: Teams can independently develop, deploy, and update each microservice, using the most suitable technologies and programming languages for each task. This accelerates development cycles and allows for easier experimentation and innovation.</p></li><li><p><strong>Improved scalability</strong>: Microservices can be scaled horizontally to meet specific demand, ensuring optimal resource utilization. For example, a service handling image processing can be scaled independently of a service responsible for natural language understanding.</p></li><li><p><strong>Increased resilience and fault isolation</strong>: If a microservice fails, the impact is localized, minimizing disruption to the entire system. This enhances overall reliability and simplifies troubleshooting.</p></li><li><p><strong>Technological diversity</strong>: Microservice architectures empower teams to leverage the best tools for each task, promoting innovation and allowing for gradual technology upgrades.</p></li></ul><h2>Challenges of microservice architectures</h2><ul><li><p><strong>Increased complexity</strong>: Managing a multitude of services and their interactions requires robust orchestration and monitoring tools. Service discovery, load balancing, and failure handling become critical considerations.</p></li><li><p><strong>Communication overhead</strong>: Excessive inter-service communication can introduce latency and impact overall performance. The careful design of APIs and communication patterns is essential to mitigate this issue.</p></li><li><p><strong>Data consistency</strong>: Maintaining data consistency across distributed services can be challenging. Strategies such as eventual consistency or distributed transactions may be required to ensure data integrity.</p></li></ul><h2>Real-world example: conversational AI microservices implementation</h2><p>To illustrate how a microservices approach can streamline a conversational AI solution, let us examine a practical example that demonstrates how these principles come to life. This section explores a conversational AI system &#8211; such as a chatbot or virtual assistant &#8211; built using a four-service microservices architecture with an API gateway.</p><h3>The four core microservices</h3><p><em>Figure 1.6</em> illustrates the high-level design of our conversational AI system:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sWP_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e45309e-9868-480f-ba2a-b1b58ef387d2_823x579.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sWP_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e45309e-9868-480f-ba2a-b1b58ef387d2_823x579.png 424w, https://substackcdn.com/image/fetch/$s_!sWP_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e45309e-9868-480f-ba2a-b1b58ef387d2_823x579.png 848w, https://substackcdn.com/image/fetch/$s_!sWP_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e45309e-9868-480f-ba2a-b1b58ef387d2_823x579.png 1272w, https://substackcdn.com/image/fetch/$s_!sWP_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e45309e-9868-480f-ba2a-b1b58ef387d2_823x579.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sWP_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e45309e-9868-480f-ba2a-b1b58ef387d2_823x579.png" width="823" height="579" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3e45309e-9868-480f-ba2a-b1b58ef387d2_823x579.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:579,&quot;width&quot;:823,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A diagram of a service\n\nAI-generated content may be incorrect.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A diagram of a service

AI-generated content may be incorrect." title="A diagram of a service

AI-generated content may be incorrect." srcset="https://substackcdn.com/image/fetch/$s_!sWP_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e45309e-9868-480f-ba2a-b1b58ef387d2_823x579.png 424w, https://substackcdn.com/image/fetch/$s_!sWP_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e45309e-9868-480f-ba2a-b1b58ef387d2_823x579.png 848w, https://substackcdn.com/image/fetch/$s_!sWP_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e45309e-9868-480f-ba2a-b1b58ef387d2_823x579.png 1272w, https://substackcdn.com/image/fetch/$s_!sWP_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e45309e-9868-480f-ba2a-b1b58ef387d2_823x579.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 1.6: Conversational AI microservices</figcaption></figure></div><p>The architecture consists of four core specialized services plus an API gateway:</p><ol><li><p>Language understanding service:</p><ul><li><p><strong>Primary functions</strong>: Intent classification, entity identification/extraction, and hosting of NLP models.</p></li><li><p><strong>Data and models</strong>: References one or more NLP model databases (for example, transformer-based classifiers).</p></li><li><p><strong>Key interactions</strong>: Receives the user&#8217;s text (through the API gateway), determines the user&#8217;s intent (e.g., &#8220;Check account balance&#8221;), and extracts relevant entities (e.g., &#8220;date,&#8221; &#8220;location,&#8221; &#8220;product name&#8221;).</p></li></ul></li><li><p>Dialog management service:</p><ul><li><p><strong>Primary functions</strong>: Oversees conversation flow, handles session state, and orchestrates the next step in the dialog.</p></li><li><p><strong>Data and state</strong>: Maintains conversation context in a dedicated state database.</p></li><li><p><strong>Key interactions</strong>: Logs conversation events (asynchronously) and updates or retrieves session details to guide the flow (e.g., &#8220;Greeting,&#8221; &#8220;Confirmation,&#8221; &#8220;Next step&#8221;).</p></li></ul></li><li><p>Knowledge response service</p><ul><li><p><strong>Primary functions</strong>: Retrieves relevant information and formulates responses. This might involve querying a knowledge base (e.g., FAQs, product info) or assembling template-based replies.</p></li><li><p><strong>Data and templates</strong>: Stores domain-specific data in a knowledge DB and uses templates or generative mechanisms for response creation.</p></li><li><p><strong>Key interactions</strong>: Receives queries from the dialog management service, finds or composes the best response, and returns it for final delivery to the user.</p></li></ul></li><li><p>Conversation analytics service:</p><ul><li><p><strong>Primary functions</strong>: Processes logs and usage metrics for reporting, visualization, and deeper analytics (e.g., intent distribution, user satisfaction trends).</p></li><li><p><strong>Data and reporting</strong>: Maintains analytics data in a separate database for dashboards or offline processing.</p></li><li><p><strong>Key interactions</strong>: Collects asynchronous event logs from the dialog management service and other components to measure performance, track user behavior, and provide insights that could improve the system over time.</p></li></ul></li></ol><h3>Role of the API gateway</h3><p>Although not counted as one of the four microservices, the <strong>API gateway</strong> is a vital component at the front of the architecture. It does the following:</p><ul><li><p>Receives requests from the user (via text or other channels)</p></li><li><p>Initializes the session and routes incoming data to the language understanding service</p></li><li><p>Forwards recognized intents and updates to the dialog management service</p></li><li><p>Passes replies from downstream services back to the user</p></li></ul><p>By centralizing traffic management, the API gateway enforces consistent security, throttling, and monitoring policies while keeping each microservice isolated and independently scalable.</p><h3>Conversation flow sequence</h3><p>To illustrate how these microservices interact during a typical user journey, <em>Figure 1.7</em> shows the sequence of calls between them in a single conversation cycle:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NItB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c9c6e30-c2c7-4f66-a3b7-c02fc2488837_793x808.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NItB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c9c6e30-c2c7-4f66-a3b7-c02fc2488837_793x808.png 424w, https://substackcdn.com/image/fetch/$s_!NItB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c9c6e30-c2c7-4f66-a3b7-c02fc2488837_793x808.png 848w, https://substackcdn.com/image/fetch/$s_!NItB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c9c6e30-c2c7-4f66-a3b7-c02fc2488837_793x808.png 1272w, https://substackcdn.com/image/fetch/$s_!NItB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c9c6e30-c2c7-4f66-a3b7-c02fc2488837_793x808.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NItB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c9c6e30-c2c7-4f66-a3b7-c02fc2488837_793x808.png" width="793" height="808" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4c9c6e30-c2c7-4f66-a3b7-c02fc2488837_793x808.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:808,&quot;width&quot;:793,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A diagram of a software project\n\nAI-generated content may be incorrect.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A diagram of a software project

AI-generated content may be incorrect." title="A diagram of a software project

AI-generated content may be incorrect." srcset="https://substackcdn.com/image/fetch/$s_!NItB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c9c6e30-c2c7-4f66-a3b7-c02fc2488837_793x808.png 424w, https://substackcdn.com/image/fetch/$s_!NItB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c9c6e30-c2c7-4f66-a3b7-c02fc2488837_793x808.png 848w, https://substackcdn.com/image/fetch/$s_!NItB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c9c6e30-c2c7-4f66-a3b7-c02fc2488837_793x808.png 1272w, https://substackcdn.com/image/fetch/$s_!NItB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c9c6e30-c2c7-4f66-a3b7-c02fc2488837_793x808.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 1.7: Sequence diagram between system components</figcaption></figure></div><p>The sequence progresses as follows:</p><ol><li><p><strong>User &#8594; API gateway</strong>: The user sends a request (e.g., a chat message). The API gateway initializes the session (if needed) and forwards the message to the language understanding service.</p></li><li><p><strong>Language understanding service</strong>:</p><ul><li><p>Performs intent classification and entity identification.</p></li><li><p>Returns a recognized intent (e.g., &#8220;CheckWeather&#8221;) and any extracted entities (e.g., date, location) to the API gateway.</p></li></ul></li><li><p><strong>Dialog management service</strong>:</p><ul><li><p>Receives recognized intent from the API gateway.</p></li><li><p>Logs conversation events (asynchronously) into the conversation analytics service.</p></li><li><p>Updates or retrieves the <strong>session state</strong> (e.g., user&#8217;s location or recent conversation context).</p></li></ul></li><li><p><strong>Knowledge response service</strong>:</p><ul><li><p>Once the dialog management service determines additional data is needed (e.g., weather info, product detail), it sends a <strong>query</strong> to the knowledge response service.</p></li><li><p>This service fetches the necessary information or constructs a response template (e.g., &#8220;The weather for your location is sunny with 75&#176;F...&#8221;).</p></li></ul></li><li><p><strong>Conversation analytics service (asynchronous logging)</strong>:</p><ul><li><p>Continuously receives usage data and conversation logs from the dialog management service (and possibly from the knowledge response service).</p></li><li><p>Processes and stores these logs for future reporting (e.g., monthly usage dashboards, model performance metrics).</p></li></ul></li><li><p><strong>Reply to the user</strong>:</p><ul><li><p>The knowledge response service&#8217;s formulated answer is routed back through the dialog management service (if necessary, for final session updates) and then returned via the API gateway.</p></li><li><p>The user receives the <strong>reply</strong> and the interaction concludes.</p></li></ul></li></ol><h3>Key aspects of microservice communication</h3><ul><li><p>Synchronous versus asynchronous calls:</p><ul><li><p>Requests that must return immediately (e.g., generating a response for the user) use synchronous calls.</p></li><li><p>Logging or analytics operations are typically performed asynchronously to avoid slowing down the core conversation loop.</p></li></ul></li><li><p>Stateful versus stateless components:</p><ul><li><p>Dialog management requires tracking session state, while other services (e.g., language understanding) often benefit from stateless designs for simpler scaling.</p></li><li><p>The dialog management service may require robust state management solutions, such as distributed caches or databases.</p></li></ul></li><li><p>Service autonomy:</p><ul><li><p>Each microservice can be updated or replaced independently without affecting the rest of the system.</p></li><li><p>The language understanding service&#8217;s NLP models may need frequent retraining. Because it is a separate service, such updates can be deployed without disrupting the other services.</p></li></ul></li><li><p>Data isolation:</p><ul><li><p>Services manage their own domain data. Dialog management stores conversation state, knowledge response holds domain facts, and analytics maintains interaction logs.</p></li><li><p>Sensitive user data should be restricted to the dialog management service&#8217;s state store when necessary, minimizing the exposure across the entire system.</p></li></ul></li></ul><h3>Implementation considerations for conversational AI microservices</h3><ol><li><p>Scaling independently:</p><ul><li><p>The language understanding service can be scaled up or down based on incoming message load (e.g., horizontal autoscaling for peak chat traffic).</p></li><li><p>The dialog management service maintains conversation state and may require different scaling strategies.</p></li><li><p>The knowledge response service often scales according to the complexity of information retrieval.</p></li><li><p>The conversation analytics service can be scaled separately, especially if analytics workloads (such as report generation) spike at different times than user requests.</p></li></ul></li><li><p>Latency management:</p><ul><li><p>Conversational AI systems aim for near real-time interactions. Minimizing network hops and communication overhead between services is crucial. Using lightweight communication protocols helps ensure the system performs well at scale.</p></li></ul></li><li><p>Fault isolation:</p><ul><li><p>If one service fails (for instance, the knowledge response service goes offline), the rest of the system can still handle other tasks or offer fallback behaviors (e.g., an apology response or a redirect to a human agent).</p></li></ul></li><li><p>Monitoring and observability:</p><ul><li><p>Robust logging and observability practices are crucial to ensure the system remains resilient to service failures or slowdowns. The conversation analytics service plays a key role in tracking system health and performance.</p></li></ul></li></ol><h3>Why microservices for conversational AI?</h3><p>Breaking down a conversational AI system into these four specialized services confers significant benefits in <strong>maintainability</strong>, <strong>scalability</strong>, and <strong>team agility</strong>. Each service can evolve independently, allowing rapid iteration on NLP models, conversation flows, and knowledge retrieval strategies without risking a &#8220;big bang&#8221; failure across the entire application.</p><p>At the same time, careful attention to <strong>inter-service communication</strong> is crucial. As the sequence diagram shows, multiple hops occur for every user request. Using lightweight communication protocols and distinguishing between synchronous and asynchronous operations helps maintain system responsiveness.</p><p>The example of conversational AI powerfully illustrates how the microservices approach enables balancing flexibility, fault tolerance, and iterative innovation. The lessons learned here &#8211; such as independently scaling critical services, isolating data for security, and ensuring graceful failure modes &#8211; apply broadly to a wide array of AI-driven solutions.</p><p>This real-world implementation pattern demonstrates that while microservices add complexity, the benefits they bring to AI systems &#8211; particularly those requiring frequent updates, variable scaling, and component-level innovation &#8211; often outweigh the challenges when properly architected and implemented.</p><div><hr></div><h1>Considerations for an AI system</h1><p>Creating a well-designed AI system architecture necessitates careful consideration of several key factors. These factors ensure that the system not only functions effectively but also adapts to future demands and challenges.</p><h2>Scalability: handling growing data and model complexity</h2><p>AI systems often encounter growing volumes of data and increasingly complex models. Scalability is the ability of a system to handle this growth without compromising performance. Effective strategies include the following:</p><ul><li><p><strong>Horizontal scaling</strong>: This involves adding more compute resources to distribute the workload. For instance, in a cloud environment, you might deploy additional virtual machines or containers to handle increased traffic. Kubernetes can orchestrate these containers, ensuring that the workload is evenly distributed.</p></li><li><p><strong>Vertical scaling</strong>: Enhancing existing resources with more powerful hardware. For example, upgrading a server&#8217;s CPU or GPUs, adding more RAM, or using SSDs instead of HDDs to improve I/O performance.</p></li><li><p><strong>Distributed computing</strong>: Utilizing frameworks such as Apache Spark or Hadoop to process data across multiple nodes. This approach breaks down large datasets into smaller chunks that can be processed in parallel, significantly reducing processing time. For instance, Spark&#8217;s <strong>Resilient Distributed Datasets</strong> (<strong>RDD</strong>s) allow for in-memory processing, which is much faster than traditional disk-based processing.</p></li></ul><h2>Performance: optimization techniques</h2><p>In many AI applications, real-time or near-real-time processing is crucial. Techniques to optimize performance include the following:</p><ul><li><p><strong>Hardware acceleration</strong>: Leveraging GPUs or TPUs for computationally intensive tasks &#8211; for example, TensorFlow and PyTorch can utilize CUDA cores in NVIDIA GPUs to accelerate deep learning model training.</p></li><li><p><strong>Parallel processing</strong>: Dividing tasks into smaller sub-tasks that can be executed concurrently. In Python, libraries such as multiprocessing or concurrent.futures can be used to parallelize tasks &#8211; for instance, training multiple models simultaneously or processing different data batches in parallel.</p></li><li><p><strong>Algorithm optimization</strong>: Choosing or designing algorithms with lower computational complexity. For example, using approximate nearest neighbor algorithms for large-scale similarity search instead of exact methods, which are computationally expensive.</p></li></ul><h2>Reliability: fault tolerance, error handling, and redundancy</h2><p>Reliability is paramount, especially in critical applications. To ensure system uptime and data integrity, strategies such as fault tolerance, error handling, and redundancy are employed:</p><ul><li><p><strong>Fault tolerance</strong>: The system can continue operating even if some components fail. For example, in a microservices architecture, if one service fails, others can continue to function. Tools such as Netflix&#8217;s Hystrix can be used to implement circuit breakers to manage failures.</p></li><li><p><strong>Error handling</strong>: Mechanisms are in place to detect and correct errors gracefully &#8211; for instance, using <code>try-catch</code> blocks in code to handle exceptions and logging errors for further analysis.</p></li><li><p><strong>Redundancy</strong>: Critical components are duplicated to prevent single points of failure &#8211; for example, using RAID configurations for disk storage or deploying services in multiple availability zones in cloud environments to ensure high availability.</p></li></ul><h2>Security: data privacy and model robustness</h2><p>AI systems often handle sensitive data, making security a top priority. Key considerations include the following:</p><ul><li><p><strong>Data encryption</strong>: Protecting data at rest and in transit &#8211; for instance, using AES encryption for data stored in databases and TLS for data transmitted over networks. The use of encryption approaches needs to be considered and tested thoroughly to scope the impact on model and system performance.</p></li><li><p><strong>Access control</strong>: Implementing strict authorization and authentication mechanisms &#8211; for example, using OAuth 2.0 for secure API access and <strong>role-based access control</strong> (<strong>RBAC</strong>) to manage permissions.</p></li><li><p><strong>Model robustness</strong>: Guarding against adversarial attacks that could manipulate the system. Techniques such as adversarial training, where the model is trained on both normal and adversarial examples, can help improve robustness. Additionally, you can deploy anomaly detection systems to monitor for unusual patterns in data input.</p></li></ul><h2>Data modeling: catalogs and ontologies</h2><p>In the realm of AI, data is not just a valuable asset but the very foundation upon which intelligent systems are built. As AI models rely heavily on vast amounts of data to learn and make informed decisions, effective management and organization of this data becomes paramount. This is where data catalogs and ontologies step in as indispensable tools for navigating the complexities of data landscapes within AI architectures.</p><p>Catalogs serve as centralized repositories of metadata, providing comprehensive information about the data assets within an AI system. They act as a comprehensive index, offering insights into the data&#8217;s location, schema, lineage, quality, and other relevant attributes. By consolidating this information in a structured and accessible manner, data catalogs empower data scientists, engineers, and analysts to gain a deeper understanding of their data resources, streamline their workflows, and ensure data governance.</p><p>Ontologies give a semantic representation of the data elements within the domain. They can aid the data engineer in understanding how and why data elements are associated and improve processing pipelines. Ontologies also give data scientists context for model development and updating.</p><p>The technical and functional attributes of AI systems have been discussed. The next section discusses the different ways to implement systems in a modern cloud context. The use of cloud technology ensures that one can readily scale an AI system based on actual demand and provides for flexibility in resource allocations.</p><div><hr></div><h1>Modern AI deployment paradigms</h1><p>As AI systems continue to evolve, new deployment paradigms have emerged to address specific requirements and use cases. This section explores two significant approaches: cloud-native AI architectures and edge AI deployments.</p><h2>Cloud-native AI architectures</h2><p>The increasing complexity and scale of AI applications have led to the adoption of cloud-native architectures. These architectures leverage the scalability, flexibility, and cost-efficiency of cloud computing platforms to enable efficient development, deployment, and management of AI systems. In a cloud-native architecture, AI components are designed to run seamlessly in cloud environments, taking advantage of specialized services for storage, compute, and networking.</p><p>Key characteristics of cloud-native AI architectures include the following:</p><ul><li><p><strong>Containerization</strong>: AI applications are packaged into lightweight, portable containers using technologies such as Docker, ensuring consistency across development, testing, and production environments.</p></li><li><p><strong>Orchestration</strong>: Container orchestration platforms such as Kubernetes manage the deployment, scaling, and operation of application containers across clusters of hosts.</p></li><li><p><strong>Microservices</strong>: As discussed earlier, breaking down AI systems into smaller, independent services enables more efficient resource utilization and easier scaling.</p></li><li><p><strong>Serverless computing</strong>: Platforms such as AWS Lambda, Azure Functions, and Google Cloud Functions allow developers to focus on writing code without worrying about the underlying infrastructure, particularly useful for event-driven AI workloads.</p></li><li><p><strong>Managed services</strong>: Cloud providers offer specialized AI services such as fully managed machine learning platforms (e.g., Amazon SageMaker, Microsoft Azure ML, Google Vertex AI) that streamline the development and deployment process.</p></li><li><p><strong>Cloud-native versus lift-and-shift</strong>: Cloud-native AI components are specifically designed to leverage the benefits of cloud environments, such as auto-scaling, serverless computing, and managed services. This approach offers greater flexibility, scalability, and cost-efficiency compared to simply &#8220;lifting and shifting&#8221; existing on-premises AI systems to the cloud without architectural modifications.</p></li></ul><div><hr></div><h1>Data lakes and data warehouses in AI architectures: foundations for data-driven intelligence</h1><p>In the realm of AI, data is the cornerstone of innovation and progress. AI models thrive on massive volumes of data, leveraging it to learn patterns, make predictions, and generate valuable insights. However, effectively managing and harnessing the vast amounts of data involved in AI projects necessitates specialized storage and management solutions. Two prominent concepts that have emerged in this context are <strong>data lakes</strong> and <strong>data warehouses</strong>.</p><h2>Data lakes: a vast reservoir of raw data</h2><p>Data lakes serve as expansive repositories where raw data is stored in its native format. They are designed to accommodate structured, semi-structured, and unstructured data from diverse sources. The flexibility of data lakes makes them ideal for storing large volumes of data that may not have a predefined purpose or structure.</p><ul><li><p><strong>Key characteristics:</strong></p><ul><li><p><strong>Schema-on-read:</strong> Data lakes do not enforce a strict schema during ingestion, allowing for flexibility in data types and structures. The schema is defined during analysis or processing, empowering users to adapt to evolving data requirements.</p></li><li><p><strong>Cost-effective scalability:</strong> Data lakes can easily scale to accommodate growing data volumes, making them a cost-effective solution for storing massive datasets.</p></li><li><p><strong>Support for diverse data:</strong> Data lakes can handle a wide range of data, including sensor readings, social media feeds, log files, and more.</p></li><li><p><strong>Ideal for exploratory analysis:</strong> Data lakes provide a fertile ground for data scientists and analysts to explore data, identify patterns, and generate hypotheses.</p></li></ul></li><li><p><strong>Example use cases:</strong></p><ul><li><p>An e-commerce company might store clickstream data, customer reviews, and social media interactions in a data lake for subsequent analysis and personalization efforts.</p></li><li><p>A healthcare organization could use a data lake to store medical images, electronic health records, and genomic data for research and development of AI-driven diagnostic tools.</p></li></ul></li></ul><h2>Data warehouses: structured repositories for analytics</h2><p>Data warehouses are structured repositories that house processed and curated data, transformed into a consistent format for analysis and reporting purposes. One can build and develop ontologies to organize and provide semantic structure to the data that comes into the system. Ontologies also provide a mechanism to better manage and control model performance by making relationships between data elements explicit.</p><p>They excel at facilitating efficient querying and analysis, making them indispensable for business intelligence and decision support applications.</p><ul><li><p><strong>Key characteristics:</strong></p><ul><li><p><strong>Schema-on-write:</strong> Data warehouses enforce a predefined schema during data ingestion, ensuring data consistency and integrity.</p></li><li><p><strong>Optimized for querying:</strong> Data warehouses employ optimized data structures and indexing techniques to accelerate data retrieval and analysis, enabling faster insights.</p></li><li><p><strong>Support for structured data:</strong> Data warehouses are primarily designed for structured data, such as transactional data, customer information, and financial records.</p></li><li><p><strong>Ideal for business intelligence:</strong> Data warehouses empower organizations to generate reports, dashboards, and visualizations for informed decision-making.</p></li></ul></li><li><p><strong>Example use cases:</strong></p><ul><li><p>A financial institution might use a data warehouse to store transaction data, customer information, and market trends for risk analysis and fraud detection.</p></li><li><p>A manufacturing company could leverage a data warehouse to analyze production data, supply chain metrics, and customer feedback to optimize operations and improve product quality.</p></li></ul></li></ul><h2>The synergy of data lakes and data warehouses</h2><p>In many AI architectures, data lakes and data warehouses complement each other. Raw data is first ingested into a data lake, where it undergoes cleansing, transformation, and enrichment. The refined data is then transferred to a data warehouse for further analysis and reporting. This synergistic approach enables organizations to leverage the flexibility of data lakes for data exploration and the structure of data warehouses for decision support, creating a robust foundation for data-driven AI applications.</p><div><hr></div><h1>AI on cloud computing: a game-changer for AI</h1><p>The convergence of AI and cloud computing has opened up a new frontier of possibilities for organizations seeking to leverage the power of AI. Cloud computing provides a scalable, flexible, and cost-effective infrastructure for developing, deploying, and scaling AI applications. By harnessing the capabilities of the cloud, businesses can overcome the limitations of traditional on-premises AI solutions and accelerate innovation.</p><h2>Benefits of cloud-based AI</h2><p>Cloud-based AI offers several key advantages that make it an attractive option for organizations of all sizes:</p><ul><li><p><strong>Scalability</strong>: Cloud resources can be easily scaled up or down to meet the fluctuating demands of AI workloads. This elasticity allows organizations to handle large datasets, train complex models, and process vast amounts of data without having to invest in and maintain expensive hardware infrastructure.</p></li><li><p><strong>Flexibility</strong>: Cloud platforms provide a wide range of AI services and tools, giving organizations the flexibility to choose the best options for their specific needs. This allows businesses to experiment with different AI approaches, quickly iterate on models, and adapt to changing requirements.</p></li><li><p><strong>Cost-efficiency</strong>: Cloud-based AI can be more cost-effective than on-premises solutions. Organizations only pay for the resources they consume, eliminating the need for upfront capital investments in hardware and software. Additionally, cloud providers often offer pay-as-you-go pricing models, which can further reduce costs.</p></li></ul><p>By leveraging the power of cloud-based AI, organizations can unlock new levels of innovation, efficiency, and competitiveness.</p><h2>Major cloud AI platforms: accelerating innovation with comprehensive toolsets</h2><p>Major cloud providers have emerged as key players in the AI landscape, offering comprehensive suites of AI services and tools that cater to a wide range of needs. These platforms provide a one-stop shop for businesses and developers looking to leverage the power of AI in their applications and workflows.</p><h3>Key cloud AI platforms</h3><ul><li><p><strong>Google Cloud AI platform (Vertex AI)</strong>: This unified platform streamlines the entire <strong>Machine Learning</strong> (<strong>ML</strong>) lifecycle, from building and training models to deploying and managing them in production. Vertex AI&#8217;s AutoML feature simplifies model development for users with limited ML expertise, while the model garden offers a collection of pre-trained models ready for deployment. Vertex AI Pipelines orchestrates complex ML workflows, enabling efficient experimentation and automation.</p></li><li><p><strong>Amazon SageMaker</strong>: A fully managed service, SageMaker empowers users to build, train, and deploy ML models at scale. It boasts a wide array of built-in algorithms and frameworks, making it accessible to both beginners and experienced practitioners. SageMaker&#8217;s scalability and integration with other AWS services make it a popular choice for enterprise-grade AI solutions.</p></li><li><p><strong>Amazon Bedrock</strong>: This cutting-edge service democratizes access to <strong>Foundation Models</strong> (<strong>FM</strong>s) from leading AI start-ups and Amazon itself through a simple API. Bedrock enables developers to harness the power of state-of-the-art generative AI capabilities without having to build and train complex models from scratch.</p></li><li><p><strong>Microsoft Azure AI</strong>: This platform offers a diverse range of AI services, including pre-built AI models for computer vision, speech recognition, natural language processing, and decision-making. Azure Machine Learning allows users to create and deploy custom AI models, while the platform&#8217;s extensive integration with other Azure services makes it a versatile choice for a variety of AI applications.</p></li></ul><p>These cloud AI platforms provide a powerful and accessible way for organizations to incorporate AI into their operations, accelerating innovation and driving business value.</p><div><hr></div><h1>Summary</h1><p>In this chapter, we have explored the fundamental principles of AI system architecture, establishing a comprehensive framework for understanding the building blocks that power intelligent systems. We examined the core components &#8211; data as the lifeblood, algorithmic frameworks that enable learning, model architectures that encapsulate intelligence, and infrastructure that provides computational resources &#8211; along with architectural patterns such as microservices that offer modularity and flexibility. Critical design considerations of scalability, performance, reliability, and security were discussed as essential elements for robust AI systems that can grow with increasing demands while remaining resilient and protected.</p><p>The landscape of AI deployment continues to evolve rapidly, with cloud-native architectures leveraging containerization, orchestration, and serverless computing to achieve unprecedented efficiency. The synergy between data lakes, data warehouses, and data catalogs creates a solid foundation for data-driven intelligence, while major cloud platforms democratize access to sophisticated AI capabilities. As we move forward, these foundational principles will guide the development of AI systems that are not only powerful but also scalable, reliable, and secure &#8211; enabling the next generation of innovations across industries.</p><div><hr></div><h1>Relevant reading</h1><ul><li><p>Baheti, Radhakisan, and Helen Gill. &#8220;<a href="https://ieeecss.org/sites/ieeecss/files/2019-07/IoCT-Part3-02CyberphysicalSystems.pdf">Cyber-Physical Systems</a>.&#8221; <em>The Impact of Control Technology</em>, edited by Tariq Samad and Anuradha M. Annaswamy, IEEE Control Systems Society, 2011, pp. 161&#8211;66.</p></li><li><p>Bass, Len, Paul Clements, and Rick Kazman. <em>Software Architecture in Practice</em>. 3rd ed., Addison-Wesley, 2012.</p></li><li><p>Hazelwood, Kim, et al. &#8220;<a href="https://ieeexplore.ieee.org/document/8327042">Applied Machine Learning at Facebook: A Datacenter Infrastructure Perspective</a>.&#8221; <em>2018 IEEE International Symposium on High Performance Computer Architecture (HPCA)</em>, IEEE, 2018, pp. 620&#8211;629.</p></li><li><p>LeCun, Yann, Yoshua Bengio, and Geoffrey Hinton. &#8220;<a href="https://www.nature.com/articles/nature14539">Deep Learning</a>.&#8221; <em>Nature</em>, vol. 521, no. 7553, 2015, pp. 436&#8211;44, doi.org/10.1038/nature14539.</p></li><li><p>Mao, Hongzi, et al. &#8220;<a href="https://www.microsoft.com/en-us/research/publication/resource-management-deep-reinforcement-learning">Resource Management with Deep Reinforcement Learning.</a>&#8221; <em>Proceedings of the 15th ACM Workshop on Hot Topics in Networks (HotNets-XV)</em>, ACM, 2016, pp. 50&#8211;56, people.csail.mit.edu/alizadeh/papers/deeprm-hotnets16.pdf.</p></li><li><p>National Institute of Standards and Technology. <em><a href="https://www.nist.gov/publications/artificial-intelligence-risk-management-framework-ai-rmf-10">Artificial Intelligence Risk Management Framework</a> (AI RMF 1.0).</em> NIST, 2023.</p></li><li><p>Patterson, David, et al. &#8220;<a href="https://arxiv.org/abs/2104.10350">Carbon Emissions and Large Neural Network Training</a>.&#8221; <em>arXiv</em>, 2021, arxiv.org/abs/2104.10350.</p></li><li><p>Sculley, D., et al. &#8220;<a href="https://proceedings.neurips.cc/paper/2015/file/86df7dcfd896fcaf2674f757a2463eba-Paper.pdf">Hidden Technical Debt in Machine Learning Systems.</a>&#8221; <em>Advances in Neural Information Processing Systems</em>, vol. 28, 2015, pp. 2503&#8211;2511.</p></li></ul><div><hr></div><p>To go deeper on designing robust, scalable AI-enabled systems&#8212;from integrating machine learning into existing architectures to managing risks like underperformance, cost overruns, and operational complexity&#8212;check out <em><strong><a href="https://www.packtpub.com/en-us/product/architecting-ai-software-systems-9781804619469">Architecting AI Software Systems</a></strong></em> by <strong>Richard D Avila</strong> and <strong>Imran Ahmad</strong> (Packt, 2025). Through a structured progression of architectural concepts, real-world case studies, and hands-on exercises (including a fictional AI-enabled system you can dissect end to end), it shows software and systems architects, CTOs, VPs of Engineering, AI/ML engineers, and developers how to select the right models and data pipelines, use architectural models to ensure cohesion, simulate and optimize AI performance through iteration, and apply patterns and heuristics to integrate AI into large-scale systems with strong user experience and performance&#8212;so you can confidently architect AI-driven products across a range of domains.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-us/product/architecting-ai-software-systems-9781804619469" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3POB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!3POB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!3POB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!3POB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3POB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775" width="312" height="384.85714285714283" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:312,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Architecting AI Software Systems&quot;,&quot;title&quot;:&quot;Architecting AI Software Systems&quot;,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-us/product/architecting-ai-software-systems-9781804619469&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Architecting AI Software Systems" title="Architecting AI Software Systems" srcset="https://substackcdn.com/image/fetch/$s_!3POB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!3POB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!3POB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!3POB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here&#8217;s what some readers have said:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aZpm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aZpm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 424w, https://substackcdn.com/image/fetch/$s_!aZpm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 848w, https://substackcdn.com/image/fetch/$s_!aZpm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 1272w, https://substackcdn.com/image/fetch/$s_!aZpm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aZpm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png" width="853" height="802" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:802,&quot;width&quot;:853,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:192721,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/180580239?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!aZpm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 424w, https://substackcdn.com/image/fetch/$s_!aZpm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 848w, https://substackcdn.com/image/fetch/$s_!aZpm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 1272w, https://substackcdn.com/image/fetch/$s_!aZpm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KWHP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KWHP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 424w, https://substackcdn.com/image/fetch/$s_!KWHP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 848w, https://substackcdn.com/image/fetch/$s_!KWHP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 1272w, https://substackcdn.com/image/fetch/$s_!KWHP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KWHP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png" width="851" height="753" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:753,&quot;width&quot;:851,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:151425,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/180580239?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!KWHP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 424w, https://substackcdn.com/image/fetch/$s_!KWHP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 848w, https://substackcdn.com/image/fetch/$s_!KWHP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 1272w, https://substackcdn.com/image/fetch/$s_!KWHP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>]]></content:encoded></item><item><title><![CDATA[Implicit Memory Systems for LLMs]]></title><description><![CDATA[When Code Surrenders to Context]]></description><link>https://deepengineering.net/p/implicit-memory-systems-for-llms</link><guid isPermaLink="false">https://deepengineering.net/p/implicit-memory-systems-for-llms</guid><dc:creator><![CDATA[Sam Keen]]></dc:creator><pubDate>Wed, 26 Nov 2025 08:42:07 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a56371ea-f985-445a-8a3f-6125fb18bee5_1280x731.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><strong>The Irony of Explicit Memory Controls</strong></h2><p>In my previous post, <a href="https://alteredcraft.com/p/the-memory-illusion-teaching-your">The Memory Illusion</a>, I demonstrated that LLM memory doesn&#8217;t require vector databases or sophisticated architectures. It&#8217;s fundamentally just text management. We built a proof-of-concept in ~150 lines of Python that stored memories in a simple markdown file. It worked. But it had an amusing limitation: <strong>The user had to remember to tell the AI to remember</strong>.</p><p>The system required explicit commands. Want the LLM to store your name? Type <code>!remember &#8220;My name is Alex&#8221;</code>. Want it to know your project preferences? Another <code>!remember</code> command. The irony was sharp: we&#8217;d outsourced memory to technology, only to burden ourselves with managing that memory manually.</p><p>This wasn&#8217;t an oversight. It was a conscious design decision. Our application code controlled every memory operation through explicit if/else logic. The host app was the memory manager, and the LLM was simply our text processor.</p><p>But what if we handed that authority to the LLM itself?</p><p>This isn&#8217;t an incremental improvement or &#8220;v2&#8221; of the same approach. It&#8217;s a fundamentally different philosophy: <strong>trusting the LLM to autonomously manage its own memory</strong>. The technical implications are profound. We move from programming specific behaviors to setting high-level intentions. We shift from writing parsing logic to defining trust boundaries.</p><p>This approach enables the system to handle its own errors, organize information without explicit rules, and maintain its own memory hygiene. All without writing a single if/else statement.</p><h2><strong>The Explicit Approach: When Code Defines Every Decision</strong></h2><p>In my original POC, every memory operation required explicit user commands. Here&#8217;s what a typical session looked like:</p><pre><code><code>[You]: Hello, I&#8217;m working on a React app
[Claude]: Hi! What kind of React app are you building?
[You]: !remember I am building a React e-commerce application
[Claude]: [Memory saved]</code></code></pre><p>Here we see that required use of <code>!remember</code>. Adding to the user&#8217;s cognitive load alongside their actual work.</p><p>Behind the scenes, our code intercepted every message, parsed for commands, managed file operations, and reconstructed the prompt with memories for each interaction. We were the brain; the LLM was just processing text within our constraints.</p><p>This gave us complete control. We defined in code the exact format of the memory file. Want memories timestamped? We coded it. Want them categorized? More code. Every behavior was explicit, predictable, testable.</p><p>This is how we&#8217;ve built software applications since the inception of the craft. We write the logic, we define the control flow, we handle the edge cases. It&#8217;s comfortable, familiar territory. But when working with LLMs, this traditional approach means we&#8217;re not fully leveraging what makes them truly powerful: their ability to understand context and make intelligent decisions autonomously.</p><p>The LLM&#8217;s contextual intelligence sits idle while our code makes every decision. This intelligence was trained on billions of examples of how humans organize and retrieve information.</p><p>Most importantly, users had to remember the commands, creating friction in your app&#8217;s usability. Edge cases multiplied. The code grew ever larger as we handled more scenarios, more commands, more special cases. We were swimming upstream against the fundamental capabilities of modern LLMs.</p><h2><strong>The Implicit Approach: LLM as Autonomous Manager</strong></h2><p>The paradigm shift is what matters. We&#8217;re implementing a harness that grants the LLM autonomous authority. While this example uses the Claude Agent SDK, the pattern can be implemented with other SDKs or custom code. The key is delegation of decision-making, not specific tooling.</p><p>Let&#8217;s look at the skeleton of our memory tool implementation:<br>Full code found in the <a href="https://github.com/AlteredCraft/implicit-memory-system-poc-article/tree/main">companion app</a></p><pre><code><code># memory_tool.py 
class LocalFilesystemMemoryTool(BetaAbstractMemoryTool):
    &#8220;&#8221;&#8220;
    The LLM calls these methods autonomously based on context.
    We provide the infrastructure; Claude makes the decisions.
    &#8220;&#8221;&#8220;

    # The hook methods (memory tools) Claude is made aware of:

    @override
    def view(self, command):
        &#8220;&#8221;&#8220;Claude calls this to read memories or list files&#8221;&#8220;&#8221;
        # Validate path, read file/directory, return contents

    @override
    def create(self, command):
        &#8220;&#8221;&#8220;Claude calls this to create new memory files&#8221;&#8220;&#8221;
        # Validate path, write file, log operation

    @override
    def str_replace(self, command):
        &#8220;&#8221;&#8220;Claude calls this to update existing memories&#8221;&#8220;&#8221;
        # Find text, replace it, handle errors

    @override
    def insert(self, command):
        &#8220;&#8221;&#8220;Claude calls this to add lines to memories&#8221;&#8220;&#8221;
        # Insert at specific line number

    @override
    def delete(self, command):
        &#8220;&#8221;&#8220;Claude calls this to remove memories&#8221;&#8220;&#8221;
        # Delete files or directories

    @override
    def rename(self, command):
        &#8220;&#8221;&#8220;Claude calls this to reorganize memories&#8221;&#8220;&#8221;
        # Move or rename files</code></code></pre><p>The SDK provides these hook methods as the interface contract. We implement the file operations; Claude decides when to invoke them. No command parsing required from us.</p><p>The system prompt grants authority. Instead of telling Claude what to remember, we grant it authority:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UbAb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6971d63d-d3ad-4258-b6cb-10527eaf33ec_1144x276.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UbAb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6971d63d-d3ad-4258-b6cb-10527eaf33ec_1144x276.png 424w, https://substackcdn.com/image/fetch/$s_!UbAb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6971d63d-d3ad-4258-b6cb-10527eaf33ec_1144x276.png 848w, https://substackcdn.com/image/fetch/$s_!UbAb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6971d63d-d3ad-4258-b6cb-10527eaf33ec_1144x276.png 1272w, https://substackcdn.com/image/fetch/$s_!UbAb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6971d63d-d3ad-4258-b6cb-10527eaf33ec_1144x276.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UbAb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6971d63d-d3ad-4258-b6cb-10527eaf33ec_1144x276.png" width="1144" height="276" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6971d63d-d3ad-4258-b6cb-10527eaf33ec_1144x276.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:276,&quot;width&quot;:1144,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:54239,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://exploregamedev.substack.com/i/178638297?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6971d63d-d3ad-4258-b6cb-10527eaf33ec_1144x276.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!UbAb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6971d63d-d3ad-4258-b6cb-10527eaf33ec_1144x276.png 424w, https://substackcdn.com/image/fetch/$s_!UbAb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6971d63d-d3ad-4258-b6cb-10527eaf33ec_1144x276.png 848w, https://substackcdn.com/image/fetch/$s_!UbAb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6971d63d-d3ad-4258-b6cb-10527eaf33ec_1144x276.png 1272w, https://substackcdn.com/image/fetch/$s_!UbAb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6971d63d-d3ad-4258-b6cb-10527eaf33ec_1144x276.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Concise system prompt</figcaption></figure></div><p>This shift is profound. We&#8217;re not programming behaviors anymore. We&#8217;re setting intentions and trusting Claude to execute them. The orchestration that once consumed lines of code now happens autonomously, guided by our system prompt.</p><p>This hands-off approach of providing just the infrastructure hooks while delegating all decision-making to the LLM may seem minimal. That&#8217;s precisely the point.</p><p>If you&#8217;ve spent years writing deterministic code, this delegation feels uncomfortable. You&#8217;re trusting the LLM to make architectural decisions you once controlled. That discomfort is valid. Let&#8217;s add some clarity to the real world implications of this approach.</p><h2><strong>Under the Hood: Autonomous Decisions in Action</strong></h2><p>Let&#8217;s trace what actually happens during a conversation. These examples come from real sessions the <a href="https://github.com/AlteredCraft/implicit-memory-system-poc/">companion app</a>&#8217;s trace and render diagram features.</p><h3><strong>Creating New Memory</strong></h3><pre><code><code>[You]: &#8220;I&#8217;m starting a new project. I&#8217;d like to build a web app for solo entrepreneurs
       to track their time and projects&#8221;

~Claude internally~: Creates new memory at path `project_solo_entrepreneur_app.txt`

[Claude]: &#8220;That&#8217;s an exciting project! A time and project tracker specifically for solo entrepreneurs ...&#8221;</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IUyu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edbd534-97ba-45a2-a53a-2194562e7a4e_3958x2438.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IUyu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edbd534-97ba-45a2-a53a-2194562e7a4e_3958x2438.png 424w, https://substackcdn.com/image/fetch/$s_!IUyu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edbd534-97ba-45a2-a53a-2194562e7a4e_3958x2438.png 848w, https://substackcdn.com/image/fetch/$s_!IUyu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edbd534-97ba-45a2-a53a-2194562e7a4e_3958x2438.png 1272w, https://substackcdn.com/image/fetch/$s_!IUyu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edbd534-97ba-45a2-a53a-2194562e7a4e_3958x2438.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IUyu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edbd534-97ba-45a2-a53a-2194562e7a4e_3958x2438.png" width="1456" height="897" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2edbd534-97ba-45a2-a53a-2194562e7a4e_3958x2438.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:897,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:474522,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://exploregamedev.substack.com/i/178638297?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edbd534-97ba-45a2-a53a-2194562e7a4e_3958x2438.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!IUyu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edbd534-97ba-45a2-a53a-2194562e7a4e_3958x2438.png 424w, https://substackcdn.com/image/fetch/$s_!IUyu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edbd534-97ba-45a2-a53a-2194562e7a4e_3958x2438.png 848w, https://substackcdn.com/image/fetch/$s_!IUyu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edbd534-97ba-45a2-a53a-2194562e7a4e_3958x2438.png 1272w, https://substackcdn.com/image/fetch/$s_!IUyu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edbd534-97ba-45a2-a53a-2194562e7a4e_3958x2438.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The sequence of actions resulting in the creation of a new memory</figcaption></figure></div><p>You didn&#8217;t ask Claude to remember anything. Claude autonomously decided this information was worth preserving. It even made an architectural decision. It created a new project file rather than appending to a general profile. These are judgment calls the LLM now makes without any explicit rules from us beyond guidance in the system prompt.</p><h3><strong>Self-Correction</strong></h3><p>What happens when the LLM makes a mistake? In traditional systems, errors bubble up to users. Here&#8217;s how autonomous authority changes that dynamic:</p><pre><code><code>[You]: &#8220;Let&#8217;s add these MVP features: client profiles, task tracking, time logging&#8221;

~Claude internally~: Tries to update `solo_entrepreneur.txt`,
gets FileNotFoundError, checks memory folder, finds correct
filename `project_solo_entrepreneur_app.txt`, successfully updates

[Claude]: &#8220;Got it! I&#8217;ve noted those MVP features for your project.&#8221;</code></code></pre><p>Claude made a mistake with the filename, detected the error, diagnosed the issue, and self-corrected. All autonomously. No error messages to the user, no manual intervention required.</p><p>Our code deliberately re-raises exceptions to Claude rather than hiding them. This design turns errors into recovery opportunities. Claude receives the FileNotFoundError, understands what went wrong, and adapts its strategy. This resilience emerges naturally from giving the LLM authority rather than rigid rules.</p><h3><strong>Context-Aware Retrieval</strong></h3><p>The LLM decides <em>when</em> to check memories. No rules tell it to look for context on greetings, it just understands that&#8217;s useful:</p><pre><code><code>[You]: &#8220;hello&#8221;

~Claude internally~: Checks memory folder, finds and reads
`user_preferences.txt` containing &#8220;Vacationing at the beach
as of 2025-11-03&#8221;

[Claude]: &#8220;Hi! How are things going at the beach today?&#8221;</code></code></pre><p>Claude found relevant information and used it naturally in conversation.</p><p>These autonomous behaviors all emerge without us programming them explicitly. They include deciding on storage structure, recovering from errors, and retrieving contextual information.</p><div class="pullquote"><p><em><strong>We implement the hooks; Claude provides the intelligence.</strong></em></p></div><h2><strong>Code vs. Prompts: Where Control Lives</strong></h2><p>The shift to implicit memory doesn&#8217;t mean abandoning all control. It means being strategic about where that control resides. By examining what stays in code versus what moves to prompts, we can understand the architecture of trust in AI systems.</p><h3><strong>Hard Boundaries in Code</strong></h3><p>The <code>memory_tool.py</code> file shows what explicit choices we&#8217;re still making in code:</p><p><strong>Security Boundaries</strong>: The <code>_validate_path()</code> method explicitly ensures all operations stay within the <code>/memories</code> directory. This is hard-coded protection against path traversal attacks. It&#8217;s a security boundary we enforce in code, not through prompts.</p><p><strong>Logging &amp; Tracing</strong>: Every operation is explicitly logged for debugging and audit trails. We know every tool call Claude made to the memory system and what was read or written.</p><p>These coded constraints are examples of the guardrails we build to create a safe sandbox within which Claude operates autonomously. They&#8217;re guarantees enforced by our infrastructure, not suggestions in a prompt. In a production system you will expand on these guardrails until you have an acceptable risk level for your line of business.</p><p><strong>The Power of Hooks: Beyond File Persistence</strong></p><p>The SDK&#8217;s hook-based architecture (<a href="https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/lib/tools/_beta_builtin_memory_tool.py">BetaAbstractMemoryTool</a>) enables remarkable flexibility. While our implementation uses a filesystem backend, you could implement these same hooks to:</p><ul><li><p>Store memories in a PostgreSQL database for multi-user applications</p></li><li><p>Use Redis for high-performance, distributed memory systems</p></li><li><p>Implement vector embeddings for semantic memory retrieval</p></li><li><p>Create hybrid systems that combine multiple storage backends</p></li></ul><p>The LLM doesn&#8217;t care about the backend. It just calls the hooks and trusts the implementation to handle the details. This separation of concerns enables powerful architectural flexibility while maintaining the same autonomous decision-making paradigm.</p><h3><strong>Guidance Through Prompts</strong></h3><p>Between the hard constraints of code and the full autonomy of the LLM lies prompt-based guidance. These are strong suggestions that shape behavior without guarantees:</p><pre><code><code>Keep your memory folder organized. Update existing files rather
than creating duplicates. Include metadata like dates when relevant.

When the memory folder exceeds 20 files, consolidate related
memories into broader topic files. Archive outdated information
with clear labels.</code></code></pre><p>This achieves implicit memory maintenance without explicit commands such as <code>!compact</code>. The LLM monitors its own memory health and performs housekeeping autonomously.</p><p>For more structure in the memory files, we could specify a frontmatter format for all new memories. This creates a sort of pseudo-schema to be leveraged by the LLM and our instrumentation code:</p><pre><code><code>All memory files should start with:
---
created: YYYY-MM-DD
relevance: current|archive
---</code></code></pre><p>This gives Claude a framework without removing its autonomy. It can still make contextual decisions within these guidelines.</p><p><strong>Making the Implicit Explicit: Explanatory Mode</strong></p><p>For demonstration purposes in the companion chat app, I added <strong>explanatory versions of the prompts</strong>. These make the implicit decision-making visible for learning:</p><pre><code><code># Directive in the system prompt

IMPORTANT: You are a teaching tool to help the user understand how you
process and utilize memories. After each natural conversational response,
add a concise summary of the memory actions you took and why.</code></code></pre><p>This produces illuminating results:</p><pre><code><code>[You]: &#8220;ok, I think I&#8217;ll set a goal to finish the outline today, by noon&#8221;

[Claude]: &#8220;Excellent goal! Finishing the outline by noon today (2025-11-10) gives ...&#8221;

---
Memory Actions Taken:
- Updated article status to reflect current goal: completing the outline by noon today (2025-11-10)
- This helps track progress and provides context if we discuss the article later today or in future sessions</code></code></pre><p>This explanatory mode becomes a powerful tool for understanding the LLM&#8217;s approach to memory and tuning your system prompts. It transforms the black box into a glass box, revealing the autonomous reasoning. Use it to validate that your prompt guidance is achieving the intended behaviors, then switch back to normal operation once satisfied.</p><p>The beauty of this division is that higher risk critical decisions stay in code where they&#8217;re guaranteed, while contextual decisions that benefit from intelligence and flexibility live in prompts. As models improve, the prompt-based behaviors get smarter automatically, while our security boundaries remain firm.</p><h2><strong>Conclusion: Learning from Scale</strong></h2><p>There&#8217;s a principle in AI research that keeps proving itself true. Systems built on general methods and scaled computation consistently outperform those with hand-crafted rules. Rich Sutton calls this <a href="http://www.incompleteideas.net/IncIdeas/BitterLesson.html">&#8220;The Bitter Lesson&#8221;</a>. Bitter because it means our clever, specialized solutions inevitably lose to simpler approaches that leverage raw intelligence.</p><p>The shift from explicit to implicit memory perfectly illustrates this principle. And it reveals what becomes possible when we stop fighting it.</p><h3><strong>The Power of Delegated Intelligence</strong></h3><p>We&#8217;ve seen the behaviors that emerge: autonomous memory creation, contextual reorganization, self-healing from errors. These weren&#8217;t programmed. They emerged from granting the LLM authority within safe boundaries.</p><p>This pattern extends beyond memory. It extends to complex decision-making in your application. Routing requests, organizing data, managing workflows. All can potentially be delegated to intelligence rather than encoded in logic. The infrastructure you build becomes a framework for capabilities you haven&#8217;t even imagined yet.</p><p><strong>Hybrid model</strong>: Codify workflows that need precise control as tools, then let the LLM autonomously decide when and how to use them. This gives you explicit control over critical operations while still leveraging the LLM&#8217;s decision-making for orchestration.</p><p><strong>As models improve, your system automatically gets better</strong>. When the next generation releases, you update one parameter. Your existing infrastructure suddenly makes smarter decisions. No refactoring. No new edge cases. The same hooks you implement today become more capable tomorrow.</p><h3><strong>Trust but Verify: Your Implementation Philosophy</strong></h3><p>This shift changes how we architect AI systems. Instead of writing decision trees, we adopt a &#8220;trust but verify&#8221; philosophy:</p><ul><li><p><strong>Trust</strong>: Grant the AI authority through system prompts</p></li><li><p><strong>Verify</strong>: Monitor the behaviors that emerge</p></li><li><p><strong>Guide</strong>: Adjust prompts based on observed patterns</p></li><li><p><strong>Iterate</strong>: Refine boundaries as models improve</p></li></ul><p>We&#8217;re reallocating the effort once spent on explicit control logic to validation and evaluation. Same total engineering effort, fundamentally better product. The interesting work moves from implementing specific behaviors to designing systems that exhibit emergent intelligence while maintaining appropriate guardrails.</p><h3><strong>The Path Forward</strong></h3><p>But here&#8217;s my challenge to you: <strong>run an experiment</strong>. The gap between what models can do and what we think they can do is often surprising. Many teams discover their explicit controls were solving problems the LLM could handle autonomously (and often better).</p><p><strong>You can test this today.</strong> The <a href="https://github.com/AlteredCraft/implicit-memory-system-poc-article/tree/main">companion app</a> provides a complete learning laboratory:</p><ul><li><p>Multiple prompting strategies for testing delegation</p></li><li><p>Full session tracing of every memory decision</p></li><li><p>Visual sequence diagrams exported from conversations</p></li><li><p>Real-time observation of memory folder organization</p></li></ul><p>Clone it. Run it. Watch what emerges. Add your own prompts. Test your assumptions. Even if the model isn&#8217;t ready for your use case today, you&#8217;ll have the framework when the next version drops.</p><p>The original memory post showed that LLM memory is just text management. This exploration reveals a deeper pattern: <strong>we&#8217;re moving from programming behaviors to orchestrating capabilities</strong>. The question isn&#8217;t whether to trust AI with decisions. It&#8217;s understanding which decisions, with what boundaries, and how to monitor the results.</p><p>The tools are ready. The models are capable. The only thing standing between you and implicit memory is running that first experiment.</p><div><hr></div><p><strong>Want to explore further?</strong></p><ul><li><p>Original approach: <a href="https://github.com/AlteredCraft/simple_llm_memory_poc">simple_llm_memory_poc</a></p></li><li><p>Implicit approach: <a href="https://github.com/AlteredCraft/implicit-memory-system-poc-article/tree/main">implicit-memory-system-poc</a></p></li><li><p>Claude Agent SDK: <a href="https://docs.claude.com/en/api/agent-sdk/overview">Documentation</a></p></li><li><p>Memory Tool: <a href="https://docs.claude.com/en/docs/agents-and-tools/tool-use/memory-tool">Official docs</a></p></li><li><p>Anthropic on Agents: <a href="https://www.anthropic.com/engineering/building-effective-agents">Building Effective Agents</a></p></li></ul><div><hr></div><p><strong>About the Author</strong>: Sam Keen publishes at at <a href="https://alteredcraft.com/">AlteredCraft</a>. Subscribe for content that dives into the paradigm shifts AI brings to software development in addition to a free weekly roundup of the latest AI Tutorials, Tool, and News relevant to software developers.</p>]]></content:encoded></item><item><title><![CDATA[Merkle Trees and Anti-Entropy — Concepts and Implementation]]></title><description><![CDATA[How Distributed Systems Like Cassandra and DynamoDB Stay Consistent at Scale &#8212; And How You Can Build It Too]]></description><link>https://deepengineering.net/p/merkle-trees-and-anti-entropy-concepts</link><guid isPermaLink="false">https://deepengineering.net/p/merkle-trees-and-anti-entropy-concepts</guid><dc:creator><![CDATA[Archit Agarwal]]></dc:creator><pubDate>Mon, 24 Nov 2025 06:22:21 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!wBKQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever gone on a road trip with friends and tried to split expenses? It&#8217;s fun &#8212; until someone gets stuck doing the dirty work of tracking who owes what. Picture this: you&#8217;re in a group of five, each person tallying their own totals. When it&#8217;s time to settle up, you don&#8217;t want to compare receipts line by line with everyone, every day. Instead, you check the overall totals. If someone&#8217;s number doesn&#8217;t match, you know there&#8217;s a discrepancy &#8212; so you only compare details with the friend who has a different total. Efficient, right?</p><p>Now, scale up this problem. Imagine a massive distributed system where 10GB of data lives on each node, and every bit is replicated across five different nodes for reliability. When a network partition occurs and a node comes back online, is it really practical to sync all 10GB of data across every node, every time? Of course not.</p><p>This is the very challenge that engineers at Cassandra, DynamoDB, and even in blockchain technology have faced. How do you quickly identify and fix just the pieces of data that have changed, without wasting massive amounts of time and bandwidth checking everything, everywhere?</p><p>The secret lies in two powerful concepts: Merkle trees and anti-entropy protocols.</p><p>In this article, we&#8217;ll step into an engineer&#8217;s shoes &#8212; exploring how these data structures and algorithms allow distributed databases like DynamoDB and Cassandra to efficiently detect, compare, and synchronize changes. We&#8217;ll break down what Merkle trees and anti-entropy actually are, why they matter, and how you can implement them yourself (with hands-on examples in Golang).</p><p><em>Ready to see how modern distributed systems stay fast and consistent, even at a massive scale? Let&#8217;s dive in!</em></p><h2><strong>Understanding Merkle Trees</strong></h2><p>Imagine you&#8217;re the head of accounting at a global company with offices in 100 cities worldwide. Your responsibility: keep track of all expenses across every location. Reconciling records for every single office, every time, would be a logistical nightmare &#8212; not to mention a massive waste of time and resources.</p><p>To solve this, you create a smarter system:</p><ul><li><p>Instead of checking every office one by one, you appoint an assistant who keeps a running total.</p></li><li><p>Now, you only compare your assistant&#8217;s master total with your own. If the totals match, you&#8217;re in sync. If not, you zero in on where the change happened.</p></li><li><p>Taking it further, you group offices by regions (say, Asia-Pacific and USA). Each region reports upward. When there&#8217;s a mismatch, you drill down region by region, then country, then city.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wBKQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wBKQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic 424w, https://substackcdn.com/image/fetch/$s_!wBKQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic 848w, https://substackcdn.com/image/fetch/$s_!wBKQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic 1272w, https://substackcdn.com/image/fetch/$s_!wBKQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wBKQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic" width="720" height="480" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:480,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:34582,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/178496085?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wBKQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic 424w, https://substackcdn.com/image/fetch/$s_!wBKQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic 848w, https://substackcdn.com/image/fetch/$s_!wBKQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic 1272w, https://substackcdn.com/image/fetch/$s_!wBKQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd0f1af4-5d95-4113-b880-6825c707b3b9_720x480.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This hierarchical, divide-and-narrow approach is precisely how Merkle trees work in distributed systems.</p><h2><strong>What is a Merkle Tree?</strong></h2><p>A Merkle tree is a data structure designed for efficient and secure verification of large sets of data. Here&#8217;s the basic anatomy:</p><ul><li><p><strong>Leaf Nodes:</strong> Each one holds a hash of actual data &#8212; like an office&#8217;s transactions.</p></li><li><p><strong>Internal (Branch) Nodes:</strong> Each combines and hashes its children&#8217;s hashes, summarizing everything below.</p></li><li><p><strong>Root Node:</strong> The master hash &#8212; changing if anything underneath changes.</p></li></ul><p><em>Key insight:</em> Instead of comparing all data, you compare hashes at each level, honing in only where there&#8217;s a mismatch. This scales logarithmically, not linearly.</p><p><strong>Historical Context:</strong><br>Devised by Ralph Merkle in 1979, Merkle trees are now central to blockchains, distributed file systems, and database consistency.</p><h2><strong>How Merkle Trees Work</strong></h2><h3><strong>a. The Hashing Process</strong></h3><ul><li><p><strong>Hashing the Leaves:</strong> Pass each data item (file, transaction, etc.) through a hash function (e.g., SHA-256). These are the leaf nodes.</p></li><li><p><strong>Building Up the Tree:</strong> Pair up the leaf hashes, concatenate and hash again to form parents, repeating until you reach the root.</p></li><li><p><strong>Checking for Changes:</strong> Any single data change changes its hash and all parents up to the root. Comparing roots lets you instantly check if two datasets match.</p></li></ul><h3><strong>b. Example: Tiny Merkle Tree</strong></h3><p>Suppose you have four data blocks: A, B, C, D.<br><strong>Step 1:</strong> Hash each block.<br><strong>Step 2:</strong> Hash pairs: (A+B), (C+D).<br><strong>Step 3:</strong> Hash those two: (AB + CD) = Root.<br>If just B changes, only three hashes need recalculating, and you can precisely spot the change.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dJP-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc87e9ee-c847-4ef1-a8a6-f21493210d72_720x1080.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dJP-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc87e9ee-c847-4ef1-a8a6-f21493210d72_720x1080.heic 424w, https://substackcdn.com/image/fetch/$s_!dJP-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc87e9ee-c847-4ef1-a8a6-f21493210d72_720x1080.heic 848w, https://substackcdn.com/image/fetch/$s_!dJP-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc87e9ee-c847-4ef1-a8a6-f21493210d72_720x1080.heic 1272w, https://substackcdn.com/image/fetch/$s_!dJP-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc87e9ee-c847-4ef1-a8a6-f21493210d72_720x1080.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dJP-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc87e9ee-c847-4ef1-a8a6-f21493210d72_720x1080.heic" width="720" height="1080" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fc87e9ee-c847-4ef1-a8a6-f21493210d72_720x1080.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1080,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38664,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/178496085?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc87e9ee-c847-4ef1-a8a6-f21493210d72_720x1080.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dJP-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc87e9ee-c847-4ef1-a8a6-f21493210d72_720x1080.heic 424w, https://substackcdn.com/image/fetch/$s_!dJP-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc87e9ee-c847-4ef1-a8a6-f21493210d72_720x1080.heic 848w, https://substackcdn.com/image/fetch/$s_!dJP-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc87e9ee-c847-4ef1-a8a6-f21493210d72_720x1080.heic 1272w, https://substackcdn.com/image/fetch/$s_!dJP-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc87e9ee-c847-4ef1-a8a6-f21493210d72_720x1080.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Real-World Applications of Merkle Trees</strong></h2><ul><li><p><strong>Blockchains:</strong> Store all transactions for a block as a Merkle tree. The root in the block header allows anyone to verify a transaction&#8217;s inclusion.</p></li><li><p><strong>Versioned File Systems (e.g., Git):</strong> Every commit is represented as a Merkle root; differences between commits highlight only what&#8217;s changed.</p></li><li><p><strong>Distributed Databases (Cassandra, DynamoDB):</strong> Use Merkle trees for anti-entropy. Only out-of-sync segments are reconciled, not the full dataset.</p></li></ul><p><em>Merkle trees guarantee tamper-evidence and make large-scale, bandwidth-efficient consistency possible.</em></p><h2><strong>What is Anti-Entropy?</strong></h2><p>Back to our global accounts team: over time, small differences creep into each office&#8217;s ledger &#8212; network delays, miscommunications, independent corrections. These inconsistencies, or entropy, must be regularly tracked down and fixed.<br>Anti-entropy is your systematic, efficient approach to reconciling just the differences, not everything.</p><h2><strong>Anti-Entropy Mechanisms</strong></h2><ul><li><p><strong>Gossip Protocols:</strong> Random offices synchronize with each other, gradually spreading updates network-wide.</p></li><li><p><strong>Vector Clocks:</strong> Track who changed what, and when, to resolve conflicts precisely.</p></li><li><p><strong>Merkle Trees:</strong> Group and summarize records as hashes; compare just summaries first to find divergences efficiently.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XX_B!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00eedf76-b399-4320-a147-c3725cae63a3_720x480.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XX_B!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00eedf76-b399-4320-a147-c3725cae63a3_720x480.heic 424w, https://substackcdn.com/image/fetch/$s_!XX_B!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00eedf76-b399-4320-a147-c3725cae63a3_720x480.heic 848w, https://substackcdn.com/image/fetch/$s_!XX_B!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00eedf76-b399-4320-a147-c3725cae63a3_720x480.heic 1272w, https://substackcdn.com/image/fetch/$s_!XX_B!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00eedf76-b399-4320-a147-c3725cae63a3_720x480.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XX_B!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00eedf76-b399-4320-a147-c3725cae63a3_720x480.heic" width="720" height="480" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/00eedf76-b399-4320-a147-c3725cae63a3_720x480.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:480,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31475,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/178496085?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00eedf76-b399-4320-a147-c3725cae63a3_720x480.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XX_B!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00eedf76-b399-4320-a147-c3725cae63a3_720x480.heic 424w, https://substackcdn.com/image/fetch/$s_!XX_B!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00eedf76-b399-4320-a147-c3725cae63a3_720x480.heic 848w, https://substackcdn.com/image/fetch/$s_!XX_B!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00eedf76-b399-4320-a147-c3725cae63a3_720x480.heic 1272w, https://substackcdn.com/image/fetch/$s_!XX_B!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00eedf76-b399-4320-a147-c3725cae63a3_720x480.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>How Merkle Trees Empower Anti-Entropy in Real Systems</strong></h2><p>This is exactly the strategy used by distributed databases like Cassandra and DynamoDB. Each node in the system summarizes its stored data using a Merkle tree. When it&#8217;s time to reconcile &#8212; maybe after a network glitch or data loss &#8212; nodes quickly compare root hashes, then only dive deeper where discrepancies are found. This process scales effortlessly, allowing massive companies (and databases) to keep their records in harmony with minimal effort.</p><p>So, instead of drowning in a sea of spreadsheets, your accounts team uses anti-entropy protocols &#8212; and especially Merkle trees &#8212; to work smarter, not harder, keeping the entire global ledger consistent, up-to-date, and audit-ready.</p><p><strong>In essence:</strong><br>Anti-entropy, led by the power of Merkle trees, turns the messy challenge of financial reconciliation across a giant organization into a fast, targeted process &#8212; ensuring global consistency with a fraction of the work. This is the same magic powering reliable, scale-proof distributed databases today.</p><h2><strong>Implementing a Merkle Tree in Code</strong></h2><p>To truly appreciate the power of Merkle trees in distributed systems, let&#8217;s walk through a practical implementation in Go. This example captures the fundamental operations: building the tree, generating cryptographic hashes, and efficiently identifying differences between trees.</p><h3><strong>Building the Merkle Tree</strong></h3><p>At the core, a Merkle tree organizes data blocks into a hierarchical hash structure. Our implementation breaks down like this:</p><ul><li><p>We start by dividing data into buckets (or partitions), each containing a fixed number of keys.</p></li><li><p>Each bucket&#8217;s combined data is hashed, creating a leaf node.</p></li><li><p>We then recursively pair leaf nodes, concatenate their hashes, and hash again to form parent nodes.</p></li><li><p>This process repeats until a single root node remains, representing a compact summary of the entire dataset.</p></li></ul><p>Here&#8217;s how these pieces fit together in code:</p><pre><code>// MerkleNode represents a leaf or internal node with its hash and children.
type MerkleNode struct {
    Hash     string
    Left     *MerkleNode
    Right    *MerkleNode
    KeyRange []string  // Only non-empty for leaves, marks keys covered
}

// Tree manages the Merkle tree for a dataset or node partition.
type Tree struct {
    BucketSize int       // Number of keys per leaf bucket
    Root       *MerkleNode
    mu         sync.RWMutex  // Protects concurrent access
}</code></pre><p>When you call the <code>Build</code> method with sorted keys and their corresponding data, it hashes data bucket by bucket to create leaves:</p><pre><code>func (t *Tree) buildLeaves(keys []string, kvs map[string][]byte) []*MerkleNode {
    var leaves []*MerkleNode
    for i := 0; i &lt; len(keys); i += t.BucketSize {
        end := min(i + t.BucketSize, len(keys))
        bucket := keys[i:end]
        
        // Concatenate keys and their values (hex encoded)
        data := &#8220;&#8221;
        for _, k := range bucket {
            data += k + &#8220;:&#8221; + hex.EncodeToString(kvs[k])
        }
        h := sha256.Sum256([]byte(data))
        
        leaves = append(leaves, &amp;MerkleNode{
            Hash: hex.EncodeToString(h[:]),
            KeyRange: bucket,
        })
    }
    return leaves
}</code></pre><p>The <code>buildMerkle</code> function then recursively builds parents by hashing pairs of child hashes, gracefully handling odd numbers of nodes:</p><pre><code>func buildMerkle(nodes []*MerkleNode) *MerkleNode {
    if len(nodes) == 0 {
        return nil
    }
    if len(nodes) == 1 {
        return nodes[0]
    }
    var parents []*MerkleNode
    for i := 0; i &lt; len(nodes); i += 2 {
        if i+1 &lt; len(nodes) {
            data := nodes[i].Hash + nodes[i+1].Hash
            h := sha256.Sum256([]byte(data))
            parents = append(parents, &amp;MerkleNode{
                Hash: hex.EncodeToString(h[:]),
                Left: nodes[i],
                Right: nodes[i+1],
            })
        } else {
            parents = append(parents, nodes[i])
        }
    }
    return buildMerkle(parents)
}</code></pre><p>After building, the single root hash summarizes the whole dataset:</p><pre><code>func (t *Tree) RootHash() string {
    t.mu.RLock()
    defer t.mu.RUnlock()
    if t.Root == nil {
        return &#8220;&#8221;
    }
    return t.Root.Hash
}</code></pre><h3><strong>Comparing Trees to Identify Differences</strong></h3><p>One of the biggest benefits of Merkle trees is their ability to efficiently detect exactly where two datasets differ &#8212; without scanning everything. This is done by recursively comparing node hashes from the roots downward:</p><pre><code>// Diff returns key ranges that differ between two Merkle trees.
func (t *Tree) Diff(other *Tree) ([][]string, error) {
    t.mu.RLock()
    defer t.mu.RUnlock()
    if t.Root == nil || other.Root == nil {
        return nil, errors.New(&#8221;cannot diff: tree(s) not built&#8221;)
    }
    diffs := make([][]string, 0)
    diffHelper(t.Root, other.Root, &amp;diffs)
    return diffs, nil
}

// Helper to recursively collect differing leaf key ranges.
func diffHelper(a, b *MerkleNode, diffs *[][]string) {
    if a == nil || b == nil {
        return
    }
    if a.Hash == b.Hash {
        return // subtree matches; no difference
    }
    if a.Left == nil &amp;&amp; a.Right == nil &amp;&amp; b.Left == nil &amp;&amp; b.Right == nil {
        // Both leaves differ; record key range
        *diffs = append(*diffs, a.KeyRange)
        return
    }
    if a.Left != nil &amp;&amp; b.Left != nil {
        diffHelper(a.Left, b.Left, diffs)
    }
    if a.Right != nil &amp;&amp; b.Right != nil {
        diffHelper(a.Right, b.Right, diffs)
    }
}</code></pre><p>This targeted approach means two large datasets can reconcile efficiently by syncing only the data within the mismatched key ranges.</p><h3><strong>Summary</strong></h3><p>This Go implementation demonstrates how Merkle trees:</p><ul><li><p>Build a cryptographic hash tree from raw data in buckets to leaves to root.</p></li><li><p>Provide a single summary root hash for efficient data verification.</p></li><li><p>Enable fast difference detection by recursive hash comparison, isolating only exact data shards that need syncing.</p></li></ul><p>Understanding and implementing these core operations is the first step toward applying Merkle trees for secure, scalable data consistency in real distributed systems.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KL2d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa507eb3-1ccd-4f0b-a954-6b05803ff9b5_720x480.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KL2d!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa507eb3-1ccd-4f0b-a954-6b05803ff9b5_720x480.heic 424w, https://substackcdn.com/image/fetch/$s_!KL2d!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa507eb3-1ccd-4f0b-a954-6b05803ff9b5_720x480.heic 848w, https://substackcdn.com/image/fetch/$s_!KL2d!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa507eb3-1ccd-4f0b-a954-6b05803ff9b5_720x480.heic 1272w, https://substackcdn.com/image/fetch/$s_!KL2d!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa507eb3-1ccd-4f0b-a954-6b05803ff9b5_720x480.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KL2d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa507eb3-1ccd-4f0b-a954-6b05803ff9b5_720x480.heic" width="720" height="480" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aa507eb3-1ccd-4f0b-a954-6b05803ff9b5_720x480.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:480,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:24559,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/178496085?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa507eb3-1ccd-4f0b-a954-6b05803ff9b5_720x480.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KL2d!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa507eb3-1ccd-4f0b-a954-6b05803ff9b5_720x480.heic 424w, https://substackcdn.com/image/fetch/$s_!KL2d!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa507eb3-1ccd-4f0b-a954-6b05803ff9b5_720x480.heic 848w, https://substackcdn.com/image/fetch/$s_!KL2d!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa507eb3-1ccd-4f0b-a954-6b05803ff9b5_720x480.heic 1272w, https://substackcdn.com/image/fetch/$s_!KL2d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa507eb3-1ccd-4f0b-a954-6b05803ff9b5_720x480.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Using Merkle Trees for Anti-Entropy: Step-by-Step</strong></h2><ol><li><p><strong>Each node builds its Merkle tree </strong>over its dataset.</p></li><li><p><strong>Nodes exchange root hashes.</strong> If equal, they&#8217;re in sync; if not, mismatches exist.</p></li><li><p><strong>Compare children hashes recursively</strong> until you reach mismatched leaves.</p></li><li><p><strong>Sync only the data for mismatched leaves,</strong> not the entire dataset.</p></li><li><p><strong>Rebuild trees and repeat as needed.</strong> This gives you fast, granular repair.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qqAG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd46715b6-269b-4baf-a82e-dbb209de8119_720x480.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qqAG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd46715b6-269b-4baf-a82e-dbb209de8119_720x480.heic 424w, https://substackcdn.com/image/fetch/$s_!qqAG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd46715b6-269b-4baf-a82e-dbb209de8119_720x480.heic 848w, https://substackcdn.com/image/fetch/$s_!qqAG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd46715b6-269b-4baf-a82e-dbb209de8119_720x480.heic 1272w, https://substackcdn.com/image/fetch/$s_!qqAG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd46715b6-269b-4baf-a82e-dbb209de8119_720x480.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qqAG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd46715b6-269b-4baf-a82e-dbb209de8119_720x480.heic" width="720" height="480" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d46715b6-269b-4baf-a82e-dbb209de8119_720x480.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:480,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22016,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/178496085?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd46715b6-269b-4baf-a82e-dbb209de8119_720x480.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qqAG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd46715b6-269b-4baf-a82e-dbb209de8119_720x480.heic 424w, https://substackcdn.com/image/fetch/$s_!qqAG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd46715b6-269b-4baf-a82e-dbb209de8119_720x480.heic 848w, https://substackcdn.com/image/fetch/$s_!qqAG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd46715b6-269b-4baf-a82e-dbb209de8119_720x480.heic 1272w, https://substackcdn.com/image/fetch/$s_!qqAG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd46715b6-269b-4baf-a82e-dbb209de8119_720x480.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Performance Considerations</strong></h2><h3><strong>Efficiency:</strong></h3><ul><li><p>Merkle trees transform O(n)<em>O</em>(<em>n</em>) comparisons into O(log&#8289;n)<em>O</em>(log<em>n</em>), minimizing bandwidth and compute, critical at scale.</p></li></ul><h3><strong>Trade-offs:</strong></h3><ul><li><p>Depth &amp; Bucket Size: Small buckets yield deep trees and fine granularity for repairs; large buckets are faster to build but coarser.</p></li><li><p>Branching Factor: Binary is simple, but higher factors further reduce tree height.</p></li><li><p>Hash Function: Use cryptographic hashes for security, but know they&#8217;re slower than simple checksums.</p></li></ul><h3><strong>Best Practices:</strong></h3><ul><li><p>Keep keys sorted and buckets consistent.</p></li><li><p>Regularly audit cryptographic choice and correctness.</p></li><li><p>Avoid over-deep trees or inconsistent bucketing, as these kill performance.</p></li></ul><h2><strong>Case Study: Cassandra&#8217;s Anti-Entropy Repair</strong></h2><p>Scenario: A network partition causes one node to lag. When it rejoins, Cassandra must reconcile data &#8212; efficiently.</p><p><strong>Process:</strong></p><ul><li><p>Each node builds a Merkle tree for a partition.</p></li><li><p>Nodes exchange Merkle roots; if mismatched, they compare subtrees.</p></li><li><p>Drill down recursively until mismatching leaves found; sync only those ranges.</p></li></ul><p><strong>Visual Flow:</strong></p><ol><li><p>Build trees</p></li><li><p>Exchange and compare roots</p></li><li><p>Drill to mismatched children</p></li><li><p>Sync only affected ranges</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!51lk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e637896-572e-44d0-9bb3-ddc9af1d3b55_720x480.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!51lk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e637896-572e-44d0-9bb3-ddc9af1d3b55_720x480.heic 424w, https://substackcdn.com/image/fetch/$s_!51lk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e637896-572e-44d0-9bb3-ddc9af1d3b55_720x480.heic 848w, https://substackcdn.com/image/fetch/$s_!51lk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e637896-572e-44d0-9bb3-ddc9af1d3b55_720x480.heic 1272w, https://substackcdn.com/image/fetch/$s_!51lk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e637896-572e-44d0-9bb3-ddc9af1d3b55_720x480.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!51lk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e637896-572e-44d0-9bb3-ddc9af1d3b55_720x480.heic" width="720" height="480" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4e637896-572e-44d0-9bb3-ddc9af1d3b55_720x480.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:480,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:37270,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/178496085?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e637896-572e-44d0-9bb3-ddc9af1d3b55_720x480.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!51lk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e637896-572e-44d0-9bb3-ddc9af1d3b55_720x480.heic 424w, https://substackcdn.com/image/fetch/$s_!51lk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e637896-572e-44d0-9bb3-ddc9af1d3b55_720x480.heic 848w, https://substackcdn.com/image/fetch/$s_!51lk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e637896-572e-44d0-9bb3-ddc9af1d3b55_720x480.heic 1272w, https://substackcdn.com/image/fetch/$s_!51lk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e637896-572e-44d0-9bb3-ddc9af1d3b55_720x480.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Conclusion</strong></h2><p>Merkle trees are the backbone of fast, scalable, and resilient data synchronization in distributed systems. They enable efficient, targeted anti-entropy repairs, keeping massive databases like Cassandra and DynamoDB consistent with minimal overhead &#8212; even in the face of network partitions or data loss.</p><p>As data volumes grow and systems span continents, intelligent anti-entropy protocols will only become more crucial. The future? Expect even tighter cryptographic guarantees, hybrid data structures, and smarter reconciliation engines &#8212; keeping the world&#8217;s distributed data trustworthy and lightning-fast.</p><h2><strong>Stay Connected!</strong></h2><ul><li><p>&#128161; Follow me on LinkedIn: <a href="https://www.linkedin.com/in/architagarwal984/">Archit Agarwal</a></p></li><li><p>&#127909; Subscribe to my YouTube: <a href="https://www.youtube.com/c/TheExceptionHandler">The Exception Handler</a></p></li><li><p>&#128236; Sign up for my newsletter: <a href="https://www.linkedin.com/newsletters/the-weekly-golang-journal-7261403856079597568/">The Weekly Golang Journal</a></p></li><li><p>&#9997;&#65039; Follow me on Medium: <a href="https://medium.com/@architagr">@architagr</a></p></li><li><p>&#128104;&#8205;&#128187; Join my subreddit: <a href="https://www.reddit.com/r/GolangJournal/">r/GolangJournal</a></p></li><li><p>&#128161; Follow me on Twitter: <a href="https://x.com/architagr">@architagr</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Security Practices and User Management]]></title><description><![CDATA[The complete "Chapter 15: Security Practices and User Management" from the book, GitHub Foundations Certification Guide (Packt, 2025), by Ayodeji Ayodele]]></description><link>https://deepengineering.net/p/security-practices-and-user-management</link><guid isPermaLink="false">https://deepengineering.net/p/security-practices-and-user-management</guid><dc:creator><![CDATA[Ayodeji Ayodele]]></dc:creator><pubDate>Thu, 06 Nov 2025 08:39:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!c1C4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to <em>Chapter 15</em>, where we explore the essentials of GitHub security and user management! By now, you&#8217;re familiar with the essentials, you have collaborated effectively, and you&#8217;ve started leveraging GitHub for your career. Now, it&#8217;s time to focus on protecting your work. In this chapter, we&#8217;ll explore GitHub&#8217;s built-in security features &#8211; such as setting up two-factor authentication, managing user permissions, and securing your CI/CD pipelines. These practices are critical for maintaining repository integrity and will also help you prepare for the certification exam.</p><p>We will cover the following main topics:</p><ul><li><p>GitHub security features</p></li><li><p>Managing access and permissions</p></li><li><p>Best practices for repository security</p></li></ul><div><hr></div><h1>GitHub security features</h1><p>In this section, we&#8217;ll explore the various security features GitHub offers to help you protect your repositories and manage user access effectively.</p><h2>Two-Factor Authentication (2FA)</h2><p>Two-factor authentication adds an extra layer of security to your GitHub account beyond just your password. By requiring a second form of verification, it ensures that even if someone gets hold of your password, they won&#8217;t be able to access your account without the second factor.</p><p>2FA is crucial for securing your account for enhanced security, mitigating against credential theft, protecting against phishing, and complying with security standards in many organizations and projects. Which 2FA methods are configurable on GitHub?</p><h3>Available 2FA methods</h3><p>GitHub offers several methods for enabling <strong>Two-Factor Authentication</strong> (<strong>2FA</strong>) to enhance the security of your account. Here are the available 2FA methods:</p><ul><li><p><strong>Time-Based One-Time Password (TOTP) authenticator apps</strong>:</p><ul><li><p>Use apps such as Google Authenticator, Authy, or Microsoft Authenticator to generate a time-based code</p></li><li><p>Recommended for its reliability and security</p></li></ul></li><li><p><strong>Short Message Service (SMS)</strong>:</p><ul><li><p>Receive a verification code via text message</p></li><li><p>Less secure compared to TOTP apps, but still an option</p></li></ul></li><li><p><strong>Physical security keys</strong>:</p><ul><li><p>Use hardware devices such as <strong>YubiKeys</strong> that support FIDO U2F or WebAuthn standards</p></li><li><p>Provide a high level of security by requiring physical possession of the key</p></li></ul></li><li><p><strong>Virtual security keys</strong>:</p><ul><li><p>Utilize built-in security features of personal devices, such as Windows Hello, Face ID, or Touch ID</p></li><li><p>Convenient and secure, leveraging device-specific authentication</p></li></ul></li><li><p><strong>GitHub Mobile</strong>:</p><ul><li><p>Use the GitHub Mobile app to authenticate using public-key cryptography</p></li><li><p>Does not rely on TOTP and provides a seamless experience</p></li></ul></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VrfZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd73d83a-b864-4607-aa88-59bce75f605a_824x543.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VrfZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd73d83a-b864-4607-aa88-59bce75f605a_824x543.png 424w, https://substackcdn.com/image/fetch/$s_!VrfZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd73d83a-b864-4607-aa88-59bce75f605a_824x543.png 848w, https://substackcdn.com/image/fetch/$s_!VrfZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd73d83a-b864-4607-aa88-59bce75f605a_824x543.png 1272w, https://substackcdn.com/image/fetch/$s_!VrfZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd73d83a-b864-4607-aa88-59bce75f605a_824x543.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VrfZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd73d83a-b864-4607-aa88-59bce75f605a_824x543.png" width="824" height="543" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cd73d83a-b864-4607-aa88-59bce75f605a_824x543.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:543,&quot;width&quot;:824,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 15.1: Available 2FA methods on GitHub&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 15.1: Available 2FA methods on GitHub" title="Figure 15.1: Available 2FA methods on GitHub" srcset="https://substackcdn.com/image/fetch/$s_!VrfZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd73d83a-b864-4607-aa88-59bce75f605a_824x543.png 424w, https://substackcdn.com/image/fetch/$s_!VrfZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd73d83a-b864-4607-aa88-59bce75f605a_824x543.png 848w, https://substackcdn.com/image/fetch/$s_!VrfZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd73d83a-b864-4607-aa88-59bce75f605a_824x543.png 1272w, https://substackcdn.com/image/fetch/$s_!VrfZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd73d83a-b864-4607-aa88-59bce75f605a_824x543.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 15.1: Available 2FA methods on GitHub</figcaption></figure></div><p>These methods offer flexibility and varying levels of security, allowing you to choose the one that best fits your needs. For the highest security, it&#8217;s recommended to use TOTP apps or physical security keys. Let&#8217;s take a look at how to set this up.</p><blockquote><p><strong>Certification tip</strong></p><p>The GitHub Foundations exam often includes questions on 2FA setup and best practices. Make sure you understand both how to enable 2FA and how to use recovery methods.</p></blockquote><h3>Setting up 2FA on GitHub</h3><p>To add an extra layer of security to your GitHub account, enable two-factor authentication (2FA) by following these steps:</p><ol><li><p>Go to your GitHub individual account settings by clicking on your <em>avatar</em> in the top-right-hand corner and clicking on <strong>Settings</strong>.</p></li><li><p>In the left sidebar, under <strong>Access</strong>, click on <strong>Password and authentication</strong>.</p></li><li><p>If not enabled yet, select <strong>Enable two-factor authentication</strong>.</p></li><li><p>Under <strong>Two-factor authentication</strong>, click <strong>Add </strong>next to the 2FA method of choice.</p></li><li><p>Follow the prompts to set up 2FA using the method selected.</p></li></ol><p>Optionally, you could set your preferred 2FA method if you enrolled in more than one.</p><p>That&#8217;s it! You&#8217;re all set up.</p><p>You will also notice <strong>Recovery codes</strong> under the <strong>Recovery options</strong> section (this section will appear only if the 2FA method is set).</p><p>Recovery codes are essential for regaining access to your GitHub account if you lose access to your 2FA credentials. These codes act as a backup method, allowing you to log in even if you can&#8217;t use your primary 2FA method, such as an authentication app or SMS. When you enable 2FA, GitHub provides a set of recovery codes that you should store securely, such as in a password manager or a safe place. If you ever lose access to your 2FA device, you can use one of these recovery codes to regain entry to your account, ensuring you are not permanently locked out.</p><p>GitHub gives you 8 recovery codes. Store them securely. You can regenerate these if needed, but old ones will be invalidated.</p><h2>Branch protection rules</h2><p>We discussed branch protection rules extensively in <em>Chapter 5</em>, <em>Branching and Merging Strategies</em>. Be sure to read this in preparation for your exam.</p><p>Branch protection rules help you enforce certain workflows and requirements before changes can be merged into your protected branches. This ensures that your codebase remains stable and secure. You can configure branch protection rules and, among many other measures, enforce code reviews, ensuring that all changes are reviewed and approved before they are merged.</p><blockquote><p><strong>Certification tip</strong></p><p>You&#8217;ll need to know how to configure branch protection rules and enable Dependabot alerts for the exam.</p></blockquote><h2>Security configurations</h2><p>GitHub provides various security settings as a collection that you can configure to enhance the security of the repositories in your organization. You can create a customized security configuration from scratch or choose the GitHub-recommended configuration that already comes preset with its settings.</p><p>GitHub-recommended security configurations are predefined settings that follow best practices to enhance security, such as enabling Dependabot alerts and secret scanning by default. Custom configurations, on the other hand, allow you to tailor security settings to meet specific needs or requirements of your project or organization, providing flexibility to adjust features such as branch protection rules and access controls.</p><p>To manage security settings at the <strong>organization level</strong>:</p><ol><li><p>Go to your organization&#8217;s main page on GitHub (remember that this is an organization, not a repo). <em>For more information on how to create an organization, review Lab 2.1 in <a href="https://subscription.packtpub.com/book/cloud-and-networking/9781836206057/20">Chapter 2</a>, Navigating the GitHub Interface</em>.</p></li><li><p>Click on <strong>Settings</strong>.</p></li><li><p>In the left sidebar, under <strong>Security</strong>, click <strong>Advanced Security</strong> to expand.</p></li><li><p>Then click on <strong>Configurations</strong>.</p></li><li><p>Choose to edit the GitHub-recommended security configuration by clicking on the edit (</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!K4VO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf293b77-2890-42f6-b67f-ebbed0088a38_25x27.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K4VO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf293b77-2890-42f6-b67f-ebbed0088a38_25x27.png 424w, https://substackcdn.com/image/fetch/$s_!K4VO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf293b77-2890-42f6-b67f-ebbed0088a38_25x27.png 848w, https://substackcdn.com/image/fetch/$s_!K4VO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf293b77-2890-42f6-b67f-ebbed0088a38_25x27.png 1272w, https://substackcdn.com/image/fetch/$s_!K4VO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf293b77-2890-42f6-b67f-ebbed0088a38_25x27.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!K4VO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf293b77-2890-42f6-b67f-ebbed0088a38_25x27.png" width="25" height="27" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/df293b77-2890-42f6-b67f-ebbed0088a38_25x27.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:27,&quot;width&quot;:25,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!K4VO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf293b77-2890-42f6-b67f-ebbed0088a38_25x27.png 424w, https://substackcdn.com/image/fetch/$s_!K4VO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf293b77-2890-42f6-b67f-ebbed0088a38_25x27.png 848w, https://substackcdn.com/image/fetch/$s_!K4VO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf293b77-2890-42f6-b67f-ebbed0088a38_25x27.png 1272w, https://substackcdn.com/image/fetch/$s_!K4VO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf293b77-2890-42f6-b67f-ebbed0088a38_25x27.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>) icon. Alternatively, you can click on <strong>New configuration</strong> to customize a new one.</p></li><li><p>Configure the security settings as needed.</p></li></ol><p>To manage <strong>repository-specific</strong> security settings:</p><ol><li><p>Navigate to the repository&#8217;s main page on GitHub.</p></li><li><p>Click on <strong>Settings</strong>.</p></li><li><p>In the left sidebar, under <strong>Security</strong>, click <strong>Advanced Security</strong>.</p></li><li><p>Enable or configure security features such as <strong>Dependabot alerts, </strong>secret scanning, and code scanning. The latter two may be missing from your view if the repository is private or internal.</p><blockquote><p>Secret scanning and code scanning are GitHub Advanced Security features and are only available as a paid subscription for private or internal repositories, or free if your repository is open source, that is, visibility is <strong>Public</strong>.</p></blockquote></li><li><p>Optionally, if you wish to configure secret scanning or code scanning, click on <strong>General</strong> from the left sidebar and scroll down to <strong>Danger Zone</strong>. Next to <strong>Change repository visibility</strong>, click on <strong>Change visibility</strong> and select <strong>Change to public</strong>. Be sure to follow the instructions.</p></li></ol><blockquote><p><strong>Certification tip</strong></p><p>Questions may come up regarding changing the visibility of a repository from public to private, or vice versa. Be sure to understand the implications of making a repo public. Read more about public repositories in <em><a href="https://subscription.packtpub.com/book/cloud-and-networking/9781836206057/20">Chapter 2</a>, Navigating the GitHub Interface</em> and <em><a href="https://subscription.packtpub.com/book/cloud-and-networking/9781836206057/11">Chapter 11</a>, Contributing to Open Source Projects</em>.</p></blockquote><p>Dependabot, secret scanning, and code scanning are all examples of security features that GitHub offers. Some of these features require a paid subscription, some are free only for public repositories, while others are completely free out of the box.</p><h2>What is Dependabot?</h2><p>Dependabot is a feature on GitHub that helps keep your project&#8217;s dependencies up to date automatically. It works by regularly checking your project&#8217;s dependency files (such as <code>package.json</code>, <code>requirements.txt</code>, etc.) for outdated packages and then creating pull requests to update them to the latest versions.</p><p>Key features of Dependabot include the following:</p><ul><li><p><strong>Automated dependency updates</strong>: It scans your project and creates pull requests to update dependencies</p></li><li><p><strong>Security alerts</strong>: It integrates with GitHub&#8217;s security features to alert you about vulnerabilities in your dependencies and can automatically fix them</p></li><li><p><strong>Customizable configuration</strong>: You can configure how often it checks for updates, which dependencies to ignore, and more, using a <code>dependabot.yml</code> file</p></li><li><p><strong>Supports multiple languages</strong>: Works with JavaScript, Python, Ruby, Java, PHP, and more</p></li></ul><p>Here is an example flow:</p><ol><li><p>You enable Dependabot in your GitHub repository.</p></li><li><p>It checks for outdated or vulnerable dependencies.</p></li><li><p>It creates a pull request with the updated version.</p></li><li><p>You review and merge the pull request.</p></li></ol><p>Now let&#8217;s talk about managing alerts and vulnerabilities.</p><h2>Security alerts and vulnerability management</h2><p>GitHub helps you stay on top of potential security issues with automated alerts and tools to manage vulnerabilities. Let&#8217;s examine some of these:</p><ul><li><p><strong>Dependabot alerts and security updates</strong>:</p><ul><li><p><strong>Dependabot alerts</strong>: Automatically scans your dependencies for known vulnerabilities and notifies you if any are found</p></li><li><p><strong>Dependabot security updates</strong>: Automatically generates pull requests to update vulnerable dependencies to secure versions</p></li></ul></li></ul><p>How can you enable and manage Dependabot alerts?</p><ol><li><p>Go to the repository&#8217;s main page on GitHub.</p></li><li><p>Click on <strong>Settings</strong>.</p></li><li><p>In the left sidebar, under <strong>Security</strong>, click <strong>Advanced Security</strong>.</p></li><li><p>Under <strong>Dependabot alerts</strong>, click <strong>Enable</strong> if not already enabled.</p></li><li><p>Afterward, you can review and manage alerts from the <strong>Security</strong> tab of the repository.</p></li></ol><ul><li><p><strong>Code scanning</strong>:</p><ul><li><p><strong>CodeQL</strong>: A powerful code analysis engine that scans your code for security vulnerabilities and coding errors. It integrates with GitHub Actions to run scans on every push or pull request.</p></li><li><p><strong>Autofix</strong>: Uses AI to suggest fixes for detected vulnerabilities, streamlining the remediation process.</p></li></ul></li><li><p><strong>Secret scanning</strong>: Detects and alerts you if sensitive information, such as API keys or passwords, is accidentally committed to your repository. This helps prevent unauthorized access and potential security breaches.</p></li><li><p><strong>Security overview dashboard</strong>: Provides a centralized view of your security alerts and vulnerabilities across all repositories. This dashboard helps you prioritize and manage security issues more effectively.</p></li><li><p><strong>Vulnerability management integrations</strong>: Integrates with third-party vulnerability management tools to consolidate and prioritize vulnerabilities, automate risk mitigation, and visualize alerts within your existing security posture.</p></li></ul><p>Talking about third-party integrations, GitHub supports receiving <strong>Static Analysis Results Interchange Format</strong> (<strong>SARIF</strong>) reports from various third-party security tools. Some of the commonly used tools include the following:</p><ul><li><p><strong>ESLint</strong>: A popular tool for identifying and reporting on patterns found in ECMAScript/JavaScript code</p></li><li><p><strong>Bandit</strong>: A tool designed to find common security issues in Python code</p></li><li><p><strong>Brakeman</strong>: A static analysis tool that checks Ruby on Rails applications for security vulnerabilities</p></li><li><p><strong>Checkmarx</strong>: A comprehensive <strong>Static Application Security Testing</strong> (<strong>SAST</strong>) tool</p></li><li><p><strong>Fortify</strong>: A suite of tools for static and dynamic application security testing</p></li><li><p><strong>SonarQube</strong>: An open source platform for continuous inspection of code quality</p></li><li><p><strong>Veracode</strong>: A cloud-based service for static and dynamic application security testing</p></li></ul><p>These tools generate SARIF files that can be uploaded to GitHub, allowing you to view and manage security alerts directly within your repository.</p><p>To handle security advisories and alerts, navigate to the <strong>Security</strong> tab of the repository (<em>you will find this tab on both the organization and the repository levels</em>). Examine the difference between the <strong>Security</strong> tabs of both levels. You will notice a stark difference in what you see. This is because the security overview at the organization level rolls up all the security advisories across all its repos, whereas the scope of the repo level is limited to only vulnerability findings of that repo.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Gjr6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2eb9205-79ee-4581-9d45-02378640b9d2_825x487.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Gjr6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2eb9205-79ee-4581-9d45-02378640b9d2_825x487.png 424w, https://substackcdn.com/image/fetch/$s_!Gjr6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2eb9205-79ee-4581-9d45-02378640b9d2_825x487.png 848w, https://substackcdn.com/image/fetch/$s_!Gjr6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2eb9205-79ee-4581-9d45-02378640b9d2_825x487.png 1272w, https://substackcdn.com/image/fetch/$s_!Gjr6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2eb9205-79ee-4581-9d45-02378640b9d2_825x487.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Gjr6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2eb9205-79ee-4581-9d45-02378640b9d2_825x487.png" width="825" height="487" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e2eb9205-79ee-4581-9d45-02378640b9d2_825x487.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:487,&quot;width&quot;:825,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 15.2: Security Overview at the organization level&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 15.2: Security Overview at the organization level" title="Figure 15.2: Security Overview at the organization level" srcset="https://substackcdn.com/image/fetch/$s_!Gjr6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2eb9205-79ee-4581-9d45-02378640b9d2_825x487.png 424w, https://substackcdn.com/image/fetch/$s_!Gjr6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2eb9205-79ee-4581-9d45-02378640b9d2_825x487.png 848w, https://substackcdn.com/image/fetch/$s_!Gjr6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2eb9205-79ee-4581-9d45-02378640b9d2_825x487.png 1272w, https://substackcdn.com/image/fetch/$s_!Gjr6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2eb9205-79ee-4581-9d45-02378640b9d2_825x487.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 15.2: Security Overview at the organization level</figcaption></figure></div><p>These tools and features help you maintain a robust security posture by automating the detection and management of vulnerabilities, ensuring your codebase remains secure.</p><p>In the next section, we will talk about how permissions and authorization can help in securing your code.</p><div><hr></div><h1>Managing access and permissions</h1><p>Effective management of access and permissions is crucial for maintaining the security and integrity of your GitHub repositories. This section delves into the various methods GitHub provides to control who has access to your repositories and what they can do.</p><h2>User roles and permissions</h2><p>GitHub offers a range of user roles to help you manage access and permissions effectively. Understanding these roles is key to maintaining a secure and organized workflow.</p><h3>Overview of different user roles</h3><p>User roles on GitHub come in three tiers: roles at the Enterprise level, Organization level, and Repository level. Here are the default roles at the Enterprise and Organization levels:</p><ul><li><p><strong>Owner</strong>: The owner has full administrative access to the organization and its repositories. This role can manage settings, users, and billing.</p></li><li><p><strong>Member</strong>: Members have basic access to repositories, typically for contributing code. They can create issues, submit pull requests, and review code.</p></li><li><p><strong>Billing manager</strong>: Billing managers can manage billing settings such as changing billing plans, managing payment methods, downloading and receiving receipts, or managing sponsorships.</p></li></ul><p>When inviting new collaborators to your organization for the first time, you choose one of these three.</p><blockquote><p><strong>Certification tip</strong></p><p>Be prepared to identify role-based use cases. The exam may ask you to match specific user scenarios with appropriate GitHub roles.</p></blockquote><h3>Assigning roles to users</h3><p>To manage access within your organization, you can assign specific roles to members by following these steps:</p><ol><li><p>Navigate to your organization&#8217;s main page on GitHub.</p></li><li><p>Click on the <strong>People </strong>tab in the organization&#8217;s navigation bar.</p></li><li><p>If the user doesn&#8217;t already exist in the organization, you can invite them by clicking on <strong>Invite member</strong>.</p></li><li><p>Supply the user&#8217;s GitHub handle and click on <strong>Invite</strong>.</p></li><li><p>For an existing member, locate the user you want to assign a role to and click on the ellipsis dropdown next to their name and select <strong>Change role&#8230;</strong>.</p></li><li><p>Select the appropriate role and click on <strong>Send invitation</strong> (for new invitations) or <strong>Change role</strong> (existing members).</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8fDS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e5b7f3b-110c-4891-94a3-9a3fb67455b3_759x566.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8fDS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e5b7f3b-110c-4891-94a3-9a3fb67455b3_759x566.png 424w, https://substackcdn.com/image/fetch/$s_!8fDS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e5b7f3b-110c-4891-94a3-9a3fb67455b3_759x566.png 848w, https://substackcdn.com/image/fetch/$s_!8fDS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e5b7f3b-110c-4891-94a3-9a3fb67455b3_759x566.png 1272w, https://substackcdn.com/image/fetch/$s_!8fDS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e5b7f3b-110c-4891-94a3-9a3fb67455b3_759x566.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8fDS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e5b7f3b-110c-4891-94a3-9a3fb67455b3_759x566.png" width="759" height="566" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7e5b7f3b-110c-4891-94a3-9a3fb67455b3_759x566.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:566,&quot;width&quot;:759,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 15.3: Example invitation showing the default available roles&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 15.3: Example invitation showing the default available roles" title="Figure 15.3: Example invitation showing the default available roles" srcset="https://substackcdn.com/image/fetch/$s_!8fDS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e5b7f3b-110c-4891-94a3-9a3fb67455b3_759x566.png 424w, https://substackcdn.com/image/fetch/$s_!8fDS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e5b7f3b-110c-4891-94a3-9a3fb67455b3_759x566.png 848w, https://substackcdn.com/image/fetch/$s_!8fDS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e5b7f3b-110c-4891-94a3-9a3fb67455b3_759x566.png 1272w, https://substackcdn.com/image/fetch/$s_!8fDS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e5b7f3b-110c-4891-94a3-9a3fb67455b3_759x566.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 15.3: Example invitation showing the default available roles</figcaption></figure></div><ol><li><p>Click on <strong>Change role&#8230;</strong></p></li><li><p>Select the appropriate role (<strong>Owner</strong> or <strong>Member</strong>) from the list displayed.</p></li><li><p>Click on <strong>Change role</strong>.</p></li><li><p>Alternatively, if the user only needs to be a billing manager, click on the <strong>Invite a billing manager</strong> link at the bottom of the user invitation screen (Step 3), or go to the organization&#8217;s settings and select <strong>Billing and licensing</strong> from the left navigation bar and invite them.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iva2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d06eeff-a792-405c-a413-88df6f1650cf_702x444.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iva2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d06eeff-a792-405c-a413-88df6f1650cf_702x444.png 424w, https://substackcdn.com/image/fetch/$s_!iva2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d06eeff-a792-405c-a413-88df6f1650cf_702x444.png 848w, https://substackcdn.com/image/fetch/$s_!iva2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d06eeff-a792-405c-a413-88df6f1650cf_702x444.png 1272w, https://substackcdn.com/image/fetch/$s_!iva2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d06eeff-a792-405c-a413-88df6f1650cf_702x444.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iva2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d06eeff-a792-405c-a413-88df6f1650cf_702x444.png" width="702" height="444" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6d06eeff-a792-405c-a413-88df6f1650cf_702x444.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:444,&quot;width&quot;:702,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Fig.15.4: Inviting a billing manager to GitHub&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Fig.15.4: Inviting a billing manager to GitHub" title="Fig.15.4: Inviting a billing manager to GitHub" srcset="https://substackcdn.com/image/fetch/$s_!iva2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d06eeff-a792-405c-a413-88df6f1650cf_702x444.png 424w, https://substackcdn.com/image/fetch/$s_!iva2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d06eeff-a792-405c-a413-88df6f1650cf_702x444.png 848w, https://substackcdn.com/image/fetch/$s_!iva2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d06eeff-a792-405c-a413-88df6f1650cf_702x444.png 1272w, https://substackcdn.com/image/fetch/$s_!iva2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d06eeff-a792-405c-a413-88df6f1650cf_702x444.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Fig.15.4: Inviting a billing manager to GitHub</figcaption></figure></div><p>In addition to these three, GitHub provides some more granular roles that help you define granular permissions to what a member can or cannot do at the different levels.</p><p>Here is a table of additional pre-defined roles that can be used:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6uIe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2314202-5372-402e-a996-6ed9768f1c05_1336x787.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6uIe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2314202-5372-402e-a996-6ed9768f1c05_1336x787.png 424w, https://substackcdn.com/image/fetch/$s_!6uIe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2314202-5372-402e-a996-6ed9768f1c05_1336x787.png 848w, https://substackcdn.com/image/fetch/$s_!6uIe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2314202-5372-402e-a996-6ed9768f1c05_1336x787.png 1272w, https://substackcdn.com/image/fetch/$s_!6uIe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2314202-5372-402e-a996-6ed9768f1c05_1336x787.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6uIe!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2314202-5372-402e-a996-6ed9768f1c05_1336x787.png" width="1200" height="706.8862275449102" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c2314202-5372-402e-a996-6ed9768f1c05_1336x787.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:787,&quot;width&quot;:1336,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:140407,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/178160677?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2314202-5372-402e-a996-6ed9768f1c05_1336x787.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6uIe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2314202-5372-402e-a996-6ed9768f1c05_1336x787.png 424w, https://substackcdn.com/image/fetch/$s_!6uIe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2314202-5372-402e-a996-6ed9768f1c05_1336x787.png 848w, https://substackcdn.com/image/fetch/$s_!6uIe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2314202-5372-402e-a996-6ed9768f1c05_1336x787.png 1272w, https://substackcdn.com/image/fetch/$s_!6uIe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2314202-5372-402e-a996-6ed9768f1c05_1336x787.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Table 15.1: List of predefined roles at can be used at the Organization and Repository levels</figcaption></figure></div><p>In addition to these predefined roles, you can create custom roles with a select combination of permissions if one of these predefined roles doesn&#8217;t exactly fit your needs. This can be done at the Enterprise, Organization, and Repository levels.</p><p>The custom role feature is only available on GitHub Enterprise. In addition, the custom role at the Enterprise level is only available for preview as of early 2025. Refer to GitHub Docs for the latest availability.</p><p>Now let&#8217;s move on to using teams for access control.</p><h2>Team management</h2><p>Teams allow you to group users and manage their access to repositories more efficiently. This is particularly useful for larger organizations with multiple projects. A team can either be <strong>visible</strong> or <strong>secret</strong>.</p><p>Visible teams can be seen and <code>@mentioned</code> by members of the organization, while secret teams can only be seen by their members. This will be specified during team creation. Teams can also be nested, with one team being the parent of another team.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mToD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f33ac30-8841-4452-bc68-79d9ef0cf64b_823x244.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mToD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f33ac30-8841-4452-bc68-79d9ef0cf64b_823x244.png 424w, https://substackcdn.com/image/fetch/$s_!mToD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f33ac30-8841-4452-bc68-79d9ef0cf64b_823x244.png 848w, https://substackcdn.com/image/fetch/$s_!mToD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f33ac30-8841-4452-bc68-79d9ef0cf64b_823x244.png 1272w, https://substackcdn.com/image/fetch/$s_!mToD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f33ac30-8841-4452-bc68-79d9ef0cf64b_823x244.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mToD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f33ac30-8841-4452-bc68-79d9ef0cf64b_823x244.png" width="823" height="244" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5f33ac30-8841-4452-bc68-79d9ef0cf64b_823x244.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:244,&quot;width&quot;:823,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:66658,&quot;alt&quot;:&quot;Figure 15.5: A GitHub Team can be visible or secret&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 15.5: A GitHub Team can be visible or secret" title="Figure 15.5: A GitHub Team can be visible or secret" srcset="https://substackcdn.com/image/fetch/$s_!mToD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f33ac30-8841-4452-bc68-79d9ef0cf64b_823x244.png 424w, https://substackcdn.com/image/fetch/$s_!mToD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f33ac30-8841-4452-bc68-79d9ef0cf64b_823x244.png 848w, https://substackcdn.com/image/fetch/$s_!mToD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f33ac30-8841-4452-bc68-79d9ef0cf64b_823x244.png 1272w, https://substackcdn.com/image/fetch/$s_!mToD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f33ac30-8841-4452-bc68-79d9ef0cf64b_823x244.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 15.5: A GitHub Team can be visible or secret</figcaption></figure></div><p>Let&#8217;s see how we create teams.</p><h3>Creating and managing teams</h3><p>Here&#8217;s how to create a team:</p><ol><li><p>Go to your organization&#8217;s main page on GitHub.</p></li><li><p>Click on <strong>Teams</strong> in the organization&#8217;s navigation bar.</p></li><li><p>Click <strong>New team</strong> to create a new team.</p></li><li><p>Enter the team name and description, then click <strong>Create team</strong>.</p></li><li><p>Add members to the team by clicking <strong>Add a member</strong> and selecting users from the list.</p></li></ol><p>Here are some important use cases for team visibility and notifications:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wi70!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc29c97b-7915-4f6e-a783-01918149fd1e_860x382.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wi70!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc29c97b-7915-4f6e-a783-01918149fd1e_860x382.png 424w, https://substackcdn.com/image/fetch/$s_!wi70!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc29c97b-7915-4f6e-a783-01918149fd1e_860x382.png 848w, https://substackcdn.com/image/fetch/$s_!wi70!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc29c97b-7915-4f6e-a783-01918149fd1e_860x382.png 1272w, https://substackcdn.com/image/fetch/$s_!wi70!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc29c97b-7915-4f6e-a783-01918149fd1e_860x382.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wi70!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc29c97b-7915-4f6e-a783-01918149fd1e_860x382.png" width="860" height="382" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dc29c97b-7915-4f6e-a783-01918149fd1e_860x382.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:382,&quot;width&quot;:860,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:63158,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/178160677?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc29c97b-7915-4f6e-a783-01918149fd1e_860x382.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wi70!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc29c97b-7915-4f6e-a783-01918149fd1e_860x382.png 424w, https://substackcdn.com/image/fetch/$s_!wi70!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc29c97b-7915-4f6e-a783-01918149fd1e_860x382.png 848w, https://substackcdn.com/image/fetch/$s_!wi70!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc29c97b-7915-4f6e-a783-01918149fd1e_860x382.png 1272w, https://substackcdn.com/image/fetch/$s_!wi70!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc29c97b-7915-4f6e-a783-01918149fd1e_860x382.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Assigning repository access to teams</h3><p>To assign access to repositories to teams, do the following:</p><ol><li><p>Navigate to the team&#8217;s page on GitHub.</p></li><li><p>Click on the <strong>Repositories</strong> tab in the team&#8217;s navigation bar.</p></li><li><p>Click <strong>Add a repository</strong> and select the repository you want to grant access to.</p></li><li><p>Set the desired permission level (<strong>Read</strong>, <strong>Triage</strong>, <strong>Write</strong>, or others) for the team. <em>More roles and the ability to create custom roles are available if you have a paid GitHub Enterprise subscription</em>.</p></li></ol><h2>Collaborator access control</h2><p>Collaborators are individuals who are granted access to specific repositories. This is useful for managing external contributors or contractors.</p><h3>Adding collaborators to repositories:</h3><p>To give others access to a specific repository without adding them to the entire organization, you can add them as collaborators:</p><ol><li><p>Navigate to the repository&#8217;s main page on GitHub.</p></li><li><p>Click on <strong>Settings</strong> in the repository&#8217;s navigation bar.</p></li><li><p>In the left sidebar, click <strong>Collaborators and teams </strong>(org-owned repos) or<strong> Collaborators </strong>(user-owned repos).</p></li><li><p>Click <strong>Add people</strong> and start typing the username of the person you want to add.</p></li><li><p>Select their name and set the appropriate permission level (<strong>Read</strong>, <strong>Triage</strong>, <strong>Write</strong>, <strong>Maintain</strong>, <strong>Admin</strong>) for the collaborator.</p></li></ol><h3>Setting permissions for collaborators:</h3><p>Once collaborators are added, you can adjust their access levels using the following steps:</p><ol><li><p>In the <strong>Collaborators and teams</strong> section, find the collaborator you want to manage.</p></li><li><p>Click on their permission dropdown and select the desired permission level.</p></li></ol><p>One common pitfall is <strong>misconfigured roles or permissions</strong>, which can inadvertently expose sensitive code. For example, imagine a scenario where a developer creates a private repository for an internal tool but mistakenly assigns a <strong>Read</strong> role to an external contractor at the organization level. Because the repository inherits permissions from the organization, the contractor now has unintended access to the private repository. This kind of oversight can lead to accidental data leaks or unauthorized code access.</p><p>To prevent such incidents, always follow the principle of least privilege, regularly audit repository access, and use fine-grained personal access tokens for automation and integrations.</p><p>Another great feature to manage access and permissions is tokens. Two examples of tokens on GitHub are OAuth and <strong>Personal Access Tokens</strong> (<strong>PAT</strong>).</p><h2>OAuth and personal access tokens</h2><p>OAuth and personal access tokens provide secure ways to authenticate and authorize access to your GitHub account and repositories. These methods are essential for integrating third-party applications and services, that is, when you are not using an interactive login of a person. Here&#8217;s where you&#8217;ll find each of these on GitHub:</p><h3>Managing OAuth applications</h3><p>To review and manage third-party applications connected to your GitHub account, follow these steps:</p><ol><li><p>Go to your GitHub account settings.</p></li><li><p>In the left sidebar, click <strong>Developer settings</strong>.</p></li><li><p>Click <strong>OAuth Apps</strong> to view and manage your OAuth applications.</p></li><li><p>Review the list of authorized applications and revoke access if necessary.</p></li></ol><h3>Creating and using personal access tokens</h3><p>To authenticate non-interactive scripts or services, you can generate a personal access token as shown below:</p><ol><li><p>Go to your GitHub account settings.</p></li><li><p>In the left sidebar, click <strong>Developer settings</strong>.</p></li><li><p>Click <strong>Personal access tokens</strong>.</p></li><li><p>Select either <strong>Fine-grained tokens</strong> or <strong>Tokens (classic)</strong> in the submenu.</p></li><li><p>Click <strong>Generate new token </strong>(for fine-grained tokens) or select <strong>Token (classic)</strong> again for the classic token option.</p></li><li><p>Select the scopes or permissions you want to grant this token, such as repo, <code>admin:org</code>, or user.</p></li><li><p>Click <strong>Generate token</strong> and copy the token for use in your applications. Store it securely, as it will not be displayed again.</p></li></ol><p>You would notice by now that there are two types of personal access tokens on GitHub: <strong>classic</strong> and <strong>fine-grained</strong>.</p><p>Both types co-exist, with classic being the older. It is expected that GitHub will deprecate classic PAT in favour of fine-grained PAT in the future, but both of them can be used interchangeably today. Let&#8217;s quickly enumerate the differences between the two.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ro1-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2af690-a05b-43e5-9b50-ec6bc977d1f6_1316x721.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ro1-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2af690-a05b-43e5-9b50-ec6bc977d1f6_1316x721.png 424w, https://substackcdn.com/image/fetch/$s_!Ro1-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2af690-a05b-43e5-9b50-ec6bc977d1f6_1316x721.png 848w, https://substackcdn.com/image/fetch/$s_!Ro1-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2af690-a05b-43e5-9b50-ec6bc977d1f6_1316x721.png 1272w, https://substackcdn.com/image/fetch/$s_!Ro1-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2af690-a05b-43e5-9b50-ec6bc977d1f6_1316x721.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ro1-!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2af690-a05b-43e5-9b50-ec6bc977d1f6_1316x721.png" width="1200" height="657.4468085106383" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f2af690-a05b-43e5-9b50-ec6bc977d1f6_1316x721.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:721,&quot;width&quot;:1316,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:139621,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/178160677?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2af690-a05b-43e5-9b50-ec6bc977d1f6_1316x721.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ro1-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2af690-a05b-43e5-9b50-ec6bc977d1f6_1316x721.png 424w, https://substackcdn.com/image/fetch/$s_!Ro1-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2af690-a05b-43e5-9b50-ec6bc977d1f6_1316x721.png 848w, https://substackcdn.com/image/fetch/$s_!Ro1-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2af690-a05b-43e5-9b50-ec6bc977d1f6_1316x721.png 1272w, https://substackcdn.com/image/fetch/$s_!Ro1-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2af690-a05b-43e5-9b50-ec6bc977d1f6_1316x721.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In summary, GitHub offers a range of measures to manage access and grant permissions to users and third-party applications and services. It is important to know which ones to combine to ensure the security of your code. Up next, let&#8217;s consider some good security practices for repo security.</p><div><hr></div><h1>Best practices for repository security</h1><p>Ensuring the security of your repositories is paramount to protecting your code and maintaining the integrity of your projects. In this section, we&#8217;ll cover best practices for securing your repositories on GitHub.</p><h2>Code scanning with Static Application Security Testing (SAST) tools</h2><p><strong>Static Application Security Testing</strong> (<strong>SAST</strong>) tools help identify security vulnerabilities in your codebase before they become issues in production. GitHub has a code scanning product. It is sold separately but can be integrated natively into your repos and organizations. It&#8217;s labelled <strong>GitHub Advanced Security</strong> (<strong>GHAS</strong>). GHAS is a <em>paid</em> subscription product, an add-on that you can purchase in addition to your GitHub subscription. If your codebase is open source, most of GHAS&#8217;s security features are <strong>free</strong> for use.</p><p>Here&#8217;s how to integrate and use these tools. You have to do this on a public repo if you haven&#8217;t purchased GHAS:</p><ul><li><p><strong>Integrating SAST tools in your workflow</strong>: Here&#8217;s how to set up code scanning with SAST tools:</p><ol><li><p>Navigate to the repository&#8217;s main page on GitHub.</p></li><li><p>Click on <strong>Security</strong> in the repository&#8217;s navigation bar.</p></li><li><p>Click <strong>Set up code scanning</strong>.</p></li><li><p>Choose a code scanning tool, such as CodeQL, and follow the prompts to configure it.</p></li></ol></li></ul><p>Examples of SAST tools include the following:</p><ul><li><p><strong>CodeQL</strong>: A powerful code analysis engine that scans your code for security vulnerabilities and coding errors</p></li><li><p><strong>Dependabot Alerts</strong>: Automatically scans your dependencies for known vulnerabilities and notifies you if any are found</p></li></ul><ul><li><p><strong>Interpreting scan results</strong>: To review and act on scan results, take the following steps:</p><ol><li><p>Go to the <strong>Security</strong> tab of the repository.</p></li><li><p>Click on <strong>Code scanning alerts</strong> to view the results.</p></li><li><p>Review the alerts and take appropriate action to fix the identified issues.</p></li></ol></li></ul><h2>CI/CD pipeline security measures</h2><p>Securing your <strong>Continuous Integration/Continuous Deployment</strong> (<strong>CI/CD</strong>) pipelines is crucial to ensure that your code remains secure throughout the development lifecycle. Follow these steps to secure your pipelines:</p><ul><li><p><strong>Securing CI/CD Pipelines Using GitHub Actions</strong>: To secure your CI/CD pipelines, take these steps:</p><ol><li><p>Navigate to the repository&#8217;s main page on GitHub.</p></li><li><p>Click on <strong>Actions</strong> in the repository&#8217;s navigation bar.</p></li><li><p>Set up workflows to include security checks, such as running SAST tools or dependency checks.</p></li></ol></li></ul><blockquote><p><strong>Mini-case example</strong></p><p>A development team noticed that their CI/CD pipeline was deploying code with outdated dependencies that had known vulnerabilities. The issue stemmed from a missing dependency scanning step in their GitHub Actions workflow. To mitigate this, they integrated Dependabot and added a step in their workflow to run <code>npm audit</code> during each build. This change helped catch vulnerable packages early and prevented insecure code from reaching production.</p></blockquote><ul><li><p><strong>Implementing secrets management in workflows</strong>: To manage secrets securely in your workflows, take these steps:</p><ol><li><p>Go to the repository&#8217;s main page on GitHub.</p></li><li><p>Click on <strong>Settings</strong>.</p></li><li><p>In the left sidebar, click <strong>Secrets and variables</strong>.</p></li><li><p>Then, select <strong>Actions</strong> from the submenu.</p></li><li><p>Click <strong>New repository secret</strong> to add secrets, such as API keys or tokens, securely.</p></li><li><p>Reference these secrets in your GitHub Actions workflows to avoid exposing sensitive information.</p></li></ol></li></ul><h2>Monitoring and auditing activities</h2><p>Regular monitoring and auditing of repository activities help you detect and respond to suspicious actions promptly. Here&#8217;s how to monitor and audit activities:</p><ul><li><p><strong>Using audit logs to monitor repository activities</strong>: To monitor repository activities, take these steps:</p><ol><li><p>Go to your organization&#8217;s main page on GitHub.</p></li><li><p>Click on <strong>Settings</strong>.</p></li><li><p>In the left sidebar, under the <strong>Archive</strong> section, click <strong>Logs</strong>,</p></li><li><p>Then, select <strong>Audit log</strong> from the submenu.</p></li><li><p>Review the audit log entries to monitor activities such as user logins, repository changes, and permission updates.</p></li></ol></li><li><p><strong>Setting up alerts for suspicious activities</strong>: To set up alerts for suspicious activities, take these steps:</p><ol><li><p>Use GitHub&#8217;s built-in security alerts to notify you of potential security issues.</p></li><li><p>Integrate with third-party monitoring tools to receive real-time alerts for suspicious activities.</p></li></ol></li></ul><h2>Incident response and recovery</h2><p>Being prepared for security incidents and having a plan for recovery is essential for minimizing the impact of security breaches. Follow these steps for incident response and recovery:</p><ul><li><p><strong>Preparing for security incidents</strong>: To prepare for security incidents, take these steps:</p><ol><li><p>Develop an incident response plan that outlines the steps to take in case of a security breach.</p></li><li><p>Ensure that all team members are aware of the plan and their roles in the response process.</p></li></ol></li><li><p><strong>Steps for incident response and recovery</strong>: To respond to and recover from security incidents, take these steps:</p><ol><li><p>Identify and contain the breach to prevent further damage.</p></li><li><p>Investigate the cause of the breach and assess the impact.</p></li><li><p>Remediate the vulnerabilities that led to the breach.</p></li><li><p>Communicate with stakeholders and provide updates on the incident and recovery efforts.</p></li><li><p>Review and update security policies and practices to prevent future incidents.</p></li></ol></li></ul><p>Et voila! This concludes the basics when it comes to security on GitHub. Let&#8217;s summarize what we learned.</p><div><hr></div><h1>Summary</h1><p>In this chapter, we delved into the intricacies of security practices and user management on GitHub. We had already mastered the essentials of effective collaboration, but we know that with great code comes great responsibility. We explored the robust security features GitHub offers, such as two-factor authentication, which added an extra layer of security to our accounts. We learned about the various methods available for 2FA, including authenticator apps, SMS, physical security keys, and GitHub Mobile.</p><p>We also discussed branch protection rules, which ensured our codebase remained stable and secure by enforcing workflows and requirements before changes could be merged. Additionally, we examined security configurations, both GitHub-recommended and custom, to enhance the security of our repositories. We looked at the management of security alerts and vulnerabilities through tools such as Dependabot, CodeQL, and secret scanning, which helped us stay on top of potential security issues.</p><p>Managing access and permissions was another crucial aspect we covered. We understood the importance of user roles and permissions at different levels, from enterprise to repository, and how to assign these roles effectively. We also explored the use of teams for access control, creating and managing teams to streamline our workflow. Finally, we looked at OAuth and personal access tokens, which provided secure ways to authenticate and authorize access to our GitHub account and repositories.</p><p>Overall, this chapter equipped us with the knowledge and tools to maintain a robust security posture and manage user access effectively on GitHub.</p><p>Let&#8217;s do a short quiz.</p><div><hr></div><h1>Test your knowledge</h1><p>Review all GitHub security and user management features, especially permission models, 2FA, and CI/CD hardening techniques &#8211; they appear frequently on the certification.</p><ol><li><p>Which of the following methods is considered the most secure for enabling <strong>Two-Factor Authentication</strong> (<strong>2FA</strong>) on GitHub?</p><ol><li><p><strong>Short Message Service</strong> (<strong>SMS</strong>)</p></li><li><p><strong>Time-Based One-Time Password</strong> (<strong>TOTP</strong>) authenticator apps</p></li><li><p>Virtual security keys</p></li><li><p>Physical security keys</p></li></ol></li><li><p>What is the primary purpose of <strong>Dependabot Security Updates</strong> in GitHub?</p><ol><li><p>To scan your code for security vulnerabilities and coding errors</p></li><li><p>To automatically generate pull requests to update vulnerable dependencies to secure versions</p></li><li><p>To detect and alert you if sensitive information is accidentally committed to your repository</p></li><li><p>To provide a centralized view of your security alerts and vulnerabilities across all repositories</p></li></ol></li><li><p>Which role in GitHub is best suited to managing security policies, security alerts, and security configurations for an organization and all its repositories?</p><ol><li><p>Owner</p></li><li><p>Security manager</p></li><li><p>Admin</p></li><li><p>CI/CD admin</p></li></ol></li></ol><div><hr></div><h1>Useful links</h1><ul><li><p>Authentication documentation: <a href="https://shorturl.at/jevdZ">https://docs.github.com/en/enterprise-cloud@latest/authentication</a></p></li><li><p>About GitHub security features: <a href="https://shorturl.at/RNF8Y">https://docs.github.com/en/enterprise-cloud@latest/code-security/getting-started/github-security-features#about-githubs-security-features</a></p></li><li><p>About GitHub Advanced Security: <a href="https://shorturl.at/7ZMJY">https://docs.github.com/en/enterprise-cloud@latest/get-started/learning-about-github/about-github-advanced-security</a></p></li></ul><div><hr></div><p>To build practical mastery of Git and GitHub&#8212; from version control basics to collaborative workflows, secure automation, and AI-assisted productivity&#8212;check out <em><strong><a href="https://www.packtpub.com/en-us/product/github-foundations-certification-guide-9781836206040">GitHub Foundations Certification Guide</a></strong></em> by Ayodeji Ayodele (Packt, 2025). Through step-by-step labs, real-world projects, and exam strategies, it helps you prepare for the GitHub Foundations certification while adopting best practices for issues and pull requests, GitHub Projects, privacy and security controls, and GitHub Copilot&#8212;so you can level up your skills and ship better software, faster.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-us/product/github-foundations-certification-guide-9781836206040" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c1C4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!c1C4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!c1C4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!c1C4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!c1C4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775" width="372" height="458.86813186813185" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:372,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;GitHub Foundations Certification Guide&quot;,&quot;title&quot;:&quot;GitHub Foundations Certification Guide&quot;,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-us/product/github-foundations-certification-guide-9781836206040&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="GitHub Foundations Certification Guide" title="GitHub Foundations Certification Guide" srcset="https://substackcdn.com/image/fetch/$s_!c1C4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!c1C4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!c1C4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!c1C4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here&#8217;s what some readers have said:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cglw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab89407-eb5e-4431-afd7-b68983a5c8cd_866x485.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cglw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab89407-eb5e-4431-afd7-b68983a5c8cd_866x485.png 424w, https://substackcdn.com/image/fetch/$s_!cglw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab89407-eb5e-4431-afd7-b68983a5c8cd_866x485.png 848w, https://substackcdn.com/image/fetch/$s_!cglw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab89407-eb5e-4431-afd7-b68983a5c8cd_866x485.png 1272w, https://substackcdn.com/image/fetch/$s_!cglw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab89407-eb5e-4431-afd7-b68983a5c8cd_866x485.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cglw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab89407-eb5e-4431-afd7-b68983a5c8cd_866x485.png" width="866" height="485" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fab89407-eb5e-4431-afd7-b68983a5c8cd_866x485.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:485,&quot;width&quot;:866,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:150335,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/178064311?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e55c92f-6989-49dc-9cdc-702306914746_885x485.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!cglw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab89407-eb5e-4431-afd7-b68983a5c8cd_866x485.png 424w, https://substackcdn.com/image/fetch/$s_!cglw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab89407-eb5e-4431-afd7-b68983a5c8cd_866x485.png 848w, https://substackcdn.com/image/fetch/$s_!cglw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab89407-eb5e-4431-afd7-b68983a5c8cd_866x485.png 1272w, https://substackcdn.com/image/fetch/$s_!cglw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab89407-eb5e-4431-afd7-b68983a5c8cd_866x485.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VEfR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VEfR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 424w, https://substackcdn.com/image/fetch/$s_!VEfR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 848w, https://substackcdn.com/image/fetch/$s_!VEfR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 1272w, https://substackcdn.com/image/fetch/$s_!VEfR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VEfR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png" width="871" height="386" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:386,&quot;width&quot;:871,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:84580,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/178064311?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!VEfR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 424w, https://substackcdn.com/image/fetch/$s_!VEfR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 848w, https://substackcdn.com/image/fetch/$s_!VEfR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 1272w, https://substackcdn.com/image/fetch/$s_!VEfR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Jch1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Jch1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 424w, https://substackcdn.com/image/fetch/$s_!Jch1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 848w, https://substackcdn.com/image/fetch/$s_!Jch1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 1272w, https://substackcdn.com/image/fetch/$s_!Jch1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Jch1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png" width="856" height="388" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d8328e5f-7901-4408-8343-78bba89ca87a_856x388.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:388,&quot;width&quot;:856,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:95238,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/178064311?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!Jch1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 424w, https://substackcdn.com/image/fetch/$s_!Jch1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 848w, https://substackcdn.com/image/fetch/$s_!Jch1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 1272w, https://substackcdn.com/image/fetch/$s_!Jch1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>]]></content:encoded></item><item><title><![CDATA[Introduction to Application Development Frameworks]]></title><description><![CDATA[The complete "Chapter 1: Introduction to Application Development Frameworks" from the book, Building an Application Development Framework (Packt, 2025), by Ivan Padabed and Roman Voronin]]></description><link>https://deepengineering.net/p/introduction-to-application-development</link><guid isPermaLink="false">https://deepengineering.net/p/introduction-to-application-development</guid><dc:creator><![CDATA[Roman Voronin]]></dc:creator><pubDate>Thu, 23 Oct 2025 07:00:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!whlm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa807bff8-4b44-4477-8828-fcec5f73cfe3_2250x2775" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this <strong><a href="https://www.packtpub.com/en-us/product/building-an-application-development-framework-9781836208570">book</a></strong> we will be delving into the different aspects of an <strong>Application Development Framework </strong>(<strong>ADF</strong>) lifecycle, allowing individual software engineers, development teams, and engineering organizations to benefit from ADF&#8217;s great potential. The initial chapter of the book is focused on providing a wide context for future chapters, setting a common ground for all ADF stakeholders, and introducing basic classifications and definitions for future use.</p><p>The concept of Application Development Framework (ADF) has been well-known for a long time, but we need to set up a crystal-clear context for further reading. This is important because it helps us deal with this complex topic by setting common ground for definitions and classifications that will be used throughout the book. First, we explore the evolution of the idea of ADF. After that, we discover the differences and connections between other SDLC-focused technologies, such as Platforms, Libraries, SDKs, and APIs, to craft a brief but concise definition that helps us keep a big picture while diving deep into implementation topics. Then, we review the place of ADF in the Software Development Lifecycle to identify and prove the advantages of adopting an ADF.</p><p>In this chapter we&#8217;re going to cover the following main topics:</p><ul><li><p>Introduction and historical references</p></li><li><p>Breaking down Application Development Framework</p></li><li><p>Exploring ADF and Platforms, Libraries, SDKs, APIs</p></li><li><p>Integrating into Software Development Lifecycle (SDLC) and Flow</p></li><li><p>Differentiating ADF and other types of Frameworks</p></li></ul><h1>Introduction and historical references</h1><p>Engineers have a long and productive history of creating building blocks for their own convenience. If we do not ignore this historical experience, we can learn many useful lessons for creating our own frameworks.</p><p>From the very beginning of the software industry, engineers and scientists have had a tendency to reuse their most successful and efficient ideas. There are quite a few historical practices that share the core objectives of a framework:</p><ul><li><p><strong>Architectural Blueprints</strong>: Since ancient times, complex structures like buildings or ships were built based on detailed plans. These plans defined the overall structure, components, and relationships - similar to how frameworks provide a blueprint for software architecture.</p></li><li><p><strong>Modular Design in Engineering</strong>: Even before the computer age, engineers approached complex machines with a modular mindset. Think of early steam engines with interchangeable parts - a principle that carries over to software components within a framework.</p></li><li><p><strong>Mathematical Frameworks</strong>: For centuries, mathematicians have relied on established frameworks like algebra or calculus to solve problems. These frameworks provide a set of rules and structures that guide the approach to solving a specific type of problem.</p></li></ul><p>While these aren&#8217;t direct equivalents to software frameworks, they all represent historical approaches to structuring complex systems in a way that aligns with the core function of a software development framework&#8212;to simplify the lives of its users when dealing with complex problems.</p><p>With the advent and adoption of computers, the concept of a framework has gone beyond the art of the elite and has become part of the daily work of many programmers. The idea evolved as computers themselves developed. Here are some contenders for the title of earliest software framework:</p><ul><li><p><strong>Early Subroutine Libraries (1940s &#8211; 1950s)</strong>: In the early days of computing, programmers might develop reusable code blocks for common tasks like mathematical functions or input/output routines. These weren&#8217;t full-fledged frameworks, but they offered a basic level of reusability and structure.</p></li><li><p><strong>FORTRAN Compilers (1950s)</strong>: FORTRAN introduced the concept of high-level languages, allowing programmers to write code that is more human-readable than machine code. While not exactly a framework, it provided a foundational structure for building software.</p></li><li><p><strong>Operating Systems (1960s onwards)</strong>: Operating systems like IBM&#8217;s OS/360 offered a platform for running applications. They provided core functionalities like memory management and device drivers, which later frameworks were built upon.</p></li></ul><p>It&#8217;s important to remember that the concept of a software development framework as we know it today &#8211; offering a comprehensive set of tools, libraries, and design patterns &#8211; is a more recent development. However, these earlier practices laid the groundwork for the frameworks we use today. In the modern world, we can only imagine the practical purpose software created on top of one or multiple frameworks.</p><blockquote><p><strong>Note</strong></p><p>This book uses both terms &#8220;software development framework (SDF)&#8221; and &#8220;application development framework (ADF)&#8221; interchangeably. Usually, &#8220;application&#8221; is not exactly the same as &#8220;software&#8221;: we have platforms, libraries, SDKs, engineering tools and frameworks as alternative kinds of software. But in the context of the topic (&#8220;building frameworks&#8221;) we can always safely assume that any &#8220;software&#8221; we are going to develop with our frameworks will serve the same purpose as &#8220;application&#8221; with a minor exception of &#8220;infrastructure management frameworks&#8221; which are mentioned explicitly.</p></blockquote><p>While most of the information on the internet about Application Development Frameworks is focused on web and mobile development, we cannot ignore trending frameworks from a non-application software, such as</p><ul><li><p>Artificial Intelligence and Machine Learning (like PyTorch, TensorFlow, and Apache MXNet),</p></li><li><p>Scheduled task management (like Celery, Temporal, and Apache Airflow),</p></li><li><p>Infrastructure management (like Terraform, Pulumi, and Crossplane),</p></li><li><p>Testing automation (like Selenium, Robot, and webdriverIO),</p></li></ul><p>and many others, including &#8220;hybrid&#8221; frameworks that provide multiple capabilities at once.</p><p>Fortunately, foundational principles of building software frameworks are common between classic ADF and these emerging types of SDF.</p><p>There are also vertical ADFs, aiming to cover corresponding business domains. Examples of such frameworks include Gamedev (Flame, Monogame), Data Visualization (Shiny, Seaborn, TensorBoard), Hardware Instrumentation (LabVIEW), etc.</p><p>A <a href="https://survey.stackoverflow.co/2023/#section-most-loved-dreaded-and-wanted-web-frameworks">StackOverflow research in 2023</a> that involved approximately 90,000 software engineers provided us with data about frameworks they use daily (See <em>Figure 1.1</em> that summarizes one of the framework-related topics from this survey). In addition to those numbers, we know that many frameworks have their own communities outside of StackOverflow, which means that more than 100k engineers work with software frameworks on a daily basis.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!J6iQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03053d91-c55a-40eb-96f6-a000cabd4e76_666x1079.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!J6iQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03053d91-c55a-40eb-96f6-a000cabd4e76_666x1079.png 424w, https://substackcdn.com/image/fetch/$s_!J6iQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03053d91-c55a-40eb-96f6-a000cabd4e76_666x1079.png 848w, https://substackcdn.com/image/fetch/$s_!J6iQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03053d91-c55a-40eb-96f6-a000cabd4e76_666x1079.png 1272w, https://substackcdn.com/image/fetch/$s_!J6iQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03053d91-c55a-40eb-96f6-a000cabd4e76_666x1079.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!J6iQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03053d91-c55a-40eb-96f6-a000cabd4e76_666x1079.png" width="666" height="1079" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/03053d91-c55a-40eb-96f6-a000cabd4e76_666x1079.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1079,&quot;width&quot;:666,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 1.4: StackOverflow research summary chart&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 1.4: StackOverflow research summary chart" title="Figure 1.4: StackOverflow research summary chart" srcset="https://substackcdn.com/image/fetch/$s_!J6iQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03053d91-c55a-40eb-96f6-a000cabd4e76_666x1079.png 424w, https://substackcdn.com/image/fetch/$s_!J6iQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03053d91-c55a-40eb-96f6-a000cabd4e76_666x1079.png 848w, https://substackcdn.com/image/fetch/$s_!J6iQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03053d91-c55a-40eb-96f6-a000cabd4e76_666x1079.png 1272w, https://substackcdn.com/image/fetch/$s_!J6iQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03053d91-c55a-40eb-96f6-a000cabd4e76_666x1079.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 1.1: StackOverflow research summary chart</em></p><p>With all the wide adoption of ADFs, it is confusing to see how many different inconsistent definitions and classifications we have all around the internet. In the following section, we will craft a brief and concise definition based on ADFs unique differentiators in a world of software engineering.</p><h1>Breaking down Application Development Framework</h1><p>I often see engineers mixing up libraries, frameworks, and Software Development Kits. They use these words like they mean the same thing. Even more confusion can come if we add APIs, platforms, and DSLs to the conversation. But to make our own framework, we need to understand all these things.</p><p>According to Dictionary.com, the formal definition of the term &#8220;framework&#8221; is <em>&#8220;a basic structure, plan, or system, as of concepts, values, customs, or rules&#8221;.</em></p><p>The collective unconscious of humanity, also known as LLM, suggests the following definition for ADF: <em>An application development framework is a software library offering a fundamental structure for building applications within a specific environment. It acts as a reusable foundation, supplying pre-defined functionalities and promoting code organization through established conventions. This approach streamlines development by reducing repetitive coding efforts</em>.</p><p>Both definitions are formally correct (except for &#8220;library&#8221; part of the second one, which I will explain later in this section). But they focus on how the framework is designed but have a lack of explanation about how it works. I am going to fill this gap by adding my own:</p><blockquote><p><strong>Definition</strong></p><p>A collection of pre-written code and tools that provide a structured approach to building applications. It simplifies development by enforcing architectural patterns: frameworks always dictate an execution flow, and stipulate specific way to structure your code, promoting maintainability, testability, low coupling, and reusability.</p></blockquote><p>It is a common misunderstanding to confuse frameworks with other engineering concepts aiming towards reusability, like API, software library, SDK, and platform. And there is always a special &#8220;tooling&#8221; category, which covers a wide range of software from smaller console scripts to powerful configurable logs processing pipelines &#8211; they all live their own life as they only used by developers to support their routine tasks, so we keep them out of conversation. Let&#8217;s set clear boundaries to understand their differences to focus on the most important aspects of our topic.</p><h2>Application Programming Interface</h2><p>Starting from <strong>API</strong> (<strong>Application Programming Interface</strong>) as the lowest-level implementation of the development tooling. The traditional understanding of API included any exposed interface available to software developers to perform manipulation with an external subsystem. This external subsystem was treated as a &#8220;black box,&#8221; which means that the developer should not worry about its internal implementation, tech stack, and logic. Thus, API provides a complete set of methods to deal with it. Modern understanding of the API concept drifted towards over-the-network API, like HTTP/gRPC/websocket APIs. Events and message-based communication interfaces are also subsets of APIs &#8211; like webhooks.</p><p>The best practice of API definition is to use open standards like OpenAPI and AsyncAPI schema languages, or other less popular languages like RAML.org or APIBlueprint.org. However, it is acceptable to use proprietary or vendor-specific tools. API concept can also be visualized with a simple diagram notation (see Figure 1.2 below). Typical representatives of the API are as follows:</p><ul><li><p>SaaS products&#8217; interfaces, e.g. <a href="https://developers.pandadoc.com/reference/about">PandaDoc API</a>, or <a href="https://platform.openai.com/docs/api-reference/introduction">OpenAI LLM API</a></p></li><li><p>Cloud management APIs; e.g. <a href="https://docs.aws.amazon.com/cloudcontrolapi/latest/APIReference/Welcome.html">AWS Cloud Control API</a> , or <a href="https://learn.microsoft.com/en-us/rest/api/azure/">Azure REST API</a>, or Google&#8217;s <a href="https://cloud.google.com/service-infrastructure/docs/service-management/reference/rest">Service Management API</a></p></li><li><p>Webhooks, like <a href="https://zapier.com/blog/what-are-webhooks/">Zapier</a></p></li><li><p>Internal/proprietary messaging-based events and commands schema registries that can be based on Confluent or <a href="https://docs.redpanda.com/current/manage/schema-reg/schema-reg-overview/">Redpanda</a> </p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!u3c7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb787246d-f808-436d-b465-c754904f58ec_366x197.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!u3c7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb787246d-f808-436d-b465-c754904f58ec_366x197.png 424w, https://substackcdn.com/image/fetch/$s_!u3c7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb787246d-f808-436d-b465-c754904f58ec_366x197.png 848w, https://substackcdn.com/image/fetch/$s_!u3c7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb787246d-f808-436d-b465-c754904f58ec_366x197.png 1272w, https://substackcdn.com/image/fetch/$s_!u3c7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb787246d-f808-436d-b465-c754904f58ec_366x197.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!u3c7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb787246d-f808-436d-b465-c754904f58ec_366x197.png" width="366" height="197" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b787246d-f808-436d-b465-c754904f58ec_366x197.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:197,&quot;width&quot;:366,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 1.5: Concept-level diagram of API&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 1.5: Concept-level diagram of API" title="Figure 1.5: Concept-level diagram of API" srcset="https://substackcdn.com/image/fetch/$s_!u3c7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb787246d-f808-436d-b465-c754904f58ec_366x197.png 424w, https://substackcdn.com/image/fetch/$s_!u3c7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb787246d-f808-436d-b465-c754904f58ec_366x197.png 848w, https://substackcdn.com/image/fetch/$s_!u3c7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb787246d-f808-436d-b465-c754904f58ec_366x197.png 1272w, https://substackcdn.com/image/fetch/$s_!u3c7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb787246d-f808-436d-b465-c754904f58ec_366x197.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><em>Figure 1.2: Concept-level diagram of API</em></p><p>The diagram helps to see that the API purpose is to provide access to exposed &#8220;black box&#8221; functions.</p><h2>Library</h2><p>The next one to review is a <strong>software library</strong> as a <em>collection of pre-written code or routines</em> that developers can use to perform specific tasks or functions within their software applications.</p><p>Sometimes developers see any software library as a framework, but the purpose of the library is completely different &#8211; it focuses on runtime/operation concerns by implementing a common part of the system, like hardware I/O operations, network protocol, authorization sequence, ranking algorithm, IoT standard, etc. It is also common to have a library to transform a low-level API into a more developer-friendly form by adding enumerables, constants, and conditional logic over a binary code and method signatures of plain API. A Library usually operates as a <strong>gray box,</strong> which means that software developers can see its internal implementation, but it is rarely necessary. In some cases, libraries can come in binary format, which makes them <strong>black boxes</strong>.</p><p>Terminology across the industry is not always consistent, we can find other synonyms for the term &#8220;software library&#8221;:</p><ul><li><p><strong>Package</strong>; usually means one or multiple software libraries that share the same license and they can be distributed as a single unit.</p></li><li><p><strong>Module</strong>; usually means a built-in software library, distributed with the program.</p></li><li><p><strong>Extension </strong>(aka add-on or plug-in); usually means a software library that follows specific program interface allowing external developers to modify original program behavior without changing any code in the original system.</p></li></ul><p>Another important consideration is <strong>control flow</strong>. For a software library, it is common for developers to have full control over the library functions &#8211; so developers are responsible for invoking the library.</p><p>Writing software libraries is one of the most common tasks in the industry; many senior developers have experience of creating libs for internal company purposes, or contributing to open-source libs, or at least have them as a part of their pet projects.</p><p>Often, a software library evolves into an SDK or a framework after multiple iterations of improvements. And we definitely need to build libraries as part of the ADF development.</p><p>To understand the idea of software library better, we can use real examples:</p><ul><li><p>Algorithm libraries like <a href="https://en.wikipedia.org/wiki/List_of_numerical_libraries">math</a>, or <a href="https://en.wikipedia.org/wiki/List_of_3D_graphics_libraries">3D</a>, or <a href="https://en.wikipedia.org/wiki/Category:Python_(programming_language)_scientific_libraries">ML</a></p></li><li><p>Hardware abstraction libraries like <a href="https://infineon.github.io/psoc6hal/html/index.html">HAL</a> or <a href="https://www.geeksforgeeks.org/operating-systems/device-driver-and-its-purpose/">device drivers</a> </p></li><li><p>Standard-compliant implementations like <a href="https://openauth.js.org/">OpenAuth</a></p></li><li><p>Programming helpers like <a href="https://www.boost.org/">Boost</a> or <a href="https://github.com/psf/requests">Requests.py</a> that provide developers with a pre-written code for HTTP requests lifecycle syntaxis helper.</p></li><li><p>Any proprietary pluggable reusable code</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1p3e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfa81e6b-268c-475d-9b8e-b3f1f1e42523_497x177.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1p3e!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfa81e6b-268c-475d-9b8e-b3f1f1e42523_497x177.png 424w, https://substackcdn.com/image/fetch/$s_!1p3e!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfa81e6b-268c-475d-9b8e-b3f1f1e42523_497x177.png 848w, https://substackcdn.com/image/fetch/$s_!1p3e!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfa81e6b-268c-475d-9b8e-b3f1f1e42523_497x177.png 1272w, https://substackcdn.com/image/fetch/$s_!1p3e!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfa81e6b-268c-475d-9b8e-b3f1f1e42523_497x177.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1p3e!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfa81e6b-268c-475d-9b8e-b3f1f1e42523_497x177.png" width="497" height="177" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bfa81e6b-268c-475d-9b8e-b3f1f1e42523_497x177.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:177,&quot;width&quot;:497,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 1.6: Concept-level diagram of API and Library&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 1.6: Concept-level diagram of API and Library" title="Figure 1.6: Concept-level diagram of API and Library" srcset="https://substackcdn.com/image/fetch/$s_!1p3e!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfa81e6b-268c-475d-9b8e-b3f1f1e42523_497x177.png 424w, https://substackcdn.com/image/fetch/$s_!1p3e!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfa81e6b-268c-475d-9b8e-b3f1f1e42523_497x177.png 848w, https://substackcdn.com/image/fetch/$s_!1p3e!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfa81e6b-268c-475d-9b8e-b3f1f1e42523_497x177.png 1272w, https://substackcdn.com/image/fetch/$s_!1p3e!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfa81e6b-268c-475d-9b8e-b3f1f1e42523_497x177.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><em>Figure 1.3: Concept-level diagram of API and Library</em></p><p>The diagram suggests that Library can serve as a pre-implemented tool to integrate an API to the application; but basically, it can provide any pre-implemented code for reuse.</p><h2>Software Development Kit</h2><p>Similar functions can be also performed by <strong>Software Development Kits</strong> (<strong>SDKs</strong>) but they usually include much more than just a software Libraries; there is a list of possible SDK internals:</p><ul><li><p>Libraries.</p></li><li><p>Tracing and Debugging tools.</p></li><li><p>Documentation.</p></li><li><p>Integrated development environments (IDEs)</p></li><li><p>Tests.</p></li><li><p>Plug-ins.</p></li><li><p>Application programming interfaces (APIs)</p></li><li><p>Sample code.</p></li></ul><p>SDKs span both design-time (organizational) and runtime/operations (product) concerns but still with focus on a runtime. SDKs are also platform&#8211; or vendor-specific, they are developed by API or Platform vendors to improve their products adoption &#8211; see Android SDK created by Google (Alphabet) and Windows ASDK developed by Microsoft.</p><p>Sometimes bigger SDKs can be designed to include frameworks (like Apple SDK), but we can also see the opposite case, where an SDK is designed as an element of the framework. The following are examples of cases where SDKs are subsystems of ADFs in the list below:</p><ul><li><p><a href="https://sdk.operatorframework.io/">Operator SDK</a> is part of the Operator Framework</p></li><li><p>SDKs as developer-friendly lib wrappers for a particular framework, (for example) <a href="https://github.com/Treblle/treblle-python">Treblle</a> provides multiple SDKs including one for the Django framework</p></li><li><p>SDKs have the same control flow as libraries have: the developer is responsible for invoking an SDK, while in the case of frameworks, we usually have the opposite control flow: the framework is responsible for invoking developer&#8217;s code. There is no exception if the framework is part of an SDK: the framework takes ownership over the control flow.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WFSx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc13f6a-9311-4c69-95a2-2661bf8be3c5_589x201.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WFSx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc13f6a-9311-4c69-95a2-2661bf8be3c5_589x201.png 424w, https://substackcdn.com/image/fetch/$s_!WFSx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc13f6a-9311-4c69-95a2-2661bf8be3c5_589x201.png 848w, https://substackcdn.com/image/fetch/$s_!WFSx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc13f6a-9311-4c69-95a2-2661bf8be3c5_589x201.png 1272w, https://substackcdn.com/image/fetch/$s_!WFSx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc13f6a-9311-4c69-95a2-2661bf8be3c5_589x201.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WFSx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc13f6a-9311-4c69-95a2-2661bf8be3c5_589x201.png" width="589" height="201" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ebc13f6a-9311-4c69-95a2-2661bf8be3c5_589x201.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:201,&quot;width&quot;:589,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 1.7: Concept diagram of API, Library, and SDK&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 1.7: Concept diagram of API, Library, and SDK" title="Figure 1.7: Concept diagram of API, Library, and SDK" srcset="https://substackcdn.com/image/fetch/$s_!WFSx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc13f6a-9311-4c69-95a2-2661bf8be3c5_589x201.png 424w, https://substackcdn.com/image/fetch/$s_!WFSx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc13f6a-9311-4c69-95a2-2661bf8be3c5_589x201.png 848w, https://substackcdn.com/image/fetch/$s_!WFSx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc13f6a-9311-4c69-95a2-2661bf8be3c5_589x201.png 1272w, https://substackcdn.com/image/fetch/$s_!WFSx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc13f6a-9311-4c69-95a2-2661bf8be3c5_589x201.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><em>Figure 1.4: Concept diagram of API, Library, and SDK</em></p><p>The diagram above depicts the SDK as a super-entity for a library.</p><h2>Framework</h2><p>Let&#8217;s elaborate on our definition here:</p><blockquote><p><em>A framework is a collection of pre-written code and tools that provides a structured approach to building applications. It simplifies development by enforcing architectural patterns: frameworks always dictate an execution flow, and stipulate specific way to structure your code, promoting maintainability, testability, low coupling, and reusability.</em></p></blockquote><p>Of course, frameworks provide more than that, there are some examples below:</p><ul><li><p>Hide low-level complexity behind a higher-level abstraction;</p></li><li><p>Promote faster development by providing pre-built binary/packaged components and functionalities;</p></li></ul><p>But those additional benefits cannot be attributed exclusively to frameworks &#8211; libraries or SDKs both have the same value propositions.</p><p>The following are the Key aspects of this definition based on usage scenarios and key attributes:</p><ul><li><p><strong>&#8220;Framework as abstraction&#8221;</strong> conceals repetitive code and low-level details by applying software libraries, acting as a higher-level interface. This allows developers to work with core functionalities without getting bogged down in implementation specifics. However, it usually gives an option of direct communication with levels under even if it is unnecessarily for overwhelming majority of scenarios; <em>in brief, any framework has one or multiple libraries coming as a built-in option or pluggable 3rd-parties.</em></p></li><li><p><strong>&#8220;Frameworks as tooling&#8221;</strong> prioritize simplifying the development process by providing pre-built components, streamlined workflows, and reduced boilerplate code. Their primary focus is on accelerating development, while runtime considerations (like operations, maintenance, portability, performance) are a secondary benefit.</p></li><li><p><strong>&#8220;Framework as architectural constraint&#8221;</strong> establishes a blueprint for system architecture. It dictates core components, their interactions, and overall structure, influencing key design decisions for developers working within the framework&#8217;s constraints.</p></li></ul><p>And finally, the relations between a frameworks and APIs, libraries and SDKs are usually follow the common pattern: ADF streamlines the software development flow for the organization, governs the control flow by invoking a custom code made by software developer, having libraries as a proxy to access external subsystem APIs, and allowing to plug in a third-party libraries or SDKs to handle specific integrations.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1x5h!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a8baefe-9549-4782-b757-4ec9fbe7c61a_716x424.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1x5h!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a8baefe-9549-4782-b757-4ec9fbe7c61a_716x424.png 424w, https://substackcdn.com/image/fetch/$s_!1x5h!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a8baefe-9549-4782-b757-4ec9fbe7c61a_716x424.png 848w, https://substackcdn.com/image/fetch/$s_!1x5h!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a8baefe-9549-4782-b757-4ec9fbe7c61a_716x424.png 1272w, https://substackcdn.com/image/fetch/$s_!1x5h!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a8baefe-9549-4782-b757-4ec9fbe7c61a_716x424.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1x5h!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a8baefe-9549-4782-b757-4ec9fbe7c61a_716x424.png" width="716" height="424" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1a8baefe-9549-4782-b757-4ec9fbe7c61a_716x424.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:424,&quot;width&quot;:716,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 1.8: Concept diagram of ADF, SDK, Library, and API&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 1.8: Concept diagram of ADF, SDK, Library, and API" title="Figure 1.8: Concept diagram of ADF, SDK, Library, and API" srcset="https://substackcdn.com/image/fetch/$s_!1x5h!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a8baefe-9549-4782-b757-4ec9fbe7c61a_716x424.png 424w, https://substackcdn.com/image/fetch/$s_!1x5h!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a8baefe-9549-4782-b757-4ec9fbe7c61a_716x424.png 848w, https://substackcdn.com/image/fetch/$s_!1x5h!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a8baefe-9549-4782-b757-4ec9fbe7c61a_716x424.png 1272w, https://substackcdn.com/image/fetch/$s_!1x5h!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a8baefe-9549-4782-b757-4ec9fbe7c61a_716x424.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 1.5: Concept diagram of ADF, SDK, Library, and API</em></p><p>There is an extended classification of the Application Development Frameworks:</p><ul><li><p>Web frameworks like Django, Node.js, Java Play, Ruby-on-Rails, etc</p></li><li><p>Enterprise frameworks like Java Spring, Oracle ADF</p></li><li><p><a href="https://github.com/topics/low-code-framework">Low-code frameworks</a> like Flutter, OpenBlocks, Appsmith</p></li><li><p>AI/ML frameworks like TensorFlow, Keras, Apache MXNet</p></li><li><p>Gamedev frameworks like Unity, UnrealEngine</p></li><li><p>Mobile frameworks like React Native, Xamarin, Apache Cordova</p></li><li><p>Microservice frameworks like GoMicro, Spring Boot, Molecular</p></li><li><p>Test automation frameworks like Selenium, Appium, WebdriverIO</p></li><li><p>Desktop OS frameworks like MFC, OS X framework, KDE framework</p></li><li><p>Utility frameworks like Python Celery, ActiveTask,</p></li><li><p><em>Custom frameworks &#8211; proprietary ones build for internal use, usually applying ADF format for a domain objects and rules</em></p></li><li><p>As Frameworks are our focus area, we add more detailed specification for three ADFs to better highlight their commonalities.</p></li></ul><p>The first one to analyze is Django:</p><ul><li><p>It employs <a href="https://djangopackages.org/">numerous libraries</a> in pluggable way</p></li></ul><ul><li><p>It focuses on design-time aspects: &#8220;<a href="https://www.djangoproject.com/">encourages rapid development and clean, pragmatic design</a>&#8221; </p></li><li><p>It is responsible for a control flow &#8211; developers don&#8217;t need to invoke Django code but to follow a Django project structure to get their code invoked in a right moment;</p></li><li><p>It enforces multiple architecture patterns (MVC / MVT as a model-view-template, ORM as object-relational mapping, extendable middleware-based request processing pipeline, class-based views, etc)</p></li><li><p>It is vendor- and platform-neutral so it can be used on any cloud platform or a virtual machine that can interpret Python programming language;</p></li><li><p>It is a &#8220;gray box&#8221; software that can be <a href="https://code.djangoproject.com/wiki/Distributions">redistributed</a> as a package but it also has its <a href="https://github.com/django/django">source code</a> published in a public GitHub repository under the BSD-3 OSS license</p></li></ul><p>The second one is <a href="https://nodejs.org">Node.js</a>, the most popular web full-stack framework based on the JavaScript programming language:</p><ul><li><p>It has number of <a href="https://nodejs.org/docs/latest-v12.x/api/">standard built-in libraries</a> listed in official documentation and hundreds of pluggable external libraries like listed <a href="https://github.com/sindresorhus/awesome-nodejs">here</a></p></li><li><p>Org design-time focus is clearly emphasized as a key success factor of this framework; &#8230;</p><blockquote><p>(As <a href="https://the-stack-overflow-podcast.simplecast.com/episodes/why-the-creator-of-nodejs-created-a-new-javascript-runtime/transcript">Ryan Dahl said</a>): &#8220;<em>So for kind of technical reasons, adding a server onto JavaScript worked really well and people who were programming front end websites were able to take those same skills and with just a small amount of additional knowledge were able to program pretty nice web servers that could do long polling or other kinds of real time interactions. And I think there&#8217;s just a large base of JavaScript users out there, naturally, it being the language of the web, and so there was a lot of people who were able to take their skills and add on Node to that and suddenly become full stack developers</em>.&#8221; </p></blockquote></li><li><p>It is responsible for the control flow</p></li><li><p>It enforces architecture patterns like event-driven, microservices, API-first etc.</p></li><li><p>It is vendor- and platform-neutral</p></li><li><p>It is a &#8220;gray box&#8221; open-source software</p></li></ul><p>And the final one is <a href="https://react.dev/">React</a>, a modern web front-end framework based on the JavaScript language:</p><ul><li><p>Dozens of libraries like <a href="https://www.reactlibraries.com/search?qType=libraries&amp;q=*">here</a></p></li><li><p>Design-time focus: most of public sources mention developer-oriented benefits as a key advantage of the framework; this list includes declarative syntaxis, reusable components, community support, detailed documentation, etc.</p></li><li><p>Control flow management based on a virtual DOM concept is the core of the framework</p></li><li><p>React&#8217;s intrinsic architecture patterns include event-driven (hooks), container-based decorators, data repository (provider), etc.</p></li><li><p>It is vendor- and platform-neutral</p></li><li><p>It is a &#8220;gray box&#8221; open-source software</p></li></ul><p>As we can see, most ADFs follow the same model which we will explore in more detail in <em>Chapter 3</em>.</p><h2>Platform</h2><p>And the final concept to review here is a <strong>Platform</strong>. The main differentiator of a Platform, in comparison with all the others: APIs, libraries, SDKs and frameworks, is its hosted server-side execution runtime. But it is important to understand that the runtime concern is a &#8220;bonus value&#8221; here because the fundamental benefits of Platforms are still organizational design-time toolings. Platforms usually combine that hosted execution backend with APIs, libraries, and SDKs; and it is common for modern Platforms to provide developers with more advanced tooling like dev portals, resource management console and UI, cloud IDE, infrastructure-as-a-code definitions support and many other org productivity boosters. However, platforms rarely include application development frameworks and vice versa. We still can see frameworks being part of the platform (like AWS Well-Architected Framework is part of AWS platform value proposition, and <a href="https://learn.microsoft.com/en-us/azure/well-architected/">Microsoft </a>also has the same one), but these are not ADFs but architecture design frameworks (set of values, viewpoints, blueprints and patterns for cloud-native applications).</p><p>Typical taxonomy with examples of the Platforms is the following:</p><ul><li><p>Cloud platforms like AWS, MS Azure or GCP</p></li><li><p>Messaging platforms like Confluent Kafka or Redpanda</p></li><li><p>Task execution platforms like Temporal.io</p></li><li><p>Robotic process automation (RPA) platforms like WorkFusion or UIPath</p></li><li><p>Game Platforms like <a href="https://heroiclabs.com/heroic-cloud/">Heroic Cloud</a></p></li><li><p>Dev platforms like Split.io, Firebase, Launchdarkly</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-9Z8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d718fe8-d32a-4d13-825c-22911b16a496_716x424.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-9Z8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d718fe8-d32a-4d13-825c-22911b16a496_716x424.png 424w, https://substackcdn.com/image/fetch/$s_!-9Z8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d718fe8-d32a-4d13-825c-22911b16a496_716x424.png 848w, https://substackcdn.com/image/fetch/$s_!-9Z8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d718fe8-d32a-4d13-825c-22911b16a496_716x424.png 1272w, https://substackcdn.com/image/fetch/$s_!-9Z8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d718fe8-d32a-4d13-825c-22911b16a496_716x424.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-9Z8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d718fe8-d32a-4d13-825c-22911b16a496_716x424.png" width="716" height="424" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4d718fe8-d32a-4d13-825c-22911b16a496_716x424.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:424,&quot;width&quot;:716,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 1.9: Concept diagram of Platform, Framework, SDK, Library, and API&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 1.9: Concept diagram of Platform, Framework, SDK, Library, and API" title="Figure 1.9: Concept diagram of Platform, Framework, SDK, Library, and API" srcset="https://substackcdn.com/image/fetch/$s_!-9Z8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d718fe8-d32a-4d13-825c-22911b16a496_716x424.png 424w, https://substackcdn.com/image/fetch/$s_!-9Z8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d718fe8-d32a-4d13-825c-22911b16a496_716x424.png 848w, https://substackcdn.com/image/fetch/$s_!-9Z8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d718fe8-d32a-4d13-825c-22911b16a496_716x424.png 1272w, https://substackcdn.com/image/fetch/$s_!-9Z8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d718fe8-d32a-4d13-825c-22911b16a496_716x424.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 1.6: Concept diagram of Platform, Framework, SDK, Library, and API</em></p><ul><li><p>Platforms are the most complete and mature kind of reusable engineering elements; however, their value comes with a high cost of ownership &#8211; this means that only large-scale companies can afford to build and operate an internal software development platform. Please do not confuse internal SDP with commercial PaaS (platform as a service) &#8211; we have many examples that successful PaaS can be created and operated with relatively small investment.</p></li></ul><h2>Domain Specific Language</h2><p>Here it is, the bonus addition to the chapter. A <strong>Domain Specific Language</strong> (<strong>DSL</strong>) is a &#8220;language&#8221; with a higher level of abstraction optimized for a specific class of problems. A DSL uses the concepts and rules from the field or domain.</p><blockquote><p><strong>Note</strong></p><p>Please note that DSL is not a programming language but rather a &#8220;formal domain description&#8221; language. In most cases, DSLs implementations are closer to executable configuration in JSON, YAML, XML or similar formats.</p></blockquote><p>In terms of architecture abstractions, extensibility, and control flow, DSL is very similar to ADF &#8211; they both imply certain design patterns, employ libraries for extensibility and portability purposes, and invoke necessary code in the right time, defined by DSL creators.</p><p>The difference is the level of freedom for software product developers to code the DSL execution. We may consider DSL a &#8220;next level&#8221; framework suitable for cases when we want to achieve a high grade of standardization at a high level of abstraction.</p><p>Typical simplified taxonomy of the DSLs is the following:</p><ul><li><p>Workflow/BPMN like Camunda, Oracle BPMS, Nikku</p></li><li><p>Rules like Drools</p></li><li><p>Infra like Terraform</p></li><li><p>Development like Gradle</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FOOQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbae80a8d-5170-4b6b-9db3-2cb7dfcccb0f_716x525.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FOOQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbae80a8d-5170-4b6b-9db3-2cb7dfcccb0f_716x525.png 424w, https://substackcdn.com/image/fetch/$s_!FOOQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbae80a8d-5170-4b6b-9db3-2cb7dfcccb0f_716x525.png 848w, https://substackcdn.com/image/fetch/$s_!FOOQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbae80a8d-5170-4b6b-9db3-2cb7dfcccb0f_716x525.png 1272w, https://substackcdn.com/image/fetch/$s_!FOOQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbae80a8d-5170-4b6b-9db3-2cb7dfcccb0f_716x525.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FOOQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbae80a8d-5170-4b6b-9db3-2cb7dfcccb0f_716x525.png" width="716" height="525" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bae80a8d-5170-4b6b-9db3-2cb7dfcccb0f_716x525.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:525,&quot;width&quot;:716,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 1.10: Concept diagram with DSL, Platform, Framework, SDK, Library, and API&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 1.10: Concept diagram with DSL, Platform, Framework, SDK, Library, and API" title="Figure 1.10: Concept diagram with DSL, Platform, Framework, SDK, Library, and API" srcset="https://substackcdn.com/image/fetch/$s_!FOOQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbae80a8d-5170-4b6b-9db3-2cb7dfcccb0f_716x525.png 424w, https://substackcdn.com/image/fetch/$s_!FOOQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbae80a8d-5170-4b6b-9db3-2cb7dfcccb0f_716x525.png 848w, https://substackcdn.com/image/fetch/$s_!FOOQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbae80a8d-5170-4b6b-9db3-2cb7dfcccb0f_716x525.png 1272w, https://substackcdn.com/image/fetch/$s_!FOOQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbae80a8d-5170-4b6b-9db3-2cb7dfcccb0f_716x525.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Figure 1.7: Concept diagram with DSL, Platform, Framework, SDK, Library, and API</p><p>The diagram demonstrates a DSL primary use case of extending a Framework with limited predefined capabilities.</p><p>In this section we focused on key differentiators of Frameworks in comparison with other kinds of &#8220;engineering building blocks&#8221;. Now we can concentrate on the most important aspects of Frameworks, including the ones we further discuss in the next chapters.</p><h1>Differentiating ADF and other Types of Frameworks</h1><p>There are even more sources of confusion: we have Frameworks that could be used in the process of developing software products, but these Frameworks are not ADFs!</p><p>In the realm of software development, <strong>framework</strong> is a broad term encompassing various tools that structure and streamline different aspects of the process. We&#8217;ve established that Application Development Frameworks (ADFs) directly assist with coding. Let&#8217;s delve deeper into two other crucial categories: <strong>Architecture Frameworks </strong>and <strong>Software Delivery Frameworks</strong>.</p><p>&#8230;</p><h2>Architecture Frameworks</h2><p>These frameworks provide a structured approach to designing the overall architecture of a software system. Think of them as <em>organizational architecture design process</em> blueprints or roadmaps that define the foundation upon which your application will be built.</p><p>Here&#8217;s a breakdown of Architecture Frameworks&#8217; types:</p><blockquote><p><strong>Enterprise Architecture Frameworks</strong> (<strong>EAFs</strong>): Focuses on the high-level structure of an entire organization&#8217;s IT infrastructure, including software applications, data, and hardware. Examples: TOGAF, Zachman Framework.</p></blockquote><blockquote><p><strong>Software Architecture Frameworks</strong> (<strong>SAFs</strong>): Specializes in designing the internal structure of a single software application. Examples: 4+1 View Model, C4 Model.</p></blockquote><p>The following are some of the benefits of adopting such frameworks:</p><blockquote><ul><li><p><strong>Consistency</strong>: Promotes a standardized approach to design, ensuring all components fit together seamlessly.</p></li><li><p><strong>Communication</strong>: Provide a common language for stakeholders (architects, developers, etc.) to discuss system design.</p></li><li><p><strong>Reduced Complexity</strong>: Break down complex systems into manageable components, simplifying design and development.</p></li></ul></blockquote><p>The following are some of the popular examples of the architecture frameworks:</p><ul><li><p><strong><a href="http://TOGAF">TOGAF</a></strong> (<strong>The Open Group Architecture Framework</strong>): A widely used EAF known for its comprehensive approach to enterprise architecture.</p></li><li><p><strong><a href="https://zachman-feac.com/zachman/about-the-zachman-framework">Zachman Framework:</a></strong> Another EAF, offering a framework for classifying architectural information across different viewpoints (e.g., business, data, application).</p></li><li><p><strong><a href="https://arxiv.org/pdf/2006.04975">4+1 View Model</a></strong>: A SAF focusing on five architectural viewpoints (system, application, deployment, container, and code) for designing software applications.</p></li></ul><p>(Another example is <a href="https://dodcio.defense.gov/Library/DoD-Architecture-Framework/">DoDAF</a>)</p><p>Architecture Frameworks can be very useful, but they are completely out of scope of this book.</p><h2>Software Delivery Frameworks</h2><p>These frameworks focus on streamlining the entire software development and delivery process, particularly for large-scale or complex projects. They don&#8217;t deal with the specifics of coding or designing the application itself, but rather how to efficiently manage the development lifecycle.</p><p>Here&#8217;s a closer look at <strong>Software Delivery Frameworks</strong>:</p><p><strong>Core Principles</strong>: Emphasize iterative development, continuous integration and continuous delivery (CI/CD), and agile methodologies.</p><p>The following are some of the benefits:</p><ul><li><p><strong>Improved Efficiency</strong>: Streamlines workflows and processes to deliver software faster and with fewer errors.</p></li><li><p><strong>Enhanced Communication</strong>: Fosters collaboration between development teams, product managers, and stakeholders.</p></li><li><p>Increased Adaptability: Enables teams to respond to changing requirements and market needs more effectively.</p></li></ul><p>The following are some of the popular examples:</p><ul><li><p><strong><a href="https://framework.scaledagile.com/">Scaled Agile Framework (SAFe)</a></strong>: A popular framework for scaling agile methodologies to large enterprises.</p></li><li><p><strong><a href="http://Large Scale Scrum (LeSS)">Large Scale Scrum</a></strong><a href="http://Large Scale Scrum (LeSS)"> (</a><strong><a href="http://Large Scale Scrum (LeSS)">LeSS</a></strong><a href="http://Large Scale Scrum (LeSS)">)</a>: An adaptation of the Scrum framework designed for large teams working on complex projects.</p></li><li><p><strong><a href="https://www.pmi.org/disciplined-agile/process/introduction-to-dad">Disciplined Agile Delivery (DAD)</a></strong>: A framework that integrates various agile practices with other project management methodologies.</p></li></ul><p>These categories provide a glimpse into the diverse landscape of frameworks beyond ADFs. Architecture Frameworks ensure a well-designed foundation for your software system, while Software Delivery Frameworks guide the overall development journey with efficiency and agility. By understanding and leveraging these frameworks, you can build robust and successful software applications.</p><p>But as we decided to focus on ADF, we need to pay special attention to a Software Development Lifecycle topic as it covers the key value of using application development frameworks.</p><h1>Software Development Lifecycle (SDLC) and Flow</h1><p>To better understand Application Development Frameworks, we need first to understand key scenarios of ADF adoption.</p><p>To set up a context, we need to differentiate the design-time and operations (runtime) aspects of the software product development. It is important to note that the <strong>Software Development Lifecycle</strong> (<strong>SDLC</strong>) can be used as a &#8220;blueprint&#8221; for software product development iteration. Visual diagram of SDLC can help us better understand this aspect:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!akyi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F354acbdf-1a65-44b6-937f-c69d506447c9_665x719.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!akyi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F354acbdf-1a65-44b6-937f-c69d506447c9_665x719.png 424w, https://substackcdn.com/image/fetch/$s_!akyi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F354acbdf-1a65-44b6-937f-c69d506447c9_665x719.png 848w, https://substackcdn.com/image/fetch/$s_!akyi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F354acbdf-1a65-44b6-937f-c69d506447c9_665x719.png 1272w, https://substackcdn.com/image/fetch/$s_!akyi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F354acbdf-1a65-44b6-937f-c69d506447c9_665x719.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!akyi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F354acbdf-1a65-44b6-937f-c69d506447c9_665x719.png" width="665" height="719" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/354acbdf-1a65-44b6-937f-c69d506447c9_665x719.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:719,&quot;width&quot;:665,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 1.11: Spiral SDLC model is one of the most advanced SDLC models for software development: image credits to https://www.tutorialspoint.com/sdlc/sdlc_spiral_model.htm&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 1.11: Spiral SDLC model is one of the most advanced SDLC models for software development: image credits to https://www.tutorialspoint.com/sdlc/sdlc_spiral_model.htm" title="Figure 1.11: Spiral SDLC model is one of the most advanced SDLC models for software development: image credits to https://www.tutorialspoint.com/sdlc/sdlc_spiral_model.htm" srcset="https://substackcdn.com/image/fetch/$s_!akyi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F354acbdf-1a65-44b6-937f-c69d506447c9_665x719.png 424w, https://substackcdn.com/image/fetch/$s_!akyi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F354acbdf-1a65-44b6-937f-c69d506447c9_665x719.png 848w, https://substackcdn.com/image/fetch/$s_!akyi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F354acbdf-1a65-44b6-937f-c69d506447c9_665x719.png 1272w, https://substackcdn.com/image/fetch/$s_!akyi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F354acbdf-1a65-44b6-937f-c69d506447c9_665x719.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 1.8: Spiral SDLC model is one of the most advanced SDLC models for software development: image credits to <a href="https://www.tutorialspoint.com/sdlc/sdlc_spiral_model.htm">tutorialspoint</a></em></p><p>We should always assume a repeatable iterative nature of the software product development process not because &#8220;agile&#8221; is our current state-of-the-art delivery methodology; every lean or efficient delivery approach relies on feedback loops to enable continuous improvement.</p><p>While ADF can be defined from both design-time and operations (runtime) perspectives, its primary value is always in design-time and SDLC. We can impact routines as repeatable and reproducible tasks. That&#8217;s how it is different from software libraries or APIs that can be employed for a one-time task that might never be repeated again (e.g. library that provides integration to specialized hardware or vendor-locked API that can be used in multiple places in source code, but the use of a library is not about repeatable part of SDLC task).</p><p>In systems engineering terminology we can declare that SDLC is a &#8220;using system&#8221; (or a supersystem) for ADF, and ADF is a &#8220;subsystem&#8221; of SDLC. This implies not only the fact that ADF is literally part of SDLC, but also enables the following mental model:</p><ul><li><p>ADF value, success criteria, and metrics are targeting SDLC improvements</p></li><li><p>ADF stakeholders&#8217; roles are SDLC participants</p></li><li><p>Key ADF architecture viewpoints are defined in SDLC</p></li></ul><p>In addition to SDLC concept, which is focused on engineering and instrumenting the development process, we can apply a &#8220;Flow&#8221; term to emphasize the systemic aspect of software product development in terms of value throughput and delivery management.</p><blockquote><p><strong>ADF and Flow</strong></p><p>&#8230;ADF (is) s one of the most influencing ways to optimize a Flow throughput and eliminate bottlenecks related to product and process complexity.</p></blockquote><h1>Summary</h1><p>An Application Development Framework is a software product &#8220;skeleton&#8221; that offers a fundamental structure for building applications within a specific environment. It acts as a reusable foundation, supplying pre-defined functionalities and promoting code organization through established conventions. This distinguishes it from other ways of reusing code in the form of an SDK, library, or API. It also makes ADF a unique opportunity to streamline development by reducing repetitive coding efforts, decreasing cognitive load, and promoting architecture best practices.</p><p>With this knowledge, we can explore the next chapters to find a way to calculate ADF return on investment, meet stakeholders&#8217; expectations, and align the ADF roadmap with a common maturity model.</p><h1>Further reading</h1><p>To know more please visit the (following) links:</p><ul><li><p><em><a href="https://developer.android.com/studio">Vendor-specific hardware SDKs like Android Studio</a></em></p></li><li><p><em><a href="https://developer.samsung.com/smarttv/develop/getting-started/setting-up-sdk/installing-tv-sdk.html">Samsung TV SDK</a></em></p></li><li><p><em><a href="https://learn.microsoft.com/en-us/gaming/gdk/docs/gdk-dev/console-dev/dev-kits/devkit-contents">MS Xbox</a></em></p></li><li><p><em><a href="https://developers.pandadoc.com/reference/sdk">Product-specific SDKs like PandaDoc SDK</a></em></p></li><li><p><em>Hubspot SDK:</em> <a href="https://developers.hubspot.com/docs/platform/ui-extensions-sdk">https://developers.hubspot.com/docs/platform/ui-extensions-sdk</a></p></li><li><p><em><a href="https://getstream.io/chat/sdk/ios/">GetStream</a></em></p></li><li><p><em>Platform SDKs like AWS</em>: <a href="https://aws.amazon.com/chime/chime-sdk/">Amazon Chime SDK</a> &amp; <a href="https://aws.amazon.com/sdk-for-net/">AWS SDK for .NET</a></p></li><li><p><em><a href="https://github.com/Azure/azure-sdk">Azure</a> or Google&#8217;s <a href="https://cloud.google.com/sdk">Cloud SDK</a></em></p></li></ul><div><hr></div><p>If you found this chapter insightful, check out <em><strong><a href="https://www.packtpub.com/en-us/product/building-an-application-development-framework-9781836208570">Building an Application Development Framework</a></strong></em> by <strong>Ivan Padabed</strong> and <strong>Roman Voronin</strong> (Packt, Sept 2025). It&#8217;s a practical, end-to-end playbook for designing, building, and rolling out a custom ADF that measurably improves flow and quality across your SDLC. The first edition includes real-world cases and a downloadable code bundle.</p><p>You&#8217;ll learn to: design and implement a robust ADF architecture; choose technologies for performance and scalability; manage versioning and packaging for efficient distribution; harden the framework&#8217;s security posture; foster collaboration to build an extensible ADF ecosystem; and measure and optimize performance (including DORA-style outcomes) for continuous improvement. Ideal for technical leaders, engineering managers, and developers who want a pragmatic, code-backed path to a reusable framework that scales with their organization.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-us/product/building-an-application-development-framework-9781836208570" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!whlm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa807bff8-4b44-4477-8828-fcec5f73cfe3_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!whlm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa807bff8-4b44-4477-8828-fcec5f73cfe3_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!whlm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa807bff8-4b44-4477-8828-fcec5f73cfe3_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!whlm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa807bff8-4b44-4477-8828-fcec5f73cfe3_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!whlm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa807bff8-4b44-4477-8828-fcec5f73cfe3_2250x2775" width="364" height="449" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a807bff8-4b44-4477-8828-fcec5f73cfe3_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:364,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Building an Application Development Framework&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-us/product/building-an-application-development-framework-9781836208570&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Building an Application Development Framework" title="Building an Application Development Framework" srcset="https://substackcdn.com/image/fetch/$s_!whlm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa807bff8-4b44-4477-8828-fcec5f73cfe3_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!whlm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa807bff8-4b44-4477-8828-fcec5f73cfe3_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!whlm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa807bff8-4b44-4477-8828-fcec5f73cfe3_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!whlm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa807bff8-4b44-4477-8828-fcec5f73cfe3_2250x2775 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here&#8217;s what some readers have said:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SE9K!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cd05c8a-1193-4e58-adee-ecaeb4df18cc_865x442.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SE9K!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cd05c8a-1193-4e58-adee-ecaeb4df18cc_865x442.png 424w, https://substackcdn.com/image/fetch/$s_!SE9K!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cd05c8a-1193-4e58-adee-ecaeb4df18cc_865x442.png 848w, https://substackcdn.com/image/fetch/$s_!SE9K!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cd05c8a-1193-4e58-adee-ecaeb4df18cc_865x442.png 1272w, https://substackcdn.com/image/fetch/$s_!SE9K!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cd05c8a-1193-4e58-adee-ecaeb4df18cc_865x442.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SE9K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cd05c8a-1193-4e58-adee-ecaeb4df18cc_865x442.png" width="865" height="442" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3cd05c8a-1193-4e58-adee-ecaeb4df18cc_865x442.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:442,&quot;width&quot;:865,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:104029,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/176895118?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cd05c8a-1193-4e58-adee-ecaeb4df18cc_865x442.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SE9K!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cd05c8a-1193-4e58-adee-ecaeb4df18cc_865x442.png 424w, https://substackcdn.com/image/fetch/$s_!SE9K!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cd05c8a-1193-4e58-adee-ecaeb4df18cc_865x442.png 848w, https://substackcdn.com/image/fetch/$s_!SE9K!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cd05c8a-1193-4e58-adee-ecaeb4df18cc_865x442.png 1272w, https://substackcdn.com/image/fetch/$s_!SE9K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cd05c8a-1193-4e58-adee-ecaeb4df18cc_865x442.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lxMu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89a854ab-a018-4179-b86e-bc1f3516345c_866x187.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lxMu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89a854ab-a018-4179-b86e-bc1f3516345c_866x187.png 424w, https://substackcdn.com/image/fetch/$s_!lxMu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89a854ab-a018-4179-b86e-bc1f3516345c_866x187.png 848w, https://substackcdn.com/image/fetch/$s_!lxMu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89a854ab-a018-4179-b86e-bc1f3516345c_866x187.png 1272w, https://substackcdn.com/image/fetch/$s_!lxMu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89a854ab-a018-4179-b86e-bc1f3516345c_866x187.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lxMu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89a854ab-a018-4179-b86e-bc1f3516345c_866x187.png" width="866" height="187" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/89a854ab-a018-4179-b86e-bc1f3516345c_866x187.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:187,&quot;width&quot;:866,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:34554,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/176895118?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89a854ab-a018-4179-b86e-bc1f3516345c_866x187.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lxMu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89a854ab-a018-4179-b86e-bc1f3516345c_866x187.png 424w, https://substackcdn.com/image/fetch/$s_!lxMu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89a854ab-a018-4179-b86e-bc1f3516345c_866x187.png 848w, https://substackcdn.com/image/fetch/$s_!lxMu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89a854ab-a018-4179-b86e-bc1f3516345c_866x187.png 1272w, https://substackcdn.com/image/fetch/$s_!lxMu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89a854ab-a018-4179-b86e-bc1f3516345c_866x187.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p></p>]]></content:encoded></item><item><title><![CDATA[From Prototype to Production: Why AI Systems Need Architecture Discipline]]></title><description><![CDATA[How architecture makes model- and data-driven systems production-ready]]></description><link>https://deepengineering.net/p/from-prototype-to-production-why</link><guid isPermaLink="false">https://deepengineering.net/p/from-prototype-to-production-why</guid><dc:creator><![CDATA[Richard D Avila]]></dc:creator><pubDate>Thu, 16 Oct 2025 05:25:33 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/972fef15-a47e-4c53-be78-af42a62be545_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://mlq.ai/media/quarterly_decks/v0.1_State_of_AI_in_Business_2025_Report.pdf">The GenAI Divide: STATE OF AI IN BUSINESS 2025</a> study by MIT NANDA surveyed several hundred executives on AI initiatives and found that only <strong>5%</strong> of AI projects demonstrated lasting value. AI initiatives fail to make it to production because teams don&#8217;t define how the AI will deliver value or account for the complex realities of a production environment. In this article, we discuss specific gates and actions that, if followed, will mitigate the risk of a failed prototype-to-production deployment. A very short exemplar or mini-case study is included.</p><blockquote><p><strong>Why call out AI systems?</strong> Unlike traditional software, <strong>AI systems are model- and data-driven</strong>: behavior depends on training data, live inputs, and evaluation metrics. Architecture is how we make that behavior predictable and operable in production.</p></blockquote><h1>Why architecture work matters</h1><p>Architecture makes prototypes survivable in production by forcing end-to-end thinking about data reality, key non-functional constraints, stakeholder trust, and the documentation that turns design into testable behavior.</p><ul><li><p><strong>Data reality:</strong> The key driver in any AI system is the quality of the data used for training and inference. Data engineering is often not done well or not done with a large enough aperture to appreciate the full end-to-end system. For example: does the prototype assume well-formatted or highly structured data that is not possible in production? Did the prototype suppose that data into the system was ordered but, in a production environment, that is not the case? Was the system overly reliant on a technology that cannot scale to expected production data rates? And so on.</p></li><li><p><strong>Partitioning and organization:</strong> Software architecture involves design decisions about partitioning and organization. An analysis of the software components of the prototype can identify where choke points may occur, how interfaces into and out of the prototype were handled, and where a component may require more design work in a production environment.</p></li><li><p><strong>Non-functional requirements:</strong> A robust architecture captures and understands the key non-functional requirements at play in the production environment. For example, will the new AI functionality impact availability requirements that, if not met, would result in a critical error? Does the new model with higher production data rates impact performance requirements that have cascading effects?</p></li><li><p><strong>Trust tolerance:</strong> Robust architecting should illuminate the trust tolerance and decision making of key stakeholders. For example, does a single off-nominal inference or decision result in a loss of confidence or outright hostility to the new AI functionality? As part of a conceptual design, guard rails or off ramps can be put into the design so there is a sort of &#8220;no-harm&#8221; guarantee that may cause consternation but not a loss of confidence.</p></li><li><p><strong>Documentation to test:</strong> The documentation of a conceptual design can aid in defining and illuminating test cases and scenarios that the prototype must satisfy in a production environment.</p></li></ul><h2>A fair criticism&#8212;and the response</h2><blockquote><p>It&#8217;s reasonable to say that architecting adds &#8220;document-centric&#8221; work that slows delivery. But the MIT statistic exists for a reason: <strong>no amount of heroic coding fixes a flawed architecture</strong>. Water doesn&#8217;t flow uphill; a bad architecture produces a bad system. Agile methods work best when a <strong>stable architectural frame</strong> guides rapid prototyping, integration, and demos. Productive engineering isn&#8217;t only writing code&#8212;it&#8217;s also designing systems that can survive production.</p></blockquote><p><strong>Architecting</strong> is critical in guiding engineering decisions and priorities. This is done by laying out the conceptual design of the end system. One of the first drivers of architecture is to have a solid conceptual design that informs prototyping. That same design later anchors the pass/fail criteria that we can categorize into gates.</p><h1>Five production-readiness gates (with actions)</h1><p>There are five gates or decision buckets&#8212;pass/fail outcomes we must clear before go-live.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gmIM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0195efce-b9d1-4bcd-8298-d91856eca0fb_1024x768.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gmIM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0195efce-b9d1-4bcd-8298-d91856eca0fb_1024x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!gmIM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0195efce-b9d1-4bcd-8298-d91856eca0fb_1024x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!gmIM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0195efce-b9d1-4bcd-8298-d91856eca0fb_1024x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!gmIM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0195efce-b9d1-4bcd-8298-d91856eca0fb_1024x768.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gmIM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0195efce-b9d1-4bcd-8298-d91856eca0fb_1024x768.jpeg" width="622" height="466.5" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0195efce-b9d1-4bcd-8298-d91856eca0fb_1024x768.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1024,&quot;resizeWidth&quot;:622,&quot;bytes&quot;:69265,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/176209001?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0195efce-b9d1-4bcd-8298-d91856eca0fb_1024x768.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gmIM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0195efce-b9d1-4bcd-8298-d91856eca0fb_1024x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!gmIM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0195efce-b9d1-4bcd-8298-d91856eca0fb_1024x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!gmIM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0195efce-b9d1-4bcd-8298-d91856eca0fb_1024x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!gmIM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0195efce-b9d1-4bcd-8298-d91856eca0fb_1024x768.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>Note: the following actions are not exhaustive.</p></blockquote><h2>1) Data Engineering <em>(AI inputs and contracts)</em></h2><ul><li><p><strong>Analysis:</strong> Confirm that production inputs and interfaces match what the model expects&#8212;data rates, formats, fields, identifiers, consistency, and summary statistics.</p></li><li><p><strong>Ingest testing:</strong> Exercise the pipeline with <em>current</em> production data and verify ingest correctness and throughput.</p></li><li><p><strong>Resilience checks:</strong> Prove the prototype tolerates malformed records, off-nominal flows, and misformatted streams without cascading failures.</p></li></ul><h3>2) Model Correctness <em>(does the model still earn its keep?)</em></h3><ul><li><p><strong>Ongoing relevance:</strong> Verify the model still solves a real user problem; retire it if it no longer does.</p></li><li><p><strong>Performance on live data:</strong> Re-evaluate with current production samples; confirm metrics such as AUC, accuracy, and false positive rate meet or exceed targets.</p></li><li><p><strong>Execution time:</strong> Show end-to-end model latency remains within bounds required by downstream consumers and users.</p></li></ul><h3>3) Observability <em>(for data pipelines and model behavior)</em></h3><ul><li><p><strong>Data flow telemetry:</strong> Logging and monitors for inputs, transformations, and handoffs.</p></li><li><p><strong>Model execution telemetry:</strong> Traces, metrics, and logs for inference paths and failure modes.</p></li><li><p><strong>Acceptance bounds:</strong> Document thresholds, alert rules, and expected distributions for observability metrics.</p></li></ul><h3>4) Operations <em>(release and recovery for AI pipelines)</em></h3><ul><li><p><strong>Canaries:</strong> Implement canary or shadow releases to surface pipeline issues before full rollout.</p></li><li><p><strong>Time budgets:</strong> Define and monitor time bounds for the full pipeline and each critical stage.</p></li><li><p><strong>Update paths:</strong> Document steps for model refresh, rollback, and disentanglement from production if needed.</p></li></ul><h3>5) Data Governance <em>(quality, compliance, ethics)</em></h3><ul><li><p><strong>Quality audit:</strong> Run a data quality review on the latest production datasets.</p></li><li><p><strong>Compliance docs:</strong> Prepare artifacts to satisfy regulatory, security, and privacy requirements.</p></li><li><p><strong>Ethical filters:</strong> Enforce policy-aligned filtering and access controls.</p></li></ul><blockquote><p>In addition to these gates, the key non-functional requirements for the system need to be monitored, with action plans to ensure they are met&#8212;for example, time budgets across the system via specialized logging and canary deployments.</p></blockquote><p>The gates define what must be true to go live; let us go a step further and look at the rubric that translates the actions into concrete checks and artifacts that prove each gate is passed.</p><h1>Rubric for pre-deployment checks</h1><p>Here is a rubric you can use as a go/no-go gate: confirm business relevance, data contracts, operational readiness (including rollback), on-call coverage and monitoring, and model quality against explicit SLOs before promoting any prototype to production. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lwu4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8adeb70e-f069-4726-905f-609558ffdae4_1024x768.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lwu4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8adeb70e-f069-4726-905f-609558ffdae4_1024x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!lwu4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8adeb70e-f069-4726-905f-609558ffdae4_1024x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!lwu4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8adeb70e-f069-4726-905f-609558ffdae4_1024x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!lwu4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8adeb70e-f069-4726-905f-609558ffdae4_1024x768.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lwu4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8adeb70e-f069-4726-905f-609558ffdae4_1024x768.jpeg" width="664" height="498" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8adeb70e-f069-4726-905f-609558ffdae4_1024x768.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1024,&quot;resizeWidth&quot;:664,&quot;bytes&quot;:129887,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/176209001?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8adeb70e-f069-4726-905f-609558ffdae4_1024x768.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lwu4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8adeb70e-f069-4726-905f-609558ffdae4_1024x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!lwu4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8adeb70e-f069-4726-905f-609558ffdae4_1024x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!lwu4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8adeb70e-f069-4726-905f-609558ffdae4_1024x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!lwu4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8adeb70e-f069-4726-905f-609558ffdae4_1024x768.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This rubric is the evidence and checks that prove each gate is passed and that you can go live. </p><ul><li><p><strong>Business Checks</strong></p><ul><li><p>Is the model output still relevant to intended users?</p></li><li><p>Is the prototype operating at time scales relevant to users?</p></li><li><p>Is the prototype still within budget and resource limits?</p></li></ul></li><li><p><strong>Data Engineering</strong></p><ul><li><p>Verify data sources.</p></li><li><p>Verify interfaces still meet service-level agreements (format, content, time budgets).</p></li><li><p>Confirm computational resources for scale are in place.</p></li></ul></li><li><p><strong>Operations</strong></p><ul><li><p>Verify the prototype in a pre-production environment or a simulation that mimics true production.</p></li><li><p>Confirm datastore schemas and queries execute correctly and produce expected results.</p></li><li><p>Ensure error logging exists at multiple points with sufficient detail for troubleshooting.</p></li><li><p>Identify a rollback procedure to take the model offline if needed.</p></li></ul></li><li><p><strong>Pre-deployment</strong></p><ul><li><p>Identify a developer or staff resource to support rollout and address issues promptly.</p></li><li><p>Ensure monitoring is in place to capture operations.</p></li></ul></li><li><p><strong>Model</strong></p><ul><li><p>Monitor non-functional requirements, SLAs, and errors.</p></li><li><p>Track correctness metrics (AUC, accuracy) and model drift.</p></li><li><p>Provide a user interface (or tools) to visualize execution.</p></li><li><p>Supply guides/documentation for troubleshooting, architecture diagrams, expected outputs at each stage, and accessible source code.</p></li><li><p>Use canaries with injected known datasets for monitoring.</p></li></ul></li></ul><h1>Mini case study</h1><p>To understand how the 5 gates and the rubric work in cohesion, let us take the example of a new application that recommends technical articles to an engineering audience. Before launch, the team decides to apply actions under each of the <strong>5 gates</strong> and the <strong>Pre-deployment checks </strong>reveal the following:</p><ul><li><p><strong>Data &amp; Schema</strong></p><ul><li><p>Since training began, an extra tag field was added; the <strong>date format also changed</strong>, breaking metadata parsing.</p></li><li><p>The <strong>datastore schema drifted</strong>: a key field changed, requiring interface updates.</p></li></ul></li><li><p><strong>Model Correctness: </strong>On current production data, <strong>AUC dropped below training thresholds</strong>; investigation showed an <strong>over-represented training subset</strong>.</p></li><li><p><strong>Observability: Logging failed</strong>; output and processed-file logs couldn&#8217;t be written due to <strong>permission issues</strong>.</p></li><li><p><strong>Operations &amp; Release: </strong>The team planned to launch at <strong>peak traffic</strong> with <strong>no rollback procedure</strong>.</p></li><li><p><strong>Governance: </strong>A compliance check found <strong>personal information</strong> was being collected and <strong>shouldn&#8217;t have been</strong>.</p></li></ul><p>As a result, a failed release is prevented.</p><p>For AI systems, some architecting during prototyping helps cross the chasm from an impressive demonstration to a production system that delivers true value and positive business outcomes.</p><div><hr></div><p>If you found this article insightful, <strong>Richard D Avila</strong> and <strong>Imran Ahmad&#8217;s</strong> forthcoming book, <strong><a href="https://www.packtpub.com/en-us/product/architecting-ai-software-systems-9781804619469">Architecting AI Software Systems</a></strong> extends the same ideas with a structured approach to integrating AI into traditional architectures&#8212;covering how inference and decision-making shape design, using architectural models to ensure cohesion, mitigating risks like underperformance and cost overruns, and applying patterns and heuristics for scalable, high-performance systems&#8212;anchored by real-world case studies, hands-on exercises, and a complete example of an AI-enabled system for architects, engineering leaders, and AI/ML practitioners.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-us/product/architecting-ai-software-systems-9781804619469" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!y8iB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29a0790d-4648-401b-a181-4c4d549a909d_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!y8iB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29a0790d-4648-401b-a181-4c4d549a909d_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!y8iB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29a0790d-4648-401b-a181-4c4d549a909d_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!y8iB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29a0790d-4648-401b-a181-4c4d549a909d_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!y8iB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29a0790d-4648-401b-a181-4c4d549a909d_2250x2775" width="346" height="426.7967032967033" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/29a0790d-4648-401b-a181-4c4d549a909d_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:346,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Architecting AI Software Systems&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-us/product/architecting-ai-software-systems-9781804619469&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Architecting AI Software Systems" title="Architecting AI Software Systems" srcset="https://substackcdn.com/image/fetch/$s_!y8iB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29a0790d-4648-401b-a181-4c4d549a909d_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!y8iB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29a0790d-4648-401b-a181-4c4d549a909d_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!y8iB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29a0790d-4648-401b-a181-4c4d549a909d_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!y8iB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29a0790d-4648-401b-a181-4c4d549a909d_2250x2775 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p><strong>About the Author:</strong></p><p><strong>Richard D Avila</strong> is a software and systems architect with over two decades of industry experience building complex software systems. He has architected and held leadership roles for building a wide array of software systems from complex simulations, autonomy and data analytic systems. He is also a principal investigator for AI topics. He has published in referred journals and industry publications on command-and-control theory, assurance architectures, multi-agent modeling, and machine learning. He was the first expert instructor on data analytics at University of Maryland Baltimore County &#8211; Training Centers. Before working as a software and systems architect, he served in the US Navy as a submarine officer.</p>]]></content:encoded></item><item><title><![CDATA[Quantum Error Correction and Fault Tolerance]]></title><description><![CDATA[The complete &#8220;Chapter 14: Quantum Error Correction and Fault Tolerance&#8221; from the book A Practical Guide to Quantum Computing by El&#237;as F. Combarro and Samuel Gonz&#225;lez-Castillo (Packt, 2025)]]></description><link>https://deepengineering.net/p/quantum-error-correction-and-fault</link><guid isPermaLink="false">https://deepengineering.net/p/quantum-error-correction-and-fault</guid><dc:creator><![CDATA[Elías F. Combarro]]></dc:creator><pubDate>Thu, 02 Oct 2025 05:29:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!IBtK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99207264-fd08-4297-897b-3837b3b2f840_2250x2775" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>To preserve the original layout, equations, and figures, we&#8217;re providing this chapter as a PDF which you can download below.</p><div class="file-embed-wrapper" data-component-name="FileToDOM"><div class="file-embed-container-reader"><div class="file-embed-container-top"><image class="file-embed-thumbnail" src="https://substackcdn.com/image/fetch/$s_!J-2j!,w_400,h_600,c_fill,f_auto,q_auto:best,fl_progressive:steep,g_auto/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9fafdf-b80f-4b70-9cdf-7fdf971f9a10_791x1024.jpeg"></image><div class="file-embed-details"><div class="file-embed-details-h1">The complete &#8220;Chapter 14: Quantum Error Correction and Fault Tolerance&#8221; from the book A Practical Guide to Quantum Computing by El&#237;as F. Combarro and Samuel Gonz&#225;lez-Castillo (Packt, 2025)</div><div class="file-embed-details-h2">4.55MB &#8729; PDF file</div></div><a class="file-embed-button wide" href="https://deepengineering.substack.com/api/v1/file/6d01b869-1ab7-45a7-a90e-11c970f25428.pdf"><span class="file-embed-button-text">Download</span></a></div><div class="file-embed-description">Chapter 14, Quantum Error Correction and Fault Tolerance, introduces quantum error correction, which could pave the road towards fault-tolerant computation and thus may be a cornerstone in the development of useful quantum computers. This chapter begins with a discussion on the necessity for quantum error correction, which is followed by the introduction and implementation of a simple quantum error-correcting code. The chapter finishes with some remarks on fault-tolerant computing and how quantum error correction can help enable it.</div><a class="file-embed-button narrow" href="https://deepengineering.substack.com/api/v1/file/6d01b869-1ab7-45a7-a90e-11c970f25428.pdf"><span class="file-embed-button-text">Download</span></a></div></div><div><hr></div><p>If you liked this chapter, check out <em><strong><a href="https://www.packtpub.com/en-in/product/a-practical-guide-to-quantum-computing-9781835885956">A Practical Guide to Quantum Computing: Hands-on approach to quantum computing with Qiskit</a></strong></em> by El&#237;as F. Combarro and Samuel Gonz&#225;lez-Castillo (Packt, July 2025). It&#8217;s a self-contained introduction that starts from a single qubit and builds to foundational algorithms&#8212;Deutsch, Bernstein&#8211;Vazirani, Grover, Shor&#8212;with runnable code in Qiskit 2.1 on simulators and real hardware. Along the way, it covers applications like quantum money and BB84, and introduces fault tolerance and the road to quantum advantage. </p><p>You&#8217;ll learn to: understand what makes a quantum computer unique; represent and measure multi-qubit states; use superposition, entanglement, and interference; implement and run quantum algorithms in Qiskit; explain how Grover&#8217;s search and Shor&#8217;s factoring work; and get familiar with quantum fault tolerance and emerging notions of quantum advantage. Ideal for university-level STEM courses and for professionals or self-learners who want a practical, code-first path into quantum computing.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IBtK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99207264-fd08-4297-897b-3837b3b2f840_2250x2775" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IBtK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99207264-fd08-4297-897b-3837b3b2f840_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!IBtK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99207264-fd08-4297-897b-3837b3b2f840_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!IBtK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99207264-fd08-4297-897b-3837b3b2f840_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!IBtK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99207264-fd08-4297-897b-3837b3b2f840_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IBtK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99207264-fd08-4297-897b-3837b3b2f840_2250x2775" width="348" height="429.2637362637363" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/99207264-fd08-4297-897b-3837b3b2f840_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:348,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A Practical Guide to Quantum Computing&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A Practical Guide to Quantum Computing" title="A Practical Guide to Quantum Computing" srcset="https://substackcdn.com/image/fetch/$s_!IBtK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99207264-fd08-4297-897b-3837b3b2f840_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!IBtK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99207264-fd08-4297-897b-3837b3b2f840_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!IBtK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99207264-fd08-4297-897b-3837b3b2f840_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!IBtK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99207264-fd08-4297-897b-3837b3b2f840_2250x2775 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here is what some readers have said:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6zNk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7cdae73-8538-4a5f-a5db-eaf54aaf42f1_853x758.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6zNk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7cdae73-8538-4a5f-a5db-eaf54aaf42f1_853x758.png 424w, https://substackcdn.com/image/fetch/$s_!6zNk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7cdae73-8538-4a5f-a5db-eaf54aaf42f1_853x758.png 848w, https://substackcdn.com/image/fetch/$s_!6zNk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7cdae73-8538-4a5f-a5db-eaf54aaf42f1_853x758.png 1272w, https://substackcdn.com/image/fetch/$s_!6zNk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7cdae73-8538-4a5f-a5db-eaf54aaf42f1_853x758.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6zNk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7cdae73-8538-4a5f-a5db-eaf54aaf42f1_853x758.png" width="853" height="758" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f7cdae73-8538-4a5f-a5db-eaf54aaf42f1_853x758.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:758,&quot;width&quot;:853,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:167282,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/175012810?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7cdae73-8538-4a5f-a5db-eaf54aaf42f1_853x758.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6zNk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7cdae73-8538-4a5f-a5db-eaf54aaf42f1_853x758.png 424w, https://substackcdn.com/image/fetch/$s_!6zNk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7cdae73-8538-4a5f-a5db-eaf54aaf42f1_853x758.png 848w, https://substackcdn.com/image/fetch/$s_!6zNk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7cdae73-8538-4a5f-a5db-eaf54aaf42f1_853x758.png 1272w, https://substackcdn.com/image/fetch/$s_!6zNk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7cdae73-8538-4a5f-a5db-eaf54aaf42f1_853x758.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pZ0d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f64ea74-f674-43a2-9eba-f300daa37fdc_860x313.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pZ0d!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f64ea74-f674-43a2-9eba-f300daa37fdc_860x313.png 424w, https://substackcdn.com/image/fetch/$s_!pZ0d!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f64ea74-f674-43a2-9eba-f300daa37fdc_860x313.png 848w, https://substackcdn.com/image/fetch/$s_!pZ0d!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f64ea74-f674-43a2-9eba-f300daa37fdc_860x313.png 1272w, https://substackcdn.com/image/fetch/$s_!pZ0d!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f64ea74-f674-43a2-9eba-f300daa37fdc_860x313.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pZ0d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f64ea74-f674-43a2-9eba-f300daa37fdc_860x313.png" width="860" height="313" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7f64ea74-f674-43a2-9eba-f300daa37fdc_860x313.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:313,&quot;width&quot;:860,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:71542,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/175012810?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f64ea74-f674-43a2-9eba-f300daa37fdc_860x313.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pZ0d!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f64ea74-f674-43a2-9eba-f300daa37fdc_860x313.png 424w, https://substackcdn.com/image/fetch/$s_!pZ0d!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f64ea74-f674-43a2-9eba-f300daa37fdc_860x313.png 848w, https://substackcdn.com/image/fetch/$s_!pZ0d!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f64ea74-f674-43a2-9eba-f300daa37fdc_860x313.png 1272w, https://substackcdn.com/image/fetch/$s_!pZ0d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f64ea74-f674-43a2-9eba-f300daa37fdc_860x313.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><a href="https://deepengineering.substack.com/api/v1/file/adef3bb7-2c9c-4998-b4f5-029c2d9b33e4.pdf">Download</a></p>]]></content:encoded></item><item><title><![CDATA[Clean Architecture Essentials: Transforming Python Development]]></title><description><![CDATA[The complete &#8220;Chapter 1: Clean Architecture Essentials: Transforming Python Development&#8221; from the book Clean Architecture with Python by Sam Keen (Packt, June 2025).]]></description><link>https://deepengineering.net/p/clean-architecture-essentials-transforming</link><guid isPermaLink="false">https://deepengineering.net/p/clean-architecture-essentials-transforming</guid><dc:creator><![CDATA[Sam Keen]]></dc:creator><pubDate>Wed, 17 Sep 2025 11:22:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!NtED!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As Python developers, we apply best practices such as writing clean functions, using descriptive variable names, and striving for modularity. Yet, as our applications grow, we often struggle to maintain this clarity and adaptability at scale. Python&#8217;s simplicity and versatility make it popular for projects ranging from web development to data science, but these strengths can become challenges as applications become more complex. We find ourselves lacking a master plan, an overarching architecture to guide our decisions and keep our projects maintainable as they evolve. This is where <strong>Clean Architecture</strong> comes into play, offering a structured approach to building Python applications that balance planning and agility, providing the architectural guidance we need for sustainable, large-scale development.</p><p>Clean Architecture, introduced by <a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">Robert C. Martin in 2012</a>, synthesizes decades of software design wisdom into a cohesive set of principles. It addresses persistent challenges in software development, such as managing complexity and accommodating change. By applying Clean Architecture principles to Python projects, developers can create systems that are not only functional but also maintainable, testable, and adaptable over time.</p><p>In this chapter, we&#8217;ll explore the essence of Clean Architecture and its relevance to Python development. We&#8217;ll examine how Clean Architecture principles align with Python&#8217;s philosophy of simplicity and readability, creating a natural synergy that enhances Python&#8217;s strengths. You&#8217;ll learn how Clean Architecture can help you build Python applications that are easier to understand, modify, and extend, even as they grow in complexity.</p><p>By the end of this chapter, you&#8217;ll have an overview of Clean Architecture principles and their potential benefits for Python development. You&#8217;ll be introduced to how this approach can address common challenges in software development, particularly as Python projects grow in scale and complexity. This foundational understanding of Clean Architecture will be essential as we delve deeper into its implementation and best practices in Python throughout the rest of the book.</p><p>In this chapter, we&#8217;re going to cover the following main topics:</p><ul><li><p>Why Clean Architecture in Python: the benefits of balancing planning and agility</p></li><li><p>What is Clean Architecture?</p></li><li><p>Clean Architecture and Python: a natural fit</p></li></ul><div><hr></div><h1>Why Clean Architecture in Python: the benefits of balancing planning and agility</h1><p>In this section, we&#8217;ll explore the critical balance between planning and agility in Python development and how Clean Architecture can help achieve this balance. We&#8217;ll examine the challenges posed by increasing complexity in modern Python applications and the imperative for agility in today&#8217;s fast-paced business environment. We&#8217;ll then discuss the trade-offs between planning and flexibility, and how architectural thinking can provide a framework for managing these trade-offs. Finally, we&#8217;ll look at the role of architecture in managing complexity and setting the stage for long-term success. Through these discussions, you&#8217;ll gain insight into why Clean Architecture is particularly valuable for Python developers striving to create maintainable, adaptable, and efficient applications.</p><p>Let&#8217;s begin by examining the complex challenges facing modern Python development.</p><h2>The complexity challenge in modern Python development</h2><p>As Python&#8217;s popularity soars, so do the scale and complexity of applications built with it. From web services to data science pipelines, Python projects are growing larger and more intricate. This growth brings significant challenges that every Python developer must grapple with.</p><p>The increasing complexity of systems makes them harder to understand, modify, and maintain. This complexity can severely limit your ability to add new features or respond to changing requirements. The maintenance burden of complex Python systems can overwhelm development teams, slowing down progress and innovation. Even small changes in large, complex systems can have far-reaching consequences, making modifications expensive and risky.</p><p>Consider a fictitious large Python-based e-commerce site: PyShop. The business decides to implement a seemingly simple feature: adding gift-wrapping options to orders. However, this straightforward addition quickly cascades into a complex project:</p><ul><li><p>The order processing module needs updates to include gift-wrapping choices</p></li><li><p>The inventory system requires modification to track gift-wrapping supplies</p></li><li><p>The pricing engine needs adjustments to calculate additional costs</p></li><li><p>The user interface (UI) must be updated to present gift-wrapping options</p></li><li><p>The fulfillment system needs changes to include gift-wrapping instructions</p></li></ul><p>What was estimated as a two-week-long task stretches into a multi-month project. Each change potentially impacts other system parts: adjustments in order processing affect reporting, inventory changes influence supply chain management, and UI modifications require extensive user experience testing.</p><p>This example highlights how interconnected modules in a complex system can turn a simple feature addition into a significant undertaking, emphasizing the need for an architecture that allows for more isolated changes and easier testing processes.</p><p>Moreover, as Python projects grow, developers often struggle with abstractions, a critical aspect that Clean Architecture helps address. Without proper guidance, codebases can suffer from extremes: either becoming a tangled mess of deeply nested class hierarchies that are hard to understand and modify or devolving into monolithic <em>do-everything</em> classes that lack any meaningful abstraction. In the former case, developers may create too-complex inheritance structures to maximize code reuse, resulting in a fragile system where changes in one place have unforeseen consequences elsewhere. In the latter case, the lack of abstraction leads to massive, unwieldy classes and rampant code duplication, making it nearly impossible to maintain consistency or make systemic changes. Both scenarios result in codebases that are difficult to understand, maintain, and extend, which is precisely the issues that a well-planned architecture helps prevent.</p><p>Furthermore, in today&#8217;s rapidly evolving tech landscape, complex, tightly coupled systems struggle to take advantage of new technologies. This limitation can significantly impact your ability to stay competitive in a field where technological agility is crucial.</p><h2>The agility imperative</h2><p>In our fast-paced business environment, agility is not just an advantage&#8212;it&#8217;s a necessity. With every company essentially becoming a technology company, the pressure to deliver quickly has never been higher. Python&#8217;s simplicity and extensive ecosystem make it an excellent choice for rapid development.</p><p>However, sustainable agility requires more than just initial speed, it demands architectural decisions that support ongoing evolution. It&#8217;s akin to building a high-performance race car: without proper design fundamentals, what starts as impressive acceleration quickly becomes limited by poor handling and maintenance challenges.</p><p>In rapidly evolving Python applications, this principle becomes starkly evident. Without a cohesive architecture, quick feature additions can create a tangled web of dependencies. What starts as a nimble codebase can, within months, become rigid and fragile. Developers find themselves spending more time deciphering existing code than writing new features. When it&#8217;s not immediately clear where new code should be added or how it should interact with existing components, developers under pressure may make hasty decisions, leading to suboptimal implementations and introducing bugs. These quick fixes further complicate the codebase, making future changes even more challenging. The initial velocity becomes unsustainable, not because of the speed itself, but due to the lack of a sturdy architectural foundation that can guide rapid changes and provide clear pathways for new feature integration.</p><p>Requirements change, often unpredictably. Your Python projects need to be structured in a way that allows for easy adaptation to these changes. This adaptability is crucial for long-term success in software development.</p><h2>Striking a balance: the planning&#8211;agility trade-off</h2><p>Finding the right balance between planning and agility is crucial in Python development. As Dave Thomas wisely said, &#8220;<em>Big design up front is dumb. Doing no design up front is even dumber.</em>&#8221; The key is finding the middle ground that allows for both structure and flexibility.</p><p>Good architecture helps you postpone decisions. It gives you the flexibility to push decisions to later stages when you have more information to make the correct choice. This approach is particularly valuable in Python development, where the language&#8217;s flexibility can sometimes lead to decision paralysis.</p><p>Introducing architectural thinking in Python development means considering the long-term structure of your project from the start, without over-engineering. It&#8217;s about creating a framework that guides development while remaining adaptable to change.</p><h2>The role of architecture in managing complexity</h2><p>Effective architecture is your best tool for managing complexity in Python systems. Good architecture simplifies complex systems by providing a clear structure and <strong>separation of concerns</strong> (<strong>SoC</strong>). One of the first steps in architecting a new system is determining how to divide it, keeping things that change for the same reason together and things that change for different reasons apart.</p><p>Consider two Python-based <strong>content management systems</strong> (<strong>CMSs</strong>) for media companies, both tasked with implementing a new AI-powered content tagging feature. In the well-architected system, this feature is implemented as a standalone module with clear interfaces. It integrates smoothly with the existing content creation and search modules through well-defined APIs. Developers can build and test the AI tagging service independently, and then connect it to the content database and UI with minimal disruption. Conversely, in a poorly structured system, adding this feature requires changes across the entire stack&#8212;from database schemas to frontend code&#8212;leading to unexpected bugs and performance issues. What takes a sprint in the well-architected system becomes a months-long refactoring project in the poorly structured one, demonstrating how thoughtful initial architecture can dramatically improve development efficiency and system adaptability.</p><p>The architectural decisions you make early on have a profound impact on the long-term development costs and flexibility of your Python projects. A well-architected system can significantly reduce the cost of change over time, allowing your team to respond more quickly to new requirements or technological changes.</p><h2>Preparing for Clean Architecture</h2><p>As we move toward discussing Clean Architecture, it&#8217;s important to understand that it offers a systematic approach to balancing planning and agility in Python projects. Architectural principles provide powerful tools for managing and reducing complexity in your Python systems.</p><p>At its core, Clean Architecture is about strategic SoC in your Python applications. It advocates for a structure where the essential business logic is insulated from external factors such as UIs, databases, and third-party integrations. This separation creates clear boundaries between different parts of your application, each with its own responsibilities. By doing so, Clean Architecture allows your core business rules to remain pure and unaffected by the implementation details of <strong>input/output</strong> (<strong>I/O</strong>) mechanisms or <strong>data management systems</strong> (<strong>DMSs</strong>).</p><p>By understanding these challenges and principles, you&#8217;ll be better prepared to appreciate the benefits that Clean Architecture can bring to your Python projects. In the next sections, we&#8217;ll delve into what Clean Architecture is and how it specifically applies to Python development, providing you with the tools to combat complexity and reduce the cost of change in your software systems.</p><div><hr></div><h1>What is Clean Architecture?</h1><p>Having explored the challenges of managing complexity in Python development and the need for balancing planning with agility, the goal of this section is to give you a high-level overview of Clean Architecture. We&#8217;ll be covering several key concepts and principles in quick succession to provide a broad understanding. Don&#8217;t worry if you don&#8217;t grasp every detail immediately. This is just the beginning of our journey. Each of these topics will be explored in depth in the chapters to come, where we&#8217;ll dive into practical Python implementations and real-world scenarios.</p><p>Clean Architecture synthesizes many ideas from previous architectural styles, but it is built around a fundamental concept: the separation of software elements into ring levels, with a strict rule that code dependencies can only move inward from outer levels. This principle is formally known as the <strong>Dependency Rule</strong>, one of the most critical aspects of Clean Architecture. The Dependency Rule states that source code dependencies must only point inward, toward higher-level policies. Inner circles must know nothing about outer circles, while outer circles must depend on and adapt to inner circles. This ensures that changes to external elements (like databases, UI, or frameworks) don&#8217;t impact the core business logic. The aim is to create software systems that are not only functional but also maintainable and adaptable over time. To illustrate this, let&#8217;s consider a simple Python application for a library management system:</p><ol><li><p>At the core, we have the <code>Book</code> class, representing the basic data structure.</p></li><li><p>Moving outward, we have a <code>BookInventory</code> class that manages operations on books.</p></li><li><p>In the outer ring, we have a <code>BookInterface</code> class that handles user interactions related to books.</p></li></ol><p>In this structure, the <code>Book</code> class knows nothing about the <code>BookInventory</code> or <code>BookInterface</code> classes. The <code>BookInventory</code> class might use the <code>Book</code> class but doesn&#8217;t know about the interface. This separation ensures that the core logic remains unaffected by external concerns.</p><p>Crucially, this structure allows us to modify or even replace outer layers without affecting the inner layers. For instance, we could change the UI from a <strong>command-line interface</strong> (<strong>CLI</strong>) to a web interface by modifying the <code>BookInterface</code> class, without needing to alter the <code>Book</code> or <code>BookInventory</code> classes. This flexibility is a key advantage of the Clean Architecture approach.</p><p>This structure is designed to produce systems that embody the key principles we introduced earlier:</p><ul><li><p>SoC</p></li><li><p>Independence of external details</p></li><li><p><strong>Testability</strong> and <strong>maintainability</strong></p></li></ul><p>Let&#8217;s explore further how Clean Architecture achieves these goals and the benefits it brings to software development.</p><h2>The onion architecture concept</h2><p>Let&#8217;s visualize the ring levels mentioned earlier and add another level of detail as to the purpose of each ring. Clean Architecture is often visualized as a series of concentric circles, like an onion. Each circle represents a different layer of software, and the Dependency Rule we discussed ensures that dependencies only flow inward across these boundaries. The core layers contain business logic (entities), while the external layers contain interface and implementation details (<em>see Figure 1.1</em>):</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6ExJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a3e611-5f6a-4b9d-be71-980ad4643b6e_1050x1050.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6ExJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a3e611-5f6a-4b9d-be71-980ad4643b6e_1050x1050.png 424w, https://substackcdn.com/image/fetch/$s_!6ExJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a3e611-5f6a-4b9d-be71-980ad4643b6e_1050x1050.png 848w, https://substackcdn.com/image/fetch/$s_!6ExJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a3e611-5f6a-4b9d-be71-980ad4643b6e_1050x1050.png 1272w, https://substackcdn.com/image/fetch/$s_!6ExJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a3e611-5f6a-4b9d-be71-980ad4643b6e_1050x1050.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6ExJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a3e611-5f6a-4b9d-be71-980ad4643b6e_1050x1050.png" width="1050" height="1050" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a2a3e611-5f6a-4b9d-be71-980ad4643b6e_1050x1050.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1050,&quot;width&quot;:1050,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 1.1: Clean Architecture: a series of concentric layers&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 1.1: Clean Architecture: a series of concentric layers" title="Figure 1.1: Clean Architecture: a series of concentric layers" srcset="https://substackcdn.com/image/fetch/$s_!6ExJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a3e611-5f6a-4b9d-be71-980ad4643b6e_1050x1050.png 424w, https://substackcdn.com/image/fetch/$s_!6ExJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a3e611-5f6a-4b9d-be71-980ad4643b6e_1050x1050.png 848w, https://substackcdn.com/image/fetch/$s_!6ExJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a3e611-5f6a-4b9d-be71-980ad4643b6e_1050x1050.png 1272w, https://substackcdn.com/image/fetch/$s_!6ExJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a3e611-5f6a-4b9d-be71-980ad4643b6e_1050x1050.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 1.1: Clean Architecture: a series of concentric layers</em></p><p><em>Figure 1.1</em> demonstrates the separation of inner core business logic progressing out to external interfaces:</p><ul><li><p><strong>Entities</strong>: At the center are entities, which encapsulate enterprise-wide business rules. Entities in this context are the primary <em>nouns</em> of your product, the core business objects that would exist even without software. For example, in an e-commerce system, entities might include <em>Customer</em>, <em>Product</em>, and <em>Order</em>. In a task management application, they could be <em>User</em>, <em>Task</em>, and <em>Project</em>. These entities contain the most basic, universal rules about how these objects behave and interact.</p></li><li><p><strong>Use Cases</strong>: The next layer contains use cases, which orchestrate the flow of data to and from entities. A use case represents a specific way the system is used. It&#8217;s essentially a description of how the system should behave for a particular scenario. For instance, in a task management app, use cases might include <em>Create New Task</em>, <em>Complete Task</em>, or <em>Assign Task</em>. Use cases contain application-specific business rules and control how and when entities are used to fulfill the goals of the application.</p></li><li><p><strong>Interface Adapters</strong>: Further out, we find interface adapters, which convert data between use cases and external agencies. This layer acts as a set of translators between the inner layers (entities and use cases) and the outer layer. It might include things such as controllers that handle HTTP requests, presenters that format data for display, and gateways that transform data for persistence. In a Python web application, this might include your view functions or classes that handle routing and request processing. A key point of this layer is that it allows us to decouple from frameworks.</p></li><li><p><strong>Frameworks and Drivers</strong>: The outermost layer contains frameworks and drivers, where the <em>external agencies</em> reside. By <em>drivers</em>, we mean the specific tools, frameworks, and delivery mechanisms that are used to run the system but aren&#8217;t core to the business logic. In a Python context, examples might include the following:</p><ul><li><p>Web frameworks such as Django or Flask</p></li><li><p>Database drivers such as <code>psycopg2</code> for PostgreSQL or <code>pymongo</code> for MongoDB</p></li><li><p>External libraries for tasks such as sending emails (for example, <code>smtplib</code>) or processing payments</p></li><li><p>UI frameworks if you&#8217;re building a desktop or mobile app (for example, PyQt)</p></li><li><p>System utilities for tasks such as logging or configuration management</p></li></ul></li></ul><p>This outermost layer is the most volatile, as it&#8217;s where we interact with the outside world and where technologies are most likely to change over time. By keeping it separate from our core business logic, we can more easily swap out these external tools without affecting the heart of our application.</p><p>This layered structure of Clean Architecture promotes SoC, establishing a clear organizational framework for software systems. Now that we have an idea of the fundamental structure of Clean Architecture, let&#8217;s further investigate its broader benefits.</p><h2>Benefits of Clean Architecture</h2><p>One of the primary advantages of Clean Architecture is its focus on protecting and isolating your core business logic, the domain objects that represent the foundation of your business. While external details such as web frameworks and persistence engines come and go, the true value to your business lies in the time invested in designing and implementing these core domain objects. Clean Architecture recognizes this and provides a structure that insulates these crucial components from the volatility of external technologies.</p><p>This architectural approach protects your investment in domain logic from the need to move away from a given framework or technology. For example, if a framework you&#8217;re using moves from an open source model to a proprietary one, Clean Architecture allows you to replace it without rewriting your core business logic. This separation significantly reduces the cost and risk of changes over time, allowing your system to evolve more easily as requirements change or as you need to adapt to new technologies. In essence, Clean Architecture ensures that the most valuable and stable part of your application, your business logic, remains unaffected by the often turbulent world of external technologies and frameworks.</p><p>Another key benefit is enhanced testability across all layers of the application. The independence of the core business logic from external details makes it much easier to write comprehensive unit tests. You can test business rules in isolation, without the need to spin up a database or web server or build cumbersome mocks. This leads to more thorough testing and, consequently, more robust software. It also encourages developers to write more tests, as the process becomes simpler and more straightforward.</p><p>Clean Architecture also provides flexibility in technology choices. Because the core of the application isn&#8217;t dependent on external frameworks or tools, you have the freedom to swap out these elements as needed. This is particularly valuable in the fast-moving world of technology, where today&#8217;s popular framework might be obsolete tomorrow. Similarly, you might start with a CLI for internal use, then add a web interface for broader access, all without altering your core business rules&#8217; code. Your core business logic remains stable, while you have the flexibility to adopt new technologies in the outer layers as they emerge. Lastly, Clean Architecture promotes long-term agility in development and leads to what Robert C. Martin calls a <em><a href="https://blog.cleancoder.com/uncle-bob/2011/09/30/Screaming-Architecture.html">Screaming Architecture</a></em>. Its focus on separating concerns and managing dependencies results in a codebase that&#8217;s easier to understand and modify. The concept of Screaming Architecture suggests that when you look at the structure of your system, it should scream its purpose and use cases, not its frameworks or tools. For instance, your architecture should scream <em>online bookstore</em>, not <em>Django application</em>. This clear, purpose-driven structure allows new team members to quickly grasp the system&#8217;s intent and make contributions. The architecture itself becomes a form of documentation, revealing the system&#8217;s core purpose and functionality at a glance. Such clarity and flexibility translate to increased development speed over the long term, even as the system grows in complexity. It also ensures that your system remains focused on its core business logic, rather than being tied to specific technical implementations.</p><h2>Clean Architecture in context</h2><p>To fully appreciate the value of Clean Architecture, it&#8217;s important to understand its place in the broader context of software development practices and methodologies.</p><p>Clean Architecture represents an evolution from traditional layered architecture. While it builds upon the concept of layers, it places a stronger emphasis on SoC and enforces the Dependency Rule more strictly than traditional architectures. Unlike traditional layered architectures where lower layers often depend on persistence or infrastructure concerns, Clean Architecture keeps the inner layers pure and focused on business logic. This shift in focus allows for greater flexibility and resilience to change.</p><p>Clean Architecture complements modern development practices such as Agile and DevOps. It aligns well with Agile methodologies by facilitating <strong>continuous delivery</strong> (<strong>CD</strong>) and making it easier to respond to change. The clear SoC supports iterative development and makes it easier to modify or extend functionality in response to changing requirements. In terms of DevOps, Clean Architecture supports practices such as <strong>continuous integration and deployment</strong> (<strong>CI/CD</strong>) by making systems more testable and modular. The clear boundaries between components can also help in scaling development across teams, as different teams can work on different layers or components with minimal interference.</p><p>In conclusion, Clean Architecture offers a powerful approach to building software systems that are scalable, maintainable, and adaptable to change. By focusing on SoC and managing dependencies, it provides a structure that can withstand the test of time and the pressures of evolving technology and business needs. As we move into the next section, we&#8217;ll explore how these principles align particularly well with Python development practices.</p><div><hr></div><h1>Clean Architecture and Python: a natural fit</h1><p>As we&#8217;ve explored the principles and benefits of Clean Architecture, you might be wondering how well these concepts align with Python development. In this section, we&#8217;ll discover that Clean Architecture and Python share a natural affinity, making Python an excellent language for implementing Clean Architecture principles.</p><p>Python&#8217;s philosophy, as embodied in <em><a href="https://peps.python.org/pep-0020/">The Zen of Python</a></em> aligns remarkably well with Clean Architecture principles. Both emphasize simplicity, readability, and the importance of well-structured code. Python&#8217;s focus on creating clear, maintainable, and adaptable code provides a strong foundation for implementing Clean Architecture. As we delve deeper into this section, we&#8217;ll explore how Python language features can be leveraged to create robust, maintainable systems that adhere to Clean Architecture principles.</p><h2>Implementing Clean Architecture in Python</h2><p>Python&#8217;s dynamic nature, combined with its strong support for <strong>object-oriented programming</strong> (<strong>OOP</strong>) and functional programming paradigms, allows developers to implement Clean Architecture concepts with less boilerplate and greater clarity than many other languages.</p><p><strong>Note on code examples</strong></p><p>Throughout this book, you&#8217;ll notice type annotations in our code examples (e.g., <code>def function(parameter: type) -&gt; return_type)</code>. These type hints enhance code clarity and help enforce Clean Architecture boundaries. We&#8217;ll explore this powerful feature in depth in <em><a href="https://subscription.packtpub.com/book/programming/9781836642893/3">Chapter 3</a></em>.</p><p>A key principle of Clean Architecture is the reliance on abstractions rather than concrete implementations. This principle directly supports the Dependency Rule we discussed earlier: dependencies should only point inward. Let&#8217;s see how this works in practice using Python&#8217;s <strong>abstract base classes</strong> (<strong>ABCs</strong>).</p><p>Consider the following example, which models a notification system:</p><pre><code><code>from abc import ABC, abstractmethod
class Notifier(ABC):
    @abstractmethod
    def send_notification(self, message: str) -&gt; None:
        pass
class EmailNotifier(Notifier):
    def send_notification(self, message: str) -&gt; None:
        print(f"Sending email: {message}")
class SMSNotifier(Notifier):
    def send_notification(self, message: str) -&gt; None:
        print(f"Sending SMS: {message}")
class NotificationService:
    def __init__(self, notifier: Notifier):
        self.notifier = notifier
    def notify(self, message: str) -&gt; None:
        self.notifier.send_notification(message)
# Usage
email_notifier = EmailNotifier()
email_service = NotificationService(email_notifier)
email_service.notify("Hello via email")
</code></code></pre><p>This example demonstrates key concepts of Clean Architecture using Python&#8217;s ABCs:</p><ol><li><p><strong>ABC</strong>: The <code>Notifier</code> class is an ABC, defining an interface that all notifier classes must follow. This represents an inner ring in our Clean Architecture structure.</p></li><li><p><strong>Abstract method</strong>: The <code>send_notification</code> method in <code>Notifier</code> is marked with <code>@abstractmethod</code>, enforcing implementation in subclasses.</p></li><li><p><strong>Concrete implementations</strong>: <code>EmailNotifier</code> and <code>SMSNotifier</code> are concrete classes in an outer ring. They inherit from <code>Notifier</code> and provide specific implementations.</p></li><li><p><strong>Dependency inversion</strong>: The <code>NotificationService</code> class depends on the abstract <code>Notifier</code> class, not on concrete implementations. This adheres to the Dependency Rule, as the abstract <code>Notifier</code> class (inner ring) doesn&#8217;t depend on the concrete notifiers (outer ring). We&#8217;ll dive deeper into dependency inversion in the next chapter.</p></li></ol><p>This structure embodies the Clean Architecture principles we&#8217;ve discussed:</p><ul><li><p><strong>It respects the Dependency Rule</strong>: The abstract <code>Notifier</code> class (inner ring) knows nothing about the concrete notifiers or the <code>NotificationService</code> class (outer rings)</p></li><li><p><strong>It allows for easy extension</strong>: We can add new types of notifiers (such as <code>PushNotifier</code>) without changing the <code>NotificationService</code> class</p></li><li><p><strong>It promotes flexibility and maintainability</strong>: The core business logic (sending a notification) is separated from the implementation details (how the notification is sent)</p></li></ul><p>By structuring our code this way, we create a system that&#8217;s not only flexible and maintainable but also adheres to the fundamental principles of Clean Architecture. The abstract <code>Notifier</code> class represents our core business rules, while the concrete notifiers and the <code>NotificationService</code> class represent the more volatile outer layers. This separation allows us to easily swap or add new notification methods without affecting the core logic of our application.</p><p>So, we&#8217;ve seen a simple ABC example, but this is where Python truly shines. We can implement the same Clean Architecture principles without using a class hierarchy, instead relying on Python&#8217;s support for <strong><a href="https://realpython.com/duck-typing-python/">duck typing</a></strong>. This flexibility is one of Python&#8217;s strengths, allowing developers to choose the approach that best fits their project&#8217;s needs while still adhering to Clean Architecture principles.</p><p>Duck typing is a programming concept where the suitability of an object is determined by the presence of certain methods or properties, rather than its explicit type. The name comes from the saying, &#8220;If it walks like a duck and quacks like a duck, then it must be a duck.&#8221; In duck typing, we don&#8217;t care about the object&#8217;s type; we care about whether it can do what we need it to do.</p><p>This approach aligns well with Clean Architecture&#8217;s emphasis on abstractions and interfaces. If you&#8217;d prefer to stay away from rigid class hierarchies, Python&#8217;s <code>Protocol</code> feature, introduced in <a href="https://peps.python.org/pep-0544/">Python 3.8</a>, offers the best of both worlds: duck typing with type hinting. Here&#8217;s an example that implements the same notification system using protocols:</p><pre><code><code>from typing import Protocol
class Notifier(Protocol):
    def send_notification(self, message: str) -&gt; None:
        ...
class EmailNotifier: # Note: no explicit inheritance
    def send_notification(self, message: str) -&gt; None:
        print(f"Sending email: {message}")
class SMSNotifier: # Note: no explicit inheritance
    def send_notification(self, message: str) -&gt; None:
        print(f"Sending SMS: {message}")
class NotificationService:
    # Still able to use type hinting
    def __init__(self, notifier: Notifier):
        self.notifier = notifier
    def notify(self, message: str) -&gt; None:
        self.notifier.send_notification(message)
# Usage
sms_notifier = SMSNotifier()
sms_service = NotificationService(sms_notifier)
sms_service.notify("Hello via SMS")
</code></code></pre><p>This example demonstrates the same notification system as before but using Python&#8217;s <code>Protocol</code> feature instead of ABCs. Let&#8217;s break down the key differences and their implications for Clean Architecture:</p><ul><li><p><strong>Protocol versus ABC</strong>: The <code>Notifier</code> class is now a <code>Protocol</code> class instead of an <code>ABC</code> class. It defines a structural subtyping interface rather than requiring explicit inheritance.</p></li><li><p><strong>Implicit conformance</strong>: The <code>EmailNotifier</code> and <code>SMSNotifier</code> classes don&#8217;t explicitly inherit from the <code>Notifier</code> class, but they conform to its interface by implementing the <code>send_notification</code> method.</p></li><li><p><strong>Duck typing with type hinting</strong>: This approach combines Python&#8217;s duck typing flexibility with the benefits of static type checking, aligning with Clean Architecture&#8217;s emphasis on loose coupling.</p></li><li><p><strong>Concrete implementations</strong>: The <code>NotificationService</code> class still depends on the abstract <code>Notifier</code> protocol, not concrete implementations, adhering to Clean Architecture principles.</p></li></ul><p>This protocol-based approach offers a flexible, Pythonic implementation of Clean Architecture concepts, balancing type safety with reduced class hierarchy rigidity. It demonstrates how to align Clean Architecture principles with Python&#8217;s philosophy, promoting adaptable and maintainable code.</p><p>We highly recommend the use of type hinting via either ABCs or protocols when implementing Clean Architecture. This approach, as opposed to simple implicit interfaces without type hinting, offers significant advantages:</p><ul><li><p>Improved code readability</p></li><li><p>Enhanced IDE support and earlier error detection</p></li><li><p>Better alignment with Clean Architecture goals</p></li></ul><p>In the remaining parts of the book, we&#8217;ll primarily use ABCs in our examples as they are in greater use in existing Python codebases. However, the principles discussed are equally applicable to protocol-based implementations, and readers can adapt the examples to use protocols if preferred.</p><h2>Practical example: a glimpse of Clean Architecture in a Python project</h2><p>To illustrate the concepts we&#8217;ve discussed, let&#8217;s examine the basic structure of a Clean Architecture Python project. This structure embodies the principles we&#8217;ve covered and demonstrates how they translate into a practical file organization. We&#8217;ll stay at a high level here; later chapters will cover real-world examples in full detail:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Kd79!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fc3da56-3a64-403d-a1c2-c1f878660b96_718x1228.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Kd79!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fc3da56-3a64-403d-a1c2-c1f878660b96_718x1228.png 424w, https://substackcdn.com/image/fetch/$s_!Kd79!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fc3da56-3a64-403d-a1c2-c1f878660b96_718x1228.png 848w, https://substackcdn.com/image/fetch/$s_!Kd79!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fc3da56-3a64-403d-a1c2-c1f878660b96_718x1228.png 1272w, https://substackcdn.com/image/fetch/$s_!Kd79!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fc3da56-3a64-403d-a1c2-c1f878660b96_718x1228.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Kd79!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fc3da56-3a64-403d-a1c2-c1f878660b96_718x1228.png" width="718" height="1228" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0fc3da56-3a64-403d-a1c2-c1f878660b96_718x1228.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1228,&quot;width&quot;:718,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 1.2: A potential layout for a Clean Architecture Python web application&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 1.2: A potential layout for a Clean Architecture Python web application" title="Figure 1.2: A potential layout for a Clean Architecture Python web application" srcset="https://substackcdn.com/image/fetch/$s_!Kd79!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fc3da56-3a64-403d-a1c2-c1f878660b96_718x1228.png 424w, https://substackcdn.com/image/fetch/$s_!Kd79!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fc3da56-3a64-403d-a1c2-c1f878660b96_718x1228.png 848w, https://substackcdn.com/image/fetch/$s_!Kd79!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fc3da56-3a64-403d-a1c2-c1f878660b96_718x1228.png 1272w, https://substackcdn.com/image/fetch/$s_!Kd79!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fc3da56-3a64-403d-a1c2-c1f878660b96_718x1228.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 1.2: A potential layout for a Clean Architecture Python web application</em></p><p>This file structure in <em>Figure 1.2</em> exemplifies the Clean Architecture principles we&#8217;ve discussed:</p><ol><li><p><strong>SoC</strong>: Each directory represents a distinct layer of the application, aligning with the concentric circles we saw in <em>Figure 1.1</em>.</p></li><li><p><strong>Dependency Rule</strong>: The structure enforces the Dependency Rule we discussed earlier. If we were to investigate the inner layers (<code>entities</code> and <code>use_cases</code>), we would not see any imports from the outer layers.</p></li><li><p><strong>Entities layer</strong>: The <code>entities</code> directory contains the core business objects, such as <code>user.py</code>. These are at the center of our Clean Architecture diagram and have no dependencies on outer layers.</p></li><li><p><strong>Use Cases layer</strong>: The <code>use_cases</code> directory holds the application-specific business rules. It depends on the entities but is independent of the outer layers.</p></li><li><p><strong>Interface Adapters layer</strong>: The <code>interfaces</code> directory contains controllers, presenters, and gateways. These adapt data between use cases and external agencies (such as web frameworks or databases).</p></li><li><p><strong>Frameworks layer</strong>: The outermost <code>frameworks</code> directory contains implementations of external interfaces, such as database <strong>object-relational mappers</strong> (<strong>ORMs</strong>) or web frameworks.</p></li><li><p><strong>Straightforward testing</strong>: The <code>tests</code> directory structure mirrors the application structure, allowing for comprehensive testing at all levels.</p></li></ol><p>This structure supports the key benefits of Clean Architecture we discussed:</p><ul><li><p><strong>Maintainability</strong>: Changes to external components (in the <code>frameworks</code> directory) don&#8217;t affect the core business logic in entities and use cases</p></li><li><p><strong>Flexibility</strong>: We can easily swap out the database or web framework in the <code>frameworks</code> directory without touching the business logic</p></li><li><p><strong>Testability</strong>: The clear separation allows for easy unit testing of core components and integration testing of interfaces</p></li></ul><p>Remember our discussion about abstractions? The <code>interfaces</code> directory is where we&#8217;d implement the ABCs or protocols we talked about. For instance, <code>user_repository.py</code> might define an abstract <code>UserRepository</code> class, which is then implemented concretely in the <code>frameworks/database/orm.py</code> file.</p><p>This structure also facilitates the <em>master plan</em> we mentioned earlier. It provides a clear roadmap for where new code should be placed, helping developers make consistent decisions even as the project grows and evolves.</p><p>By organizing our Python project this way, we&#8217;re setting ourselves up for long-term success, creating a codebase that&#8217;s not just functional but also maintainable, flexible, and aligned with Clean Architecture principles.</p><h2>Python-specific considerations and potential pitfalls</h2><p>While Clean Architecture and Python are highly compatible, there are some important considerations to be aware of when implementing these principles in Python projects. Throughout this book, we&#8217;ll guide you through mitigating these concerns, providing practical solutions and best practices.</p><h3>Balancing Pythonic code with architectural principles</h3><p>Python&#8217;s <em>batteries included</em> philosophy and an extensive standard library can sometimes tempt developers to bypass architectural boundaries for the sake of convenience. However, maintaining a clean architecture often involves creating abstractions around even standard library functions to maintain SoC. For example, instead of directly using Python&#8217;s <code>smtplib</code> library in your use cases, consider creating an abstraction layer for sending notifications.</p><p>As we progress through this book, we&#8217;ll demonstrate how this effort of creating abstractions pays off in terms of maintainability, flexibility, and testability. You&#8217;ll see that the initial investment in Clean Architecture principles yields significant long-term benefits.</p><p>Python&#8217;s ease of importing can sometimes lead to messy dependency structures, as all packages are effectively public. We&#8217;ll show you how to be vigilant about maintaining the Dependency Rule, ensuring that inner layers don&#8217;t depend on outer layers. In <em><a href="https://subscription.packtpub.com/book/programming/9781836642893/2">Chapter 2</a></em>, we&#8217;ll explore techniques and tools to help you maintain clean dependency structures in your Python projects.</p><h3>Scaling Clean Architecture in Python projects</h3><p>The application of Clean Architecture principles should be tailored to the size and complexity of your Python project.</p><p>For instance, in small projects or quick prototypes, it&#8217;s perfectly fine to have a simple, monolithic architecture. However, even in these cases, building in a thoughtful, modular manner can set the stage for future growth. You might start with a simple structure:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AkGu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5f89c92-294d-4045-bff0-554020b53ec0_315x249.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AkGu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5f89c92-294d-4045-bff0-554020b53ec0_315x249.png 424w, https://substackcdn.com/image/fetch/$s_!AkGu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5f89c92-294d-4045-bff0-554020b53ec0_315x249.png 848w, https://substackcdn.com/image/fetch/$s_!AkGu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5f89c92-294d-4045-bff0-554020b53ec0_315x249.png 1272w, https://substackcdn.com/image/fetch/$s_!AkGu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5f89c92-294d-4045-bff0-554020b53ec0_315x249.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AkGu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5f89c92-294d-4045-bff0-554020b53ec0_315x249.png" width="315" height="249" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d5f89c92-294d-4045-bff0-554020b53ec0_315x249.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:249,&quot;width&quot;:315,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 1.3: Quick prototype Python project&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 1.3: Quick prototype Python project" title="Figure 1.3: Quick prototype Python project" srcset="https://substackcdn.com/image/fetch/$s_!AkGu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5f89c92-294d-4045-bff0-554020b53ec0_315x249.png 424w, https://substackcdn.com/image/fetch/$s_!AkGu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5f89c92-294d-4045-bff0-554020b53ec0_315x249.png 848w, https://substackcdn.com/image/fetch/$s_!AkGu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5f89c92-294d-4045-bff0-554020b53ec0_315x249.png 1272w, https://substackcdn.com/image/fetch/$s_!AkGu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5f89c92-294d-4045-bff0-554020b53ec0_315x249.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 1.3: Quick prototype Python project</em></p><p>In this small project, you can still apply Clean Architecture principles by doing the following:</p><ul><li><p>Keeping business logic in <code>models.py</code> separate from presentation logic in <code>views.py</code></p></li><li><p>Using <strong>dependency injection</strong> (<strong>DI</strong>) to make components more modular and testable</p></li><li><p>Defining clear interfaces between modules</p></li></ul><p>As your project grows, you can gradually evolve toward a more comprehensive Clean Architecture structure. This evolution might involve the following:</p><ol><li><p>Separating core business logic (entities and use cases) into their own modules</p></li><li><p>Introducing interfaces to abstract away framework-specific code</p></li><li><p>Organizing tests to align with the architectural layers</p></li></ol><p>This book takes a hands-on approach, starting with a basic application and a pragmatic application of Clean Architecture principles. As we progress, the complexity of our example application will increase, demonstrating how to evolve the Clean Architecture approach as the codebase grows.</p><p>Clean Architecture is a spectrum, not a binary choice. The patterns we&#8217;ll explore represent a comprehensive implementation designed to showcase Clean Architecture&#8217;s full capabilities, but in practice, you might choose to implement only the patterns that provide clear value for your specific context. A small API might benefit from clean controller patterns without needing full presenter abstractions, while a data processing script might adopt domain entities while skipping interface adapters entirely. You&#8217;ll learn how to apply these principles judiciously, avoiding over-engineering in smaller projects while leveraging the full power of Clean Architecture in larger systems. The key is understanding what each pattern provides so you can make informed decisions about which architectural boundaries matter most for your project.</p><h3>Leveraging Python&#8217;s dynamic nature appropriately</h3><p>While Python&#8217;s dynamic nature is powerful, it can also lead to issues if not used carefully. <em><a href="https://subscription.packtpub.com/book/programming/9781836642893/3">Chapter 3</a></em> is devoted to aspects of Python&#8217;s dynamic nature, including duck typing, the use of type hints, and newer features such as protocols. By the end of this chapter, you&#8217;ll have a solid foundation on how to best leverage these language features to support a Clean Architecture approach, balancing Python&#8217;s flexibility with architectural rigor.</p><h3>Testing considerations</h3><p>This book, as with Clean Architecture itself, strongly promotes the use of tests. Tests are essentially first-class clients of your application code, using the codebase and making assertions on the results. The same architectural considerations that apply to your main codebase also apply to your Python tests.</p><p>We&#8217;ll guide you through writing tests that respect architectural boundaries. You&#8217;ll learn to recognize when your tests are indicating potential issues in your architecture, such as when they require excessive setup or mocking. In the test cases for each chapter&#8217;s code examples and culminating in <em><a href="https://subscription.packtpub.com/book/programming/9781836642893/8">Chapter 8</a></em>, we&#8217;ll explore these concepts in depth, showing you how to use tests not just for verification, but as a tool for maintaining and improving your architecture.</p><p>By being aware of these considerations and potential pitfalls and following the guidance provided throughout this book, you can create Python systems that are both clean and practical, leveraging the strengths of both Clean Architecture and Python. Remember, the key is to apply these principles thoughtfully, always with an eye toward creating maintainable, testable, and flexible Python code.</p><div><hr></div><h1>Summary</h1><p>In this chapter, we introduced Clean Architecture at a high level and its relevance to Python development. We gave you context by exploring the evolution of software architecture, from Waterfall to Agile, highlighting persistent challenges in managing complexity, accommodating change, and maintaining long-term productivity.</p><p>We introduced Clean Architecture&#8217;s core principles:</p><ul><li><p>Separation of concerns (SoC)</p></li><li><p>Independence of external details</p></li><li><p>Testability and maintainability</p></li></ul><p>We examined Clean Architecture&#8217;s general structure, from core entities and use cases to outer layers of interface adapters, frameworks, and drivers, emphasizing how this structure promotes maintainability and flexibility. We discussed the benefits of Clean Architecture, including improved adaptability, enhanced testability, and long-term development agility, and how it complements modern development practices such as Agile and DevOps.</p><p>Furthermore, we explored the natural fit between Clean Architecture and Python, addressing how Python&#8217;s features can be leveraged to implement Clean Architecture effectively. We also highlighted Python-specific considerations and potential pitfalls, emphasizing the need to balance Pythonic code with architectural principles and adapt Clean Architecture to different project sizes.</p><p>In this chapter, we introduced the primary goals of Clean Architecture and explored its natural fit with Python development. We saw how Clean Architecture principles can be implemented using Python&#8217;s features such as ABCs and protocols, providing a foundation for creating maintainable and flexible software systems.</p><p>In the next chapter, we&#8217;ll build upon this foundation by diving into the SOLID principles. These principles, which form the bedrock of Clean Architecture, will be explored in depth with practical Python examples, showing how they contribute to robust and extensible application design.</p><div><hr></div><h1>Further reading</h1><p>To learn more about the topics that were covered in this chapter, take a look at the following resources:</p><ul><li><p><em>Clean Architecture: A Craftsman&#8217;s Guide to Software Structure and Design</em> by Robert C. Martin. This book provides a comprehensive look at Clean Architecture from its originator.</p></li><li><p><em>Domain-Driven Design: Tackling Complexity in the Heart of Software</em> by Eric Evans. While not specific to Clean Architecture, this book provides valuable insights into designing software around business domains.</p></li><li><p><em>Agile Software Development, Principles, Patterns, and Practices</em> by Robert C. Martin. This book covers many of the principles that underpin Clean Architecture in the context of Agile development.</p></li><li><p><em>The Pragmatic Programmer: Your Journey to Mastery</em> by Andrew Hunt and David Thomas. This classic book offers practical advice on software design and development that aligns well with Clean Architecture principles.</p></li></ul><div><hr></div><p>To learn Clean Architecture through a series of real-world, code-centric examples and exercises, optimize system componentization, and significantly reduce maintenance burden and overall complexity, check out <em><strong><a href="https://www.packtpub.com/en-us/product/clean-architecture-with-python-9781836642886">Clean Architecture with Python</a></strong></em> by <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Sam Keen&quot;,&quot;id&quot;:11641009,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/0463b396-9180-4110-872c-fa7218339e5b_98x98.jpeg&quot;,&quot;uuid&quot;:&quot;38f18b45-5075-4dd4-86d5-b2179584f1db&quot;}" data-component-name="MentionToDOM"></span>. The book helps you apply Clean Architecture concepts confidently to new Python projects and legacy code refactoring.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-us/product/clean-architecture-with-python-9781836642886" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NtED!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!NtED!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!NtED!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!NtED!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NtED!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775" width="268" height="330.5824175824176" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:268,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Clean Architecture with Python&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-us/product/clean-architecture-with-python-9781836642886&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Clean Architecture with Python" title="Clean Architecture with Python" srcset="https://substackcdn.com/image/fetch/$s_!NtED!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!NtED!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!NtED!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!NtED!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p>]]></content:encoded></item><item><title><![CDATA[When to Use Object-Oriented Programming]]></title><description><![CDATA[The complete &#8220;Chapter 5: When to Use Object-Oriented Programming&#8221; from the book Python Object-Oriented Programming, Fourth Edition by Steven F. Lott and Dusty Phillips (Packt, July 2021).]]></description><link>https://deepengineering.net/p/when-to-use-object-oriented-programming</link><guid isPermaLink="false">https://deepengineering.net/p/when-to-use-object-oriented-programming</guid><dc:creator><![CDATA[Steven Lott]]></dc:creator><pubDate>Wed, 27 Aug 2025 08:36:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!S24_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0059565-ab59-4421-90dd-de189e63939f_810x1000" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In previous chapters, we've covered many of the defining features of object-oriented programming. We now know some principles and paradigms of object-oriented design, and we've covered the syntax of object-oriented programming in Python.</p><p>Yet, we don't know exactly how and, especially, when to utilize these principles and syntax in practice. In this chapter, we'll discuss some useful applications of the knowledge we've gained, looking at some new topics along the way:</p><ul><li><p>How to recognize objects</p></li><li><p>Data and behaviors, once again</p></li><li><p>Wrapping data behaviors using properties</p></li><li><p>The Don't Repeat Yourself principle and avoiding repetition</p></li></ul><p>In this chapter, we'll also address some alternative designs for our case study problem. We'll look at ways to partition the sample data into training sets and test sets.</p><p>We'll start this chapter with a close look at the nature of objects and their internal state. There are cases when there's no state change, and a class definition isn't desirable.</p><div><hr></div><h1>Treat objects as objects</h1><p>This may seem obvious; you should generally give separate objects in your problem domain a special class in your code. We've seen examples of this in the case studies in previous chapters: first, we identify objects in the problem, and then model their data and behaviors.</p><p>Identifying objects is a very important task in object-oriented analysis and programming. But it isn't always as easy as counting the nouns in short paragraphs that, frankly, the authors have constructed explicitly for that purpose. Remember, objects are things that have both data and behavior. If we are working only with data, we are often better off storing it in a list, set, dictionary, or other Python data structure (which we'll be covering thoroughly in <em>Chapter 7</em>, <em>Python Data Structures</em>). On the other hand, if we are working only with behavior, but no stored data, a simple function is more suitable.</p><p>An object, however, has both data and behavior. Proficient Python programmers use built-in data structures unless (or until) there is an obvious need to define a class. There is no reason to add an extra level of complexity if it doesn't help organize our code. On the other hand, the need is not always self-evident.</p><p>We can often start our Python programs by storing data in a few variables. As the program expands, we will later find that we are passing the same set of related variables to a set of functions. This is the time to think about grouping both variables and functions into a class.</p><p>For example, if we are designing a program to model polygons in two-dimensional space, we might start with each polygon represented as a list of points. The points would be modeled as two tuples (x, y) describing where that point is located. This is all data, stored in a set of nested data structures (specifically, a list of tuples). We can (and often do) start hacking at the command prompt:</p><pre><code><code>&gt;&gt;&gt; square = [(1,1), (1,2), (2,2), (2,1)] </code></code></pre><p>Now, if we want to calculate the distance around the perimeter of the polygon, we need to sum the distances between each point. To do this, we need a function to calculate the distance between two points. Here are two such functions:</p><pre><code><code>&gt;&gt;&gt; from math import hypot
&gt;&gt;&gt; def distance(p_1, p_2):
...     return hypot(p_1[0]-p_2[0], p_1[1]-p_2[1])
&gt;&gt;&gt; def perimeter(polygon):
...     pairs = zip(polygon, polygon[1:]+polygon[:1])
...     return sum(
...         distance(p1, p2) for p1, p2 in pairs
...     )</code></code></pre><p>We can exercise the functions to check our work:</p><pre><code><code>&gt;&gt;&gt; perimeter(square)
4.0</code></code></pre><p>This is a start, but it's not completely descriptive of the problem domain. We can kind of see what a polygon might be. But we need to read the entire batch of code to see how the two functions work together.</p><p>We can add type hints to help clarify the intent behind each function. The result looks like this:</p><pre><code><code>from __future__ import annotations
from math import hypot
from typing import Tuple, List
Point = Tuple[float, float]
def distance(p_1: Point, p_2: Point) -&gt; float:
    return hypot(p_1[0] - p_2[0], p_1[1] - p_2[1])
Polygon = List[Point]
def perimeter(polygon: Polygon) -&gt; float:
    pairs = zip(polygon, polygon[1:] + polygon[:1])
    return sum(distance(p1, p2) for p1, p2 in pairs)</code></code></pre><p>We've added two type definitions, <code>Point</code> and <code>Polygon</code>, to help clarify our intentions. The definition of <code>Point</code> shows how we'll use the built-in <code>tuple</code> class. The definition of <code>Polygon</code> shows how the built-in <code>list</code> class builds on the <code>Point</code> class.</p><p>When writing annotations inside method parameter definitions, we can generally use the type name directly, for example, <code>def method(self, values: list[int]) -&gt; None:</code>. For this to work, we need to use <code>from __future__ import annotations</code>. When defining a new type hint, however, we need to use the names from the <code>typing</code> module. That's why the definition of the new <code>Point</code> type uses <code>typing.Tuple</code> in the expression <code>Tuple[float, float]</code>.</p><p>Now, as object-oriented programmers, we clearly recognize that a <code>polygon</code> class could encapsulate the list of points (data) and the <code>perimeter</code> function (behavior). Further, a <code>Point</code> class, such as we defined in <em>Chapter 2</em>, <em>Objects in Python</em>, might encapsulate the <code>x</code> and <code>y</code> coordinates and the <code>distance</code> method. The question is: is it valuable to do this?</p><p>For the previous code, maybe yes, maybe no. With our recent experience in object-oriented principles, we can write an object-oriented version in record time. Let's compare them as follows:</p><pre><code><code>from math import hypot
from typing import Tuple, List, Optional, Iterable
class Point:
    def __init__(self, x: float, y: float) -&gt; None:
        self.x = x
        self.y = y
    def distance(self, other: "Point") -&gt; float:
        return hypot(self.x - other.x, self.y - other.y)
class Polygon:
    def __init__(self) -&gt; None:
        self.vertices: List[Point] = []
    def add_point(self, point: Point) -&gt; None:
        self.vertices.append((point))
    def perimeter(self) -&gt; float:
        pairs = zip(
            self.vertices, self.vertices[1:] + self.vertices[:1])
        return sum(p1.distance(p2) for p1, p2 in pairs)</code></code></pre><p>There seems to be almost twice as much code here as there was in our earlier version, although we could argue that the <code>add_point</code> method is not strictly necessary. We could also try to insist on using <code>_vertices</code> to discourage the use of the attribute, but the use of leading <code>_</code> variable names doesn't seem to really solve the problem.</p><p>Now, to understand the differences between the two classes a little better, let's compare the two APIs in use. Here's how to calculate the perimeter of a square using the object-oriented code:</p><pre><code><code>&gt;&gt;&gt; square = Polygon()
&gt;&gt;&gt; square.add_point(Point(1,1))
&gt;&gt;&gt; square.add_point(Point(1,2))
&gt;&gt;&gt; square.add_point(Point(2,2))
&gt;&gt;&gt; square.add_point(Point(2,1))
&gt;&gt;&gt; square.perimeter()
4.0</code></code></pre><p>That's fairly succinct and easy to read, you might think, but let's compare it to the function-based code:</p><pre><code><code>&gt;&gt;&gt; square = [(1,1), (1,2), (2,2), (2,1)]
&gt;&gt;&gt; perimeter(square)
4.0</code></code></pre><p>Hmm, maybe the object-oriented API isn't so compact! Our first, hacked-in version, without type hints or class definitions, is the shortest. How do we know what the list of tuples is supposed to represent? How do we remember what kind of object we're supposed to pass into the <code>perimeter</code> function? We needed some documentation to explain how the first set of functions should be used.</p><p>The functions annotated with type hints were quite a bit easier to understand, as were the class definitions. The relationships among the objects are more clearly defined by hints or classes or both.</p><p>Code length is not a good indicator of code complexity. Some programmers get hung up on complicated <em>one-liners</em> that do an incredible amount of work in one line of code. This can be a fun exercise, but the result is often unreadable, even to the original author the following day. Minimizing the amount of code can often make a program easier to read, but do not blindly assume this is the case.</p><p>No one wins at code golf. Minimizing the volume of code is rarely desirable.</p><p>Luckily, this trade-off isn't necessary. We can make the object-oriented <code>Polygon</code> API as easy to use as the functional implementation. All we have to do is alter our <code>Polygon</code> class so that it can be constructed with multiple points.</p><p>Let's give it an initializer that accepts a list of <code>Point</code> objects:</p><pre><code><code>class Polygon_2:
    def __init__(self, vertices: Optional[Iterable[Point]] = None) -&gt; None:
        self.vertices = list(vertices) if vertices else []
    def perimeter(self) -&gt; float:
        pairs = zip(
            self.vertices, self.vertices[1:] + self.vertices[:1])
        return sum(p1.distance(p2) for p1, p2 in pairs)</code></code></pre><p>For the <code>perimeter()</code> method, we've used the <code>zip()</code> function to create pairs of vertices, with items drawn from two lists to create a sequence of pairs. One list provided to <code>zip()</code> is the complete sequence of vertices. The other list of vertices starts from vertex 1 (not 0) and ends with the vertex before 1 (that is, vertex 0). For a triangle, this will make three pairs: <code>(v[0], v[1])</code>, <code>(v[1], v[2])</code>, and <code>(v[2], v[0])</code>. We can then compute the distance between the pairs using <code>Point.distance()</code>. Finally, we sum the sequence of distances. This seems to improve things considerably. We can now use this class like the original hacked-in function definitions:</p><pre><code><code>&gt;&gt;&gt; square = Polygon_2(
... [Point(1,1), Point(1,2), Point(2,2), Point(2,1)]
... )
&gt;&gt;&gt; square.perimeter()
4.0
</code></code></pre><p>It's handy to have the details of the individual method definitions. We've built an API that's close to the original, succinct set of definitions. We've added enough formality to be confident the code is likely to work before we even start putting test cases together.</p><p>Let's take one more step. Let's allow it to accept tuples too, and we can construct the <code>Point</code> objects ourselves, if needed:</p><pre><code><code>Pair = Tuple[float, float]
Point_or_Tuple = Union[Point, Pair]
class Polygon_3:
    def __init__(self, vertices: Optional[Iterable[Point_or_Tuple]] = None) -&gt; None:
        self.vertices: List[Point] = []
        if vertices:
            for point_or_tuple in vertices:
                self.vertices.append(self.make_point(point_or_tuple))
    @staticmethod
    def make_point(item: Point_or_Tuple) -&gt; Point:
        return item if isinstance(item, Point) else Point(*item)</code></code></pre><p>This initializer goes through the list of items (either <code>Point</code> or <code>Tuple[float, float]</code>) and ensures that any non-<code>Point</code> objects are converted to <code>Point</code> instances.</p><p>If you are experimenting with the above code, you should also define these variant class designs by creating subclasses of <code>Polygon</code> and overriding the <code>__init__()</code> method. Extending a class with dramatically different method signatures can raise error flags from <em><strong>mypy</strong></em>.</p><p>For an example this small, there's no clear winner between the object-oriented and more data-oriented versions of this code. They all do the same thing. If we have new functions that accept a polygon argument, such as <code>area(polygon)</code> or <code>point_in_polygon(polygon, x, y)</code>, the benefits of the object-oriented code become increasingly obvious. Likewise, if we add other attributes to the polygon, such as <code>color</code> or <code>texture</code>, it makes more and more sense to encapsulate that data into a single class.</p><p>The distinction is a design decision, but in general, the more important a set of data is, the more likely it is to have multiple functions specific to that data, and the more useful it is to use a class with attributes and methods instead.</p><p>When making this decision, it also pays to consider how the class will be used. If we're only trying to calculate the perimeter of one polygon in the context of a much greater problem, using a function will probably be quickest to code and easier to use <em>one time only</em>. On the other hand, if our program needs to manipulate numerous polygons in a wide variety of ways (calculating the perimeter, area, and intersection with other polygons, moving or scaling them, and so on), we have almost certainly identified a class of related objects. The class definition becomes more important as the number of instances increases.</p><p>Additionally, pay attention to the interaction between objects. Look for inheritance relationships; inheritance is impossible to model elegantly without classes, so make sure to use them. Look for the other types of relationships we discussed in <em>Chapter 1</em>, <em>Object-Oriented Design</em>, association and composition.</p><p>Composition can, technically, be modeled using only data structures &#8211; for example, we can have a list of dictionaries holding tuple values &#8211; but it is sometimes less complicated to create a few classes of objects, especially if there is behavior associated with the data.</p><p>One size does not fit all. The built-in, generic collections and functions work well for a large number of simple cases. A class definition works well for a large number of more complex cases. The boundary is hazy at best.</p><div><hr></div><h1>Adding behaviors to class data with properties</h1><p>Throughout this book, we've focused on the separation of behavior and data. This is very important in object-oriented programming, but we're about to see that, in Python, the distinction is uncannily blurry. Python is very good at blurring distinctions; it doesn't exactly help us to <em>think outside the box</em>. Rather, it teaches us to stop thinking about the box.</p><p>Before we get into the details, let's discuss some bad object-oriented design principles. Many object-oriented developers teach us to never access attributes directly. They insist that we write attribute access like this:</p><pre><code><code>class Color:
    def __init__(self, rgb_value: int, name: str) -&gt; None:
        self._rgb_value = rgb_value
        self._name = name
    def set_name(self, name: str) -&gt; None:
        self._name = name
    def get_name(self) -&gt; str:
        return self._name
    def set_rgb_value(self, rgb_value: int) -&gt; None:
        self._rgb_value = rgb_value
    def get_rgb_value(self) -&gt; int:
        return self._rgb_value</code></code></pre><p>The instance variables are prefixed with an underscore to suggest that they are private (other languages would actually force them to be private). Then, the <code>get</code> and <code>set</code> methods provide access to each variable. This class would be used in practice as follows:</p><pre><code><code>&gt;&gt;&gt; c = Color(0xff0000, "bright red")
&gt;&gt;&gt; c.get_name()
'bright red'
&gt;&gt;&gt; c.set_name("red")
&gt;&gt;&gt; c.get_name()
'red'</code></code></pre><p>The above example is not nearly as readable as the direct access version that Python favors:</p><pre><code><code>class Color_Py:
    def __init__(self, rgb_value: int, name: str) -&gt; None:
        self.rgb_value = rgb_value
        self.name = name</code></code></pre><p>Here's how this class works. It's slightly simpler:</p><pre><code><code>&gt;&gt;&gt; c = Color_Py(0xff0000, "bright red")
&gt;&gt;&gt; c.name
'bright red'
&gt;&gt;&gt; c.name = "red"
&gt;&gt;&gt; c.name
'red'</code></code></pre><p>So, why would anyone insist upon the method-based syntax?</p><p>The idea of setters and getters seems helpful for encapsulating the class definitions. Some Java-based tools can generate all the getters and setters automagically, making them almost invisible. Automating their creation doesn't make them a great idea. The most important historical reason for having getters and setters was to make the separate compilation of binaries work out in a tidy way. Without a need to link separately compiled binaries, this technique doesn't always apply to Python.</p><p>One ongoing justification for getters and setters is that, someday, we may want to add extra code when a value is set or retrieved. For example, we could decide to cache a value to avoid complex computations, or we might want to validate that a given value is a suitable input.</p><p>For example, we could decide to change the <code>set_name()</code> method as follows:</p><pre><code><code>class Color_V:
    def __init__(self, rgb_value: int, name: str) -&gt; None:
        self._rgb_value = rgb_value
        if not name:
            raise ValueError(f"Invalid name {name!r}")
        self._name = name
    def set_name(self, name: str) -&gt; None:
        if not name:
            raise ValueError(f"Invalid name {name!r}")
        self._name = name</code></code></pre><p>If we had written our original code for direct attribute access, and then later changed it to a method like the preceding one, we'd have a problem: anyone who had written code that accessed the attribute directly would now have to change their code to access a method. If they didn't then change the access style from attribute access to a function call, their code would be broken.</p><p>The mantra that we should make all attributes private, accessible through methods, doesn't make much sense in Python. The Python language lacks any real concept of private members! We can see the source; we often say "We're all adults here." What can we do? We can make the syntax distinction between attribute and method less visible.</p><p>Python gives us the <code>property</code> function to make methods that <em>look</em> like attributes. We can therefore write our code to use direct member access, and if we ever unexpectedly need to alter the implementation to do some calculation when getting or setting that attribute's value, we can do so without changing the interface. Let's see how it looks:</p><pre><code><code>class Color_VP:
    def __init__(self, rgb_value: int, name: str) -&gt; None:
        self._rgb_value = rgb_value
        if not name:
            raise ValueError(f"Invalid name {name!r}")
        self._name = name
    def _set_name(self, name: str) -&gt; None:
        if not name:
            raise ValueError(f"Invalid name {name!r}")
        self._name = name
    def _get_name(self) -&gt; str:
        return self._name
    name = property(_get_name, _set_name)</code></code></pre><p>Compared to the earlier class, we first change the <code>name</code> attribute into a (semi-)private <code>_name</code> attribute. Then, we add two more (semi-)private methods to get and set that variable, performing our validation when we set it.</p><p>Finally, we have the <code>property</code> construction at the bottom. This is the Python magic. It creates a new attribute on the <code>Color</code> class called <code>name</code>. It sets this attribute to be a <strong>property</strong>. Under the hood, a <code>property</code> attribute delegates the real work to the two methods we just created. When used in an access context (on the right side of the <code>=</code> or <code>:=</code>), the first function gets the value. When used in an update context (on the left side of <code>=</code> or <code>:=</code>), the second function sets the value.</p><p>This new version of the <code>Color</code> class can be used in exactly the same way as the earlier version, yet it now performs validation when we set the <code>name</code> attribute:</p><pre><code><code>&gt;&gt;&gt; c = Color_VP(0xff0000, "bright red")
&gt;&gt;&gt; c.name
'bright red'
&gt;&gt;&gt; c.name = "red"
&gt;&gt;&gt; c.name
'red'
&gt;&gt;&gt; c.name = ""
Traceback (most recent call last):
  File "&lt;stdin&gt;", line 1, in &lt;module&gt;
  File "setting_name_property.py", line 8, in _set_name
    raise ValueError(f"Invalid name {name!r}")
ValueError: Invalid name ''</code></code></pre><p>So, if we'd previously written code to access the <code>name</code> attribute, and then changed it to use our <code>property</code>-based object, the previous code would still work. If it attempts to set an empty <code>property</code> value, this is behavior we wanted to forbid. Success!</p><p>Bear in mind that, even with the <code>name</code> property, the previous code is not 100% safe. People can still access the <code>_name</code> attribute directly and set it to an empty string if they want to. But if they access a variable we've explicitly marked with an underscore to suggest it is private, they're the ones that have to deal with the consequences, not us. We established a formal contract, and if they elect to break the contract, they own the consequences.</p><h2>Properties in detail</h2><p>Think of the <code>property</code> function as returning an object that proxies any requests to get or set the attribute value through the method names we have specified. The <code>property</code> built-in is like a constructor for such an object, and that object is set as the public-facing member for the given attribute.</p><p>This <code>property</code> constructor can actually accept two additional arguments, a <code>delete</code> function and a docstring for the property. The <code>delete</code> function is rarely supplied in practice, but it can be useful for logging the fact that a value has been deleted, or possibly to veto deleting if we have reason to do so. The docstring is just a string describing what the property does, no different from the docstrings we discussed in <em>Chapter 2</em>, <em>Objects in Python</em>. If we do not supply this parameter, the docstring will instead be copied from the docstring for the first argument: the <code>getter</code> method.</p><p>Here is a silly example that states whenever any of the methods are called:</p><pre><code><code>class NorwegianBlue:
    def __init__(self, name: str) -&gt; None:
        self._name = name
        self._state: str
    def _get_state(self) -&gt; str:
        print(f"Getting {self._name}'s State")
        return self._state
    def _set_state(self, state: str) -&gt; None:
        print(f"Setting {self._name}'s State to {state!r}")
        self._state = state
    def _del_state(self) -&gt; None:
        print(f"{self._name} is pushing up daisies!")
        del self._state
    silly = property(
        _get_state, _set_state, _del_state, 
        "This is a silly property")</code></code></pre><p>Note that the <code>state</code> attribute has a type hint, <code>str</code>, but no initial value. It can be deleted, and only exists for part of the life of a <code>NorwegianBlue</code>. We need to provide a hint to help <em><strong>mypy</strong></em> understand what the type should be. But we don't assign a default value because that's the job of the <code>setter</code> method.</p><p>If we actually use an instance of this class, it does indeed print out the correct strings when we ask it to:</p><pre><code><code>&gt;&gt;&gt; p = NorwegianBlue("Polly")
&gt;&gt;&gt; p.silly = "Pining for the fjords"
Setting Polly's State to 'Pining for the fjords'
&gt;&gt;&gt; p.silly
Getting Polly's State
'Pining for the fjords'
&gt;&gt;&gt; del p.silly
Polly is pushing up daisies!</code></code></pre><p>Further, if we look at the help text for the <code>Silly</code> class (by issuing <code>help(Silly)</code> at the interpreter prompt), it shows us the custom docstring for our <code>silly</code> attribute:</p><pre><code><code>Help on class NorwegianBlue in module colors:
class NorwegianBlue(builtins.object)
 |  NorwegianBlue(name: str) -&gt; None
 |  
 |  Methods defined here:
 |  
 |  __init__(self, name: str) -&gt; None
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  silly
 |      This is a silly property</code></code></pre><p>Once again, everything is working as we planned. In practice, properties are normally only defined with the first two parameters: the <code>getter</code> and <code>setter</code> functions. If we want to supply a docstring for a property, we can define it on the <code>getter</code> function; the property proxy will copy it into its own docstring. The <code>delete</code> function is often left empty because object attributes are so rarely deleted.</p><h2>Decorators &#8211; another way to create properties</h2><p>We can create properties using decorators. This makes the definitions easier to read. Decorators are a ubiquitous feature of Python syntax, with a variety of purposes. For the most part, decorators modify the function definition that they precede. We'll look at the decorator design pattern more broadly in <em>Chapter 11</em>, <em>Common Design Patterns</em>.</p><p>The <code>property</code> function can be used with the decorator syntax to turn a <code>get</code> method into a <code>property</code> attribute, as follows:</p><pre><code><code>class NorwegianBlue_P:
    def __init__(self, name: str) -&gt; None:
        self._name = name
        self._state: str
    @property
    def silly(self) -&gt; str:
        print(f"Getting {self._name}'s State")
        return self._state</code></code></pre><p>This applies the <code>property</code> function as a decorator to the function that follows. It is equivalent to the previous <code>silly = property(_get_state)</code> syntax. The main difference, from a readability perspective, is that we get to mark the <code>silly</code> method as a property at the top of the method, instead of after it is defined, where it can be easily overlooked. It also means we don't have to create private methods with underscore prefixes just to define a property.</p><p>Going one step further, we can specify a <code>setter</code> function for the new property as follows:</p><pre><code><code>class NorwegianBlue_P:
    def __init__(self, name: str) -&gt; None:
        self._name = name
        self._state: str
    @property
    def silly(self) -&gt; str:
        """This is a silly property"""
        print(f"Getting {self._name}'s State")
        return self._state
    @silly.setter
    def silly(self, state: str) -&gt; None:
        print(f"Setting {self._name}'s State to {state!r}")
        self._state = state</code></code></pre><p>This syntax, <code>@silly.setter</code>, looks odd compared with <code>@property</code>, although the intent should be clear. First, we decorate the <code>silly</code> method as a getter. Then, we decorate a second method with exactly the same name by applying the <code>setter</code> attribute of the originally decorated <code>silly</code> method! This works because the <code>property</code> function returns an object; this object also has its own <code>setter</code> attribute, which can then be applied as a decorator to other methods. Using the same name for the get and set methods helps to group together the multiple methods that access one common attribute.</p><p>We can also specify a <code>delete</code> function with <code>@silly.deleter</code>. Here's what it looks like:</p><pre><code><code>@silly.deleter
def silly(self) -&gt; None:
    print(f"{self._name} is pushing up daisies!")
    del self._state</code></code></pre><p>We cannot specify a docstring using <code>property</code> decorators, so we need to rely on the decorator copying the docstring from the initial getter method. This class operates <em>exactly</em> the same as our earlier version, including the help text. You'll see the decorator syntax in widespread use. The function syntax is how it actually works under the hood.</p><h2>Deciding when to use properties</h2><p>With the built-in <code>property</code> blurring the division between behavior and data, it can be confusing to know when to choose an attribute, or a method, or a property. In the <code>Color_VP</code> class example we saw earlier, we added argument value validation to setting an attribute. In the <code>NorwegianBlue</code> class example, we wrote detailed log entries when attributes were set and deleted. There are also other factors to take into account when deciding to use a property.</p><p>In Python, data, properties, and methods are all attributes of a class. The fact that a method is callable does not distinguish it from other types of attributes; indeed, we'll see in <em>Chapter 8</em>, <em>The Intersection of Object-Oriented and Functional Programming</em>, that it is possible to create normal objects that can be called like functions. We'll also discover that functions and methods are themselves ordinary objects.</p><p>The fact that methods are callable attributes, and properties are also attributes, can help us make this decision. We suggest the following principles:</p><ul><li><p>Use methods to represent actions; things that can be done to, or performed by, the object. When you call a method, even with only one argument, it should <em>do</em> something. Method names are generally verbs.</p></li><li><p>Use attributes or properties to represent the state of the object. These are the nouns, adjectives, and prepositions that describe an object.</p><ul><li><p>Default to ordinary (non-property) attributes, initialized in the <code>__init__()</code> method. These must be computed eagerly, which is a good starting point for any design.</p></li><li><p>Use properties for attributes in the exceptional case when there's a computation involved with setting or getting (or deleting) an attribute. Examples include data validation, logging, and access controls. We'll look at cache management in a moment. We can also use properties for lazy attributes, where we want to defer the computation because it's costly and rarely needed.</p></li></ul></li></ul><p>Let's look at a more realistic example. A common need for custom behavior is caching a value that is difficult to calculate or expensive to look up (requiring, for example, a network request or database query). The goal is to store the value locally to avoid repeated calls to the expensive calculation.</p><p>We can do this with a custom getter on the property. The first time the value is retrieved, we perform the lookup or calculation. Then, we can locally cache the value as a private attribute on our object (or in dedicated caching software), and the next time the value is requested, we return the stored data. Here's how we might cache a web page:</p><pre><code><code>from urllib.request import urlopen
from typing import Optional, cast
class WebPage:
    def __init__(self, url: str) -&gt; None:
        self.url = url
        self._content: Optional[bytes] = None
    @property
    def content(self) -&gt; bytes:
        if self._content is None:
            print("Retrieving New Page...")
            with urlopen(self.url) as response:
                self._content = response.read()
        return self._content</code></code></pre><p>We'll only read the website content once, when <code>self._content</code> has the initial value of <code>None</code>. After that, we'll return the value most recently read for the site. We can test this code to see that the page is only retrieved once:</p><pre><code><code>import time
webpage = WebPage("http://ccphillips.net/")
now = time.perf_counter()
content1 = webpage.content
first_fetch = time.perf_counter() - now
now = time.perf_counter()
content2 = webpage.content
second_fetch = time.perf_counter() - now
assert content2 == content1, "Problem: Pages were different"
print(f"Initial Request     {first_fetch:.5f}")
print(f"Subsequent Requests {second_fetch:.5f}")</code></code></pre><p>The output?</p><pre><code><code>% python src/colors.py
Retrieving New Page...
Initial Request     1.38836
Subsequent Requests 0.00001</code></code></pre><p>It took about 1.388 seconds to retrieve a page from the <code>ccphilips.net</code> web host. The second fetch &#8211; from a laptop's RAM &#8211; takes 0.01 milliseconds! This is sometimes written as 10 &#956;s, 10 microseconds. Since this is the last digit, we can suspect it's subject to rounding, and the time may be only half that, perhaps as little as 5 &#956;s.</p><p>Custom getters are also useful for attributes that need to be calculated on the fly, based on other object attributes. For example, we might want to calculate the average for a list of integers:</p><pre><code><code>class AverageList(List[int]):
    @property
    def average(self) -&gt; float:
        return sum(self) / len(self)</code></code></pre><p>This small class inherits from <code>list</code>, so we get list-like behavior for free. We added a property to the class, and &#8211; hey, presto! &#8211; our list can have an average as follows:</p><pre><code><code>&gt;&gt;&gt; a = AverageList([10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5])
&gt;&gt;&gt; a.average
9.0</code></code></pre><p>Of course, we could have made this a method instead, but if we do, then we ought to call it <code>calculate_average()</code>, since methods represent actions. But a property called <code>average</code> is more suitable, and is both easier to type and easier to read.</p><p>We can imagine a number of similar reductions, including minimum, maximum, standard deviation, median, and mode, all being properties of a collection of numbers. This can simplify a more complex analysis by encapsulating these summaries into the collection of data values.</p><p>Custom setters are useful for validation, as we've already seen, but they can also be used to proxy a value to another location. For example, we could add a content setter to the <code>WebPage</code> class that automatically logs into our web server and uploads a new page whenever the value is set.</p><div><hr></div><h1>Manager objects</h1><p>We've been focused on objects and their attributes and methods. Now, we'll take a look at designing higher-level objects; the kind of objects that manage other objects &#8211; the objects that tie everything together. These are sometimes called Fa&#231;ade objects because they present a pleasant, easy-to-use fa&#231;ade over some underlying complexity. See <em>Chapter 12</em>, <em>Advanced Design Patterns</em>, for an additional look at the Fa&#231;ade design pattern.</p><p>Most of the previous examples tend to model concrete ideas. Management objects are more like office managers; they don't do the actual visible work out on the floor, but without them, there would be no communication between departments, and nobody would know what they are supposed to do (although, this can be true anyway if the organization is badly managed!). Analogously, the attributes on a management class tend to refer to other objects that do the visible work; the behaviors on such a class delegate to those other classes at the right time, and pass messages between them.</p><p>A manager relies on composite design. We assemble a manager class by knitting other objects together. The overall behavior of the manager emerges from the interaction of objects. To an extent, a manager is also an Adapter among the various interfaces. See <em>Chapter 12</em>, <em>Advanced Design Patterns</em>, for an additional look at the Adapter design pattern.</p><p>As an example, we'll write a program that does a find-and-replace action for text files stored in a compressed archive file, either a ZIP archive or a TAR archive. We'll need objects to represent the archive file as a whole and each individual text file (luckily, we don't have to write these classes, as they're available in the Python standard library).</p><p>An overall manager object will be responsible for ensuring the following three steps occur:</p><ol><li><p>Unzipping the compressed file to examine each member</p></li><li><p>Performing the find-and-replace action on text members</p></li><li><p>Zipping up the new files with the untouched as well as changed members</p></li></ol><p>Note that we have to choose between an eager and a lazy approach to the three steps of this process. We can eagerly unzip (or untar) the entire archive, process all the files, and then build a new archive. This tends to use a lot of disk space. An alternative is to lazily extract items one at a time from the archive, perform the find-and-replace, and then build a new compressed archive as we go. The lazy approach doesn't require as much storage.</p><p>This design will knit together elements of the <code>pathlib</code>, <code>zipfile</code>, and the regular expression (<code>re</code>) module. The initial design will be focused on the job at hand. Later in this chapter, we'll rethink this design as new requirements surface.</p><p>The class is initialized with the archive file's name. We don't do anything else upon creation. We'll define a method with a good, clear verb in its name that does any processing:</p><pre><code><code>from __future__ import annotations
import fnmatch
from pathlib import Path
import re
import zipfile
class ZipReplace:
    def __init__(
            self,
            archive: Path,
            pattern: str,
            find: str,
            replace: str
    ) -&gt; None:
        self.archive_path = archive
        self.pattern = pattern
        self.find = find
        self.replace = replace</code></code></pre><p>Given the archive, the filename pattern to match, and the strings to work with, the object will have everything it needs. We might provide arguments like <code>ZipReplace(Path("sample.zip"), "*.md", "xyzzy", "xyzzy")</code>.</p><p>The overall manager method for the find-and-replace operation revises a given archive. This method of the <code>ZipReplace</code> class (started above) uses two other methods and delegates most of the real work to other objects:</p><pre><code><code>    def find_and_replace(self) -&gt; None:
        input_path, output_path = self.make_backup()
        with zipfile.ZipFile(output_path, "w") as output:
            with zipfile.ZipFile(input_path) as input:
                self.copy_and_transform(input, output)</code></code></pre><p>The <code>make_backup()</code> method will use the <code>pathlib</code> module to rename the old ZIP file so it's obviously the backup copy, untouched. This backup copy is input to the <code>copy_and_transform()</code> method. The original name will be the final output, also. This makes it look like the file was updated "in place." In fact, a new file was created, but the old name will be assigned to the new content.</p><p>We create two context managers (a special kind of manager) to control the open files. An open file is entangled with operating system resources. In the case of a ZIP file or TAR archive, there are summaries and checksums that need to be properly written when the file is closed. Using a context manager assures that this additional work is done, and done properly, in spite of any exceptions being raised. All file operations should be wrapped in a <code>with</code> statement to leverage Python's context manager and handle proper cleanup. We'll look at this again in <em>Chapter 9</em>, <em>Strings, Serialization, and File Paths</em>.</p><p>The <code>copy_and_transform()</code> method uses methods of the two <code>ZipFile</code> instances and the <code>re</code> module to transform members of the original file. Since a backup was made of the original file, this will build the output file from the backup file. It examines each member of the archive, performing a number of steps, including expanding the compressed data, doing the transformation with a <code>transform()</code> method, and compressing to write to the output file, and then cleaning up the temporary file (and directories).</p><p>Obviously, we could do all of these steps in one method of a class, or indeed do the whole thing in one complex script, without ever creating an object. There are several advantages to separating the steps:</p><ul><li><p><strong>Readability</strong>: The code for each step is in a self-contained unit that is easy to read and understand. The method name describes what the method does, and less additional documentation is required to understand what is going on.</p></li><li><p><strong>Extensibility</strong>: If a subclass wanted to use compressed TAR files instead of ZIP files, it could override the <code>copy_and_transform()</code> method, reusing all the supporting methods because they apply to any file irrespective of the kind of archive.</p></li><li><p><strong>Partitioning</strong>: An external class could create an instance of this class and use <code>make_backup()</code> or the <code>copy_and_transform()</code> methods directly, bypassing the <code>find_and_replace()</code> manager.</p></li></ul><p>These two methods of the <code>ZipReplace</code> class (started above) make the backup copy and create the new file by reading from the backup and writing new items after they've been modified:</p><pre><code><code>def make_backup(self) -&gt; tuple[Path, Path]:
    input_path = self.archive_path.with_suffix(
        f"{self.archive_path.suffix}.old")
    output_path = self.archive_path
    self.archive_path.rename(input_path)
    return input_path, output_path
def copy_and_transform(
    self, input: zipfile.ZipFile, output: zipfile.ZipFile
) -&gt; None:
    for item in input.infolist():
        extracted = Path(input.extract(item))
        if (not item.is_dir() 
                and fnmatch.fnmatch(item.filename, self.pattern)):
            print(f"Transform {item}")
            input_text = extracted.read_text()
            output_text = re.sub(self.find, self.replace, input_text)
            extracted.write_text(output_text)
        else:
            print(f"Ignore    {item}")
        output.write(extracted, item.filename)
        extracted.unlink()
        for parent in extracted.parents:
            if parent == Path.cwd():
                break
            parent.rmdir()</code></code></pre><p>The <code>make_backup()</code> method applies a common strategy to avoid damaging a file. The original file is renamed to preserve it, and a new file is created that will have the original file's name. This method is designed to be independent of the file type or other processing details.</p><p>The <code>copy_and_transform()</code> function method builds the new archive out of members extracted from the original archive. It performs a number of steps for each member of the archive:</p><ul><li><p>Extract this file from the original archive.</p></li><li><p>If the item is not a directory (this is unlikely, but still possible), and the name matches the wild-card pattern, we want to transform it. This is a three-step process.</p><ol><li><p>Read the file's text.</p></li><li><p>Transform the file, using the <code>sub()</code> function of the <code>re</code> module.</p></li><li><p>Write the text, replacing the extracted file. This is where we create a copy of the content.</p></li></ol></li><li><p>Compress the file &#8211; either an untouched file or a transformed file &#8211; into the new archive.</p></li><li><p>We unlink the temporary copy. If there are no links left to the file, it will be deleted by the operating system.</p></li><li><p>We clean up any temporary directories created by the extraction process.</p></li><li><p>The <code>copy_and_transform()</code> method's operations span the <code>pathlib</code>, <code>zipfile</code>, and <code>re</code> modules. Wrapping these operations up into a manager that uses context managers gives us a tidy package with a small interface.</p></li></ul><p>We can create a main script to use the <code>ZipReplace</code> class:</p><pre><code><code>if __name__ == "__main__":
    sample_zip = Path("sample.zip")
    zr = ZipReplace(sample_zip, "*.md", "xyzzy", "plover's egg")
    zr.find_and_replace()</code></code></pre><p>We've provided the archive (<code>sample.zip</code>), the file matching pattern (<code>*.md</code>), the string to replace (<code>xyzzy</code>), and the final replacement (<code>plover's egg</code>). This will perform a complex series of file operations. A more practical approach is to use the <code>argparse</code> module to define the <strong>command-line interface</strong> (<strong>CLI</strong>) for this application.</p><p>For brevity, the details are sparsely documented. Our current focus is on object-oriented design; if you are interested in the inner details of the <code>zipfile</code> module, refer to the documentation in the standard library, either online or by typing <code>import zipfile</code> and <code>help(zipfile)</code> into your interactive interpreter.</p><p>Of course, an instance of the <code>ZipReplace</code> class does not have to be created from the command line; our class could be imported into another module (to perform batch ZIP file processing), or accessed as part of a GUI interface or even a higher-level manager object that knows where to get ZIP files (for example, to retrieve them from an FTP server or back them up to an external disk).</p><p>The benefit of the Fa&#231;ade and Adapter design patterns is to encapsulate complexity into a more useful class design. These composite objects tend to be less like physical objects, and enter the realm of conceptual objects. When we step away from objects that have a close parallel with the real world, the methods are actions that change the state of those concepts; care is required because the simple analogies start to disappear in the haze of ideas. It helps when the foundation is a set of concrete data values and well-defined behaviors.</p><p>A good example to bear in mind is the World Wide Web. A <em>web server</em> provides <em>content</em> to <em>browsers</em>. The content can include JavaScript that behaves like a desktop application which reaches out to other web servers to present content. These conceptual relationships are implemented by tangible transfers of bytes. It also includes a browser to paint pages of text, images, video, or sound. The foundation is transfers of bytes, a tangible action. In a classroom setting, it's possible to have developers pass sticky notes and rubber balls to each other to represent requests and responses.</p><p>This example works nicely. When we're confronted with additional requirements, we need to find a way to build new, related features without duplicating code. We'll talk about this engineering imperative first, then look at the revised design.</p><h2>Removing duplicate code</h2><p>Often, the code in management-style classes such as <code>ZipReplace</code> is quite generic and can be applied in a variety of ways. It is possible to use either composition or inheritance to help keep this code in one place, thus eliminating duplicate code. Before we look at any examples of this, let's discuss some design principles. Specifically, why is duplicate code a bad thing?</p><p>There are several reasons, but they all boil down to readability and maintainability. When we're writing a new piece of code that is similar to an earlier piece, the easiest thing to do is copy and paste the old code and change whatever needs to be changed (variable names, logic, comments) to make it work in the new location. Alternatively, if we're writing new code that seems similar, but not identical, to code elsewhere in the project, it is often easier to write fresh code with similar behavior, rather than figuring out how to extract the overlapping functionality. We sometimes call this copy-pasta programming because the result is a big mass of tangled noodles of code, like a bowl of spaghetti.</p><p>But as soon as someone trying to understand the code comes across duplicate (or nearly duplicate) code blocks, they now have an <em>additional</em> barrier to understanding. There's an intellectual friction created by a number of side-bar questions. Are they truly identical? If not, how is one section different from the other? What parts are the same? Under what conditions is one section called? When do we call the other? You might argue that you're the only one reading your code, but if you don't touch that code for eight months, it will be as incomprehensible to you as it is to a fresh coder. When we're trying to read two similar pieces of code, we have to understand why they're different, as well as how they're different. This wastes the reader's time; code should always be written to be readable first.</p><blockquote><p><em>[Dusty here, stepping out of formal author mode] I once had to try to understand someone's code that had three identical copies of the same 300 lines of very poorly written code. I had been working with the code for a month before I finally comprehended that the three identical versions were actually performing slightly different tax calculations. Some of the subtle differences were intentional, but there were also obvious areas where someone had updated a calculation in one function without updating the other two. The number of subtle, incomprehensible bugs in the code could not be counted. I eventually replaced all 900 lines with an easy-to-read function of 20 lines or so.</em></p></blockquote><p>As the preceding story suggests, keeping two similar pieces of code up to date can be a nightmare. We have to remember to update both sections whenever we update one of them, and we have to remember how multiple sections differ so we can modify our changes when we are editing each of them. If we forget to update all sections, we will end up with extremely annoying bugs that usually manifest themselves as: "<em>But I fixed that already, why is it still happening?</em>"</p><p>The key factor here is the time spent in troubleshooting, maintenance, and enhancement compared with the time spent initially creating the code. Software that's in use for more than a few weeks will have a lot more eyeballs on it than the time spent creating it. The tiny bit of time we "save" by copying and pasting existing code is more than wasted when we have to maintain it.</p><p>One of the author's personal bests was an application that was in use for almost seventeen years. If other developers and users wasted one extra day each year trying to sort out some confusing part of the code, it means the author should have spent at least two more weeks improving the code to head off this future maintenance cost.</p><p>Code is both read and modified many more times and much more often than it is written. Comprehensible code should always be a priority.</p><p>This is why programmers, especially Python programmers (who tend to value elegant code more than average developers), follow what is known as the <strong>Don't Repeat Yourself</strong> (<strong>DRY</strong>) principle. Our advice for beginner programmers is to never use the copy-and-paste feature of their editor. To intermediate programmers: think thrice before hitting Ctrl + C.</p><p>But what should we do instead of code duplication? The simplest solution is often to move the code into a function that accepts parameters to account for whatever parts are different. This isn't a strictly object-oriented solution, but it is frequently optimal.</p><p>For example, if we have two pieces of code that unzip a ZIP file into two different directories, we can easily replace it with a function that accepts a parameter for the directory to which it should be unzipped. This may make the function itself slightly longer. The size of a function &#8211; measured as lines of code &#8211; isn't a good metric for readability. No one wins at code golf.</p><p>Good names and docstrings are essential. Each class, method, function, variable, property, attribute, module, and package name should be chosen thoughtfully. When writing docstrings, don't explain how the code works (the code should do that). Be sure to focus on what the code's purpose is, what the preconditions are for using it, and what will be true after the function or method has been used.</p><p>The moral of the story is: always make the effort to refactor your code to be easier to read, instead of writing bad code that may seem easier to write. Now we can look at the revised design to the <code>ZipReplace</code> class definition.</p><h2>In practice</h2><p>Let's explore two ways we can reuse our existing code. After writing our code to replace strings in a ZIP file full of text files, we are later contracted to scale all the images in a ZIP file to a size suitable for mobile devices. While resolutions vary, 640 x 960 is about the smallest we need. It looks like we could use a very similar paradigm to what we used in <code>ZipReplace</code>.</p><p>Our first impulse might be to save a copy of that module and change the <code>find_replace</code> method to <code>scale_image</code> or something similar in the copy.</p><p>This processing will rely on the Pillow library to open an image file, scale it, and save it. The Pillow image processing tools can be installed with the following command:</p><pre><code><code>% python -m pip install pillow</code></code></pre><p>This will provide some great image-processing tools.</p><p>As we noted above in the <em>Removing duplicate code</em> section of this chapter, this copy-and-paste programming approach is suboptimal. What if someday we want to change the <code>unzip</code> and <code>zip</code> methods to also open TAR files? Or maybe we'll want to use a guaranteed unique directory name for temporary files. In either case, we'd have to change it in two different places!</p><p>We'll start by demonstrating an inheritance-based solution to this problem. First, we'll modify our original <code>ZipReplace</code> class into a superclass for processing ZIP files in a variety of ways:</p><pre><code><code>from abc import ABC, abstractmethod
class ZipProcessor(ABC):
    def __init__(self, archive: Path) -&gt; None:
        self.archive_path = archive
        self._pattern: str
    def process_files(self, pattern: str) -&gt; None:
        self._pattern = pattern
        input_path, output_path = self.make_backup()
        with zipfile.ZipFile(output_path, "w") as output:
            with zipfile.ZipFile(input_path) as input:
                self.copy_and_transform(input, output)
    def make_backup(self) -&gt; tuple[Path, Path]:
        input_path = self.archive_path.with_suffix(
            f"{self.archive_path.suffix}.old")
        output_path = self.archive_path
        self.archive_path.rename(input_path)
        return input_path, output_path
    def copy_and_transform(
        self, input: zipfile.ZipFile, output: zipfile.ZipFile
    ) -&gt; None:
        for item in input.infolist():
            extracted = Path(input.extract(item))
            if self.matches(item):
                print(f"Transform {item}")
                self.transform(extracted)
            else:
                print(f"Ignore    {item}")
            output.write(extracted, item.filename)
            self.remove_under_cwd(extracted)
    def matches(self, item: zipfile.ZipInfo) -&gt; bool:
        return (
            not item.is_dir() 
            and fnmatch.fnmatch(item.filename, self._pattern))
    def remove_under_cwd(self, extracted: Path) -&gt; None:
        extracted.unlink()
        for parent in extracted.parents:
            if parent == Path.cwd():
                break
            parent.rmdir()
    @abstractmethod
    def transform(self, extracted: Path) -&gt; None:
        ...</code></code></pre><p>We dropped the three parameters to <code>__init__()</code>, <code>pattern</code>, <code>find</code>, and <code>replace</code>, that were specific to <code>ZipReplace</code>. Then, we renamed the <code>find_replace()</code> method to <code>process_files()</code>. We decomposed the complex <code>copy_and_transform()</code> method and made it call several other methods to do the real work. This includes a placeholder for a <code>transform()</code> method. These name changes help demonstrate the more generalized nature of our new class.</p><p>This new <code>ZipProcessor</code> class is a subclass of <code>ABC</code>, an abstract base class, allowing us to provide placeholders instead of methods. (More on ABCs to come in <em>Chapter 6</em>, <em>Abstract Base Classes and Operator Overloading</em>.) This abstract class doesn't actually define a <code>transform()</code> method. If we try to create an instance of the <code>ZipProcessor</code> class, the missing <code>transform()</code> method will raise an exception. The formality of an <code>@abstractmethod</code> decoration makes it clear that there's a piece missing, and the piece must have the expected shape.</p><p>Now, before we move on to our image processing application, let's create a version of our original <code>ZipReplace</code> class. This will be based on the <code>ZipProcessor</code> class to make use of this parent class, as follows:</p><pre><code><code>class TextTweaker(ZipProcessor):
    def __init__(self, archive: Path) -&gt; None:
        super().__init__(archive)
        self.find: str
        self.replace: str
    def find_and_replace(self, find: str, replace: str) -&gt; "TextTweaker":
        self.find = find
        self.replace = replace
        return self
    def transform(self, extracted: Path) -&gt; None:
        input_text = extracted.read_text()
        output_text = re.sub(self.find, self.replace, input_text)
        extracted.write_text(output_text)</code></code></pre><p>This code is shorter than the original version, since it inherits its ZIP processing abilities from the parent class. We first import the base class we just wrote and make <code>TextTweaker</code> extend that class. Then, we use <code>super()</code> to initialize the parent class.</p><p>We need two extra parameters, and we've used a technique called a <em>fluent interface</em> to provide the two parameters. The <code>find_and_replace()</code> method updates the state of the object, then returns the <code>self</code> object. This lets us use the class with a line of code like the following:</p><pre><code><code>TextTweaker(zip_data)\
.find_and_replace("xyzzy", "plover's egg")\
.process_files("*.md")</code></code></pre><p>We've created an instance of the class, used the <code>find_and_replace()</code> method to set some of the attributes, then used the <code>process_files()</code> method to start the processing. This is called a "fluent" interface because a number of methods are used to help clarify the parameters and their relationships.</p><p>We've done a fair amount of work to recreate a program that is functionally not different from the one we started with! But having done that work, it is now much easier for us to write other classes that operate on files in a ZIP archive, such as the (hypothetically requested) photo scaler.</p><p>Further, if we ever want to improve or bug fix the ZIP functionality, we can do it for all subclasses at once by changing only the one <code>ZipProcessor</code> base class. Therefore maintenance will be much more effective.</p><p>See how simple it is now to create a photo scaling class that takes advantage of the <code>ZipProcessor</code> functionality:</p><pre><code><code>from PIL import Image  # type: ignore [import]
class ImgTweaker(ZipProcessor):
    def transform(self, extracted: Path) -&gt; None:
        image = Image.open(extracted)
        scaled = image.resize(size=(640, 960))
        scaled.save(extracted)</code></code></pre><p>Look how simple this class is! All that work we did earlier paid off. All we do is open each file, scale it, and save it back. The <code>ZipProcessor</code> class takes care of the zipping and unzipping without any extra work on our part. This seems to be a huge win.</p><p>Creating reusable code isn't easy. It generally requires more than one use case to make it clear what parts are generic and what parts are specific. Because we need concrete examples, it pays to avoid over-engineering to strive for imagined reuse. This is Python and things can be very flexible. Rewrite as needed to cover the cases as they arrive on the scene.</p><div><hr></div><h1>Case study</h1><p>In this chapter, we'll continue developing elements of the case study. We want to explore some additional features of object-oriented design in Python. The first is what is sometimes called "syntactic sugar," a handy way to write something that offers a simpler way to express something fairly complex. The second is the concept of a manager for providing a context for resource management.</p><p>In <em>Chapter 4</em>, <em>Expecting the Unexpected</em>, we built an exception for identifying invalid input data. We used the exception for reporting when the inputs couldn't be used.</p><p>Here, we'll start with a class to gather data by reading the file with properly classified training and test data. In this chapter, we'll ignore some of the exception-handling details so we can focus on another aspect of the problem: partitioning samples into testing and training subsets.</p><h2>Input validation</h2><p>The <code>TrainingData</code> object is loaded from a source file of samples, named <code>bezdekIris.data</code>. Currently, we don't make a large effort to validate the contents of this file. Rather than confirm the data contains correctly formatted samples with numeric measurements and a proper species name, we simply create <code>Sample</code> instances, and hope nothing goes wrong. A small change to the data could lead to unexpected problems in obscure parts of our application. By validating the input data right away, we can focus on the problems and provide a focused, actionable report back to the user. Something like "Row 42 has an invalid petal_length value of '1b.25'" with the line of data, the column, and the invalid value.</p><p>A file with training data is processed in our application via the <code>load()</code> method of <code>TrainingData</code>. Currently, this method requires an iterable sequence of dictionaries; each individual sample is read as a dictionary with the measurements and the classification. The type hint is <code>Iterable[dict[str, str]]</code>. This is one way the <code>csv</code> module works, making it very easy to work with. We'll return to additional details of loading the data in <em>Chapter 8</em>, <em>The Intersection of Object-Oriented and Functional Programming</em>, and <em>Chapter 9</em>, <em>Strings, Serialization, and File Paths</em>.</p><p>Thinking about the possibility of alternative formats suggests the <code>TrainingData</code> class should not depend on the <code>dict[str, str]</code> row definition suggested by CSV file processing. While expecting a dictionary of values for each row is simple, it pushes some details into the <code>TrainingData</code> class that may not belong there. Details of the source document's representation have nothing to do with managing a collection of training and test samples; this seems like a place where object-oriented design will help us disentangle the two ideas.</p><p>In order to support multiple sources of data, we will need some common rules for validating the input values. We'll need a class like this:</p><pre><code><code>class SampleReader:
    """
    See iris.names for attribute ordering in bezdekIris.data file
    """
    target_class = Sample
    header = [
        "sepal_length", "sepal_width", 
        "petal_length", "petal_width", "class"
    ]
    def __init__(self, source: Path) -&gt; None:
        self.source = source
    def sample_iter(self) -&gt; Iterator[Sample]:
        target_class = self.target_class
        with self.source.open() as source_file:
            reader = csv.DictReader(source_file, self.header)
            for row in reader:
                try:
                    sample = target_class(
                        sepal_length=float(row["sepal_length"]),
                        sepal_width=float(row["sepal_width"]),
                        petal_length=float(row["petal_length"]),
                        petal_width=float(row["petal_width"]),
                    )
                except ValueError as ex:
                    raise BadSampleRow(f"Invalid {row!r}") from ex
                yield sample</code></code></pre><p>This builds an instance of the <code>Sample</code> superclass from the input fields read by a CSV <code>DictReader</code> instance. The <code>sample_iter()</code> method uses a series of conversion expressions to translate input data from each column into useful Python objects. In this example, the conversions are simple, and the implementation is a bunch of <code>float()</code> functions to convert CSV string data into Python objects. We can imagine more complex conversions might be present for other problem domains.</p><p>The <code>float()</code> functions &#8211; when confronted with bad data &#8211; will raise a <code>ValueError</code>. While this is helpful, a bug in a distance formula may also raise a <code>ValueError</code>, leading to possible confusion. It's slightly better for our application to produce unique exceptions; this makes it easier to identify a root cause for a problem.</p><p>The target type, <code>Sample</code>, is provided as a class-level variable, <code>target_class</code>. This lets us introduce a new subclass of <code>Sample</code> by making one relatively visible change. This isn't required, but a visible dependency like this provides a way to disentangle classes from each other.</p><p>We'll follow <em>Chapter 4</em>, <em>Expecting the Unexpected</em>, and define a unique exception definition. This is a better way to help disentangle our application's errors from ordinary bugs in our Python code:</p><pre><code><code>class BadSampleRow(ValueError):
    pass</code></code></pre><p>To make use of this, we mapped the various <code>float()</code> problems signaled by <code>ValueError</code> exceptions to our application's <code>BadSampleRow</code> exception. This can help someone distinguish between a bad CSV source file and a bad computation due to a bug in a <em>k</em>-NN distance computation. While both can raise <code>ValueError</code> exceptions, the CSV processing exception is wrapped into an application-specific exception to disambiguate the context.</p><p>We've done the exception transform by wrapping the creation of an instance of the target class in a <code>try:</code> statement. Any <code>ValueError</code> that's raised here will become a <code>BadSampleRow</code> exception. We've used a <code>raise...from...</code> so that the original exception is preserved to help with the debugging.</p><p>Once we have valid input, we have to decide whether the object should be used for training or testing. We'll turn to that problem next.</p><h2>Input partitioning</h2><p>The <code>SampleReader</code> class we just introduced uses a variable to identify what kind of objects to create. The <code>target_class</code> variable provides a class to use. Note that we need to be a little careful in the ways we refer to <code>SampleReader.target_class</code> or <code>self.target_class</code>.</p><p>A simple expression like <code>self.target_class(sepal_length=, ... etc.)</code> looks like a method evaluation. Except, of course, <code>self.target_class</code> is not a method; it's another class. To make sure Python doesn't assume that <code>self.target_class()</code> refers to a method, we've assigned it to a local variable called <code>target_class</code>. Now we can use <code>target_class(sepal_length=, &#8230; etc.)</code> and there's no ambiguity.</p><p>This is pleasantly Pythonic. We can create subclasses of this reader to create different kinds of samples from the raw data.</p><p>This <code>SampleReader</code> class definition exposes a problem. A single source of raw sample data needs to be partitioned into two separate subclasses of <code>KnownSample</code>; it's either a <code>TrainingSample</code> or a <code>TestingSample</code>. There's a tiny difference in behavior between these two classes. A <code>TestingSample</code> is used to confirm the <em>k</em>-NN algorithm works, and is used to compare an algorithmic classification against the expert Botanist-assigned species. This is not something a <code>TrainingSample</code> needs to do.</p><p>Ideally, a single reader would emit a mixture of the two classes. The design so far only allows for instances of a single class to be created. We have two paths forward to provide the needed functionality:</p><ul><li><p>A more sophisticated algorithm for deciding what class to create. The algorithm would likely include an <code>if</code> statement to create an instance of one object or another.</p></li><li><p>A simplified definition of <code>KnownSample</code>. This single class can handle immutable training samples separately from mutable testing samples that can be classified (and reclassified) any number of times.</p></li></ul><p>Simplification seems to be a good idea. Less complexity means less code and fewer places for bugs to hide. The second alternative suggests we can separate three distinct aspects of a sample:</p><ul><li><p>The "raw" data. This is the core collection of measurements. They are immutable. (We'll address this design variation in <em>Chapter 7</em>, <em>Python Data Structures</em>.)</p></li><li><p>The Botanist-assigned species. This is available for training or testing data, but not part of an unknown sample. The assigned species, like the measurements, is immutable.</p></li><li><p>An algorithmically assigned classification. This is applied to the testing and unknown samples. This value can be seen as mutable; each time we classify a sample (or reclassify a test sample), the value changes.</p></li></ul><p>This a profound change to the design created so far. Early in a project, this kind of change can be necessary. Way back in <em>Chapters 1</em> and <em>2</em>, we decided to create a fairly sophisticated class hierarchy for various kinds of samples. It's time to revisit that design. This won't be the last time we think through this. The essence of good design is to create and dispose of a number of bad designs first.</p><h2>The sample class hierarchy</h2><p>We can rethink our earlier designs from several points of view. One alternative is to separate the essential <code>Sample</code> class from the additional features. It seems like we can identify four additional behaviors for each <code>Sample</code> instance, shown in the following table.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7iz5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb015a1f-f7c1-4cc9-b53b-9464eaf6f2f1_590x171.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7iz5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb015a1f-f7c1-4cc9-b53b-9464eaf6f2f1_590x171.png 424w, https://substackcdn.com/image/fetch/$s_!7iz5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb015a1f-f7c1-4cc9-b53b-9464eaf6f2f1_590x171.png 848w, https://substackcdn.com/image/fetch/$s_!7iz5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb015a1f-f7c1-4cc9-b53b-9464eaf6f2f1_590x171.png 1272w, https://substackcdn.com/image/fetch/$s_!7iz5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb015a1f-f7c1-4cc9-b53b-9464eaf6f2f1_590x171.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7iz5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb015a1f-f7c1-4cc9-b53b-9464eaf6f2f1_590x171.png" width="590" height="171" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fb015a1f-f7c1-4cc9-b53b-9464eaf6f2f1_590x171.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:171,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:17712,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/172067259?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb015a1f-f7c1-4cc9-b53b-9464eaf6f2f1_590x171.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7iz5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb015a1f-f7c1-4cc9-b53b-9464eaf6f2f1_590x171.png 424w, https://substackcdn.com/image/fetch/$s_!7iz5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb015a1f-f7c1-4cc9-b53b-9464eaf6f2f1_590x171.png 848w, https://substackcdn.com/image/fetch/$s_!7iz5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb015a1f-f7c1-4cc9-b53b-9464eaf6f2f1_590x171.png 1272w, https://substackcdn.com/image/fetch/$s_!7iz5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb015a1f-f7c1-4cc9-b53b-9464eaf6f2f1_590x171.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>We've omitted a detail from the <strong>Classified</strong> row. Each time we do a classification, a specific hyperparameter is associated with the resulting classified sample. It would be more accurate to say it's a sample classified by a specific <code>Hyperparameter</code> object. But this might be too much clutter.</p><p>The distinction between the two cells in the <strong>Unknown</strong> column is minute. The distinction is so minor as to be essentially irrelevant to most processing. An <strong>Unknown</strong> sample will be waiting to be classified for &#8211; at most &#8211; a few lines of code.</p><p>If we rethink this, we may be able to create fewer classes and still reflect the object state and behavior changes correctly.</p><p>There can be two subclasses of <code>Sample</code> with a separate <code>Classification</code> object. Here's a diagram.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Inh_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32144b6e-2602-46b7-a343-0bd4add34769_827x711.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Inh_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32144b6e-2602-46b7-a343-0bd4add34769_827x711.png 424w, https://substackcdn.com/image/fetch/$s_!Inh_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32144b6e-2602-46b7-a343-0bd4add34769_827x711.png 848w, https://substackcdn.com/image/fetch/$s_!Inh_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32144b6e-2602-46b7-a343-0bd4add34769_827x711.png 1272w, https://substackcdn.com/image/fetch/$s_!Inh_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32144b6e-2602-46b7-a343-0bd4add34769_827x711.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Inh_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32144b6e-2602-46b7-a343-0bd4add34769_827x711.png" width="827" height="711" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/32144b6e-2602-46b7-a343-0bd4add34769_827x711.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:711,&quot;width&quot;:827,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Diagram\n\nDescription automatically generated&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Diagram

Description automatically generated" title="Diagram

Description automatically generated" srcset="https://substackcdn.com/image/fetch/$s_!Inh_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32144b6e-2602-46b7-a343-0bd4add34769_827x711.png 424w, https://substackcdn.com/image/fetch/$s_!Inh_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32144b6e-2602-46b7-a343-0bd4add34769_827x711.png 848w, https://substackcdn.com/image/fetch/$s_!Inh_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32144b6e-2602-46b7-a343-0bd4add34769_827x711.png 1272w, https://substackcdn.com/image/fetch/$s_!Inh_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32144b6e-2602-46b7-a343-0bd4add34769_827x711.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 5.1: Sample class diagram</em></p><p>We've refined the class hierarchy to reflect two essentially different kinds of samples:</p><ul><li><p>A <code>KnownSample</code> instance can be used for testing or training. The difference between other classes is implemented in the method that does classification. We can make this depend on a <code>purpose</code> attribute, shown with a small square (or sometimes a "<code>-</code>") as a prefix. Python doesn't have private variables, but this marker can be helpful as a design note. The public attributes can be shown with a small circle (or a "<code>+</code>" to save space) as a prefix.</p><ul><li><p>When the purpose has a value of <code>Training</code>, the <code>classify()</code> method will raise an exception. The sample cannot be re-classified; that would invalidate the training.</p></li><li><p>When the purpose has a value of <code>Testing</code>, the <code>classify()</code> method will work normally, applying a given <code>Hyperparameter</code> to compute a species.</p></li></ul></li><li><p>An <code>UnknownSample</code> instance can be used for user classification. The classification method here does not depend on the value of the <code>purpose</code> attribute, and always performs classification.</p></li></ul><p>Let's look at implementing these behaviors with the <code>@property</code> decorator we learned about in this chapter. We can use <code>@property</code> to fetch computed values as if they were simple attributes. We can also use <code>@property</code> to define attributes that cannot be set.</p><h2>The purpose enumeration</h2><p>We'll start by enumerating a domain of <code>purpose</code> values:</p><pre><code><code>class Purpose(enum.IntEnum):
    Classification = 0
    Testing = 1
    Training = 2</code></code></pre><p>This definition creates a namespace with three objects we can use in our code: <code>Purpose.Classification</code>, <code>Purpose.Testing</code>, and <code>Purpose.Training</code>. For example, we can use <code>if sample.purpose == Purpose.Testing:</code> to identify a testing sample.</p><p>We can convert to <code>Purpose</code> objects from input values using <code>Purpose(x)</code> where <code>x</code> is an integer value, 0, 1, or 2. Any other value will raise a <code>ValueError</code> exception. We can convert back to numeric values, also. For example, <code>Purpose.Training.value</code> is <code>1</code>. This use of numeric codes can fit well with external software that doesn't deal well with an enumeration of Python objects.</p><p>We'll decompose the <code>KnownSample</code> subclass of the <code>Sample</code> class into two parts. Here's the first part. We initialize a sample with the data required by the <code>Sample.__init__()</code> method plus two additional values, the <code>purpose</code> numeric code, and the assigned species:</p><pre><code><code>class KnownSample(Sample):
    def __init__(
        self,
        sepal_length: float,
        sepal_width: float,
        petal_length: float,
        petal_width: float,
        purpose: int,
        species: str,
    ) -&gt; None:
        purpose_enum = Purpose(purpose)
        if purpose_enum not in {Purpose.Training, Purpose.Testing}:
            raise ValueError(
                f"Invalid purpose: {purpose!r}: {purpose_enum}"
            )
        super().__init__(
            sepal_length=sepal_length,
            sepal_width=sepal_width,
            petal_length=petal_length,
            petal_width=petal_width,
        )
        self.purpose = purpose_enum
        self.species = species
        self._classification: Optional[str] = None
    def matches(self) -&gt; bool:
        return self.species == self.classification</code></code></pre><p>We validate the <code>purpose</code> parameter's value to be sure it decodes to either <code>Purpose.Training</code> or <code>Purpose.Testing</code>. If the <code>purpose</code> value isn't one of the two allowed values, we'll raise a <code>ValueError</code> exception because the data is unusable.</p><p>We've created an instance variable, <code>self._classification</code>, with a leading <code>_</code> name. This is a convention that suggests the name is not for general use by clients of this class. It's not "private," since there's no notion of privacy in Python. We could call it "concealed" or perhaps "watch out for surprises here."</p><p>Instead of a large, opaque wall available in some languages, Python uses a low, decorative floral border that sets this variable apart from the others. You can march right through the floral <code>_</code> character to look at the value closely, but you probably shouldn't.</p><p>Here's the first <code>@property</code> method:</p><pre><code><code>    @property
    def classification(self) -&gt; Optional[str]:
        if self.purpose == Purpose.Testing:
            return self._classification
        else:
            raise AttributeError(f"Training samples have no classification")</code></code></pre><p>This defines a method that will be visible as an attribute name. Here's an example of creating a sample for testing purposes:</p><pre><code><code>&gt;&gt;&gt; from model import KnownSample, Purpose
&gt;&gt;&gt; s2 = KnownSample(
...     sepal_length=5.1, 
...     sepal_width=3.5, 
...     petal_length=1.4, 
...     petal_width=0.2, 
...     species="Iris-setosa", 
...     purpose=Purpose.Testing.value)
&gt;&gt;&gt; s2
KnownSample(sepal_length=5.1, sepal_width=3.5, petal_length=1.4, petal_width=0.2, purpose=1, species='Iris-setosa')
&gt;&gt;&gt; s2.classification is None
True</code></code></pre><p>When we evaluate <code>s2.classification</code>, this will call the method. This function makes sure this is a sample to be used for testing, and returns the value of the "concealed" instance variable <code>self._classification</code>.</p><p>If this is a <code>Purpose.Training</code> sample, the property will raise an <code>AttributeError</code> exception because any application that checks the value of the classification for a training sample has a bug in it that needs to be fixed.</p><h2>Property setters</h2><p>How do we set the classification? Do we really execute the statement <code>self._classification= h.classify(self)</code>? The answer is no &#8211; we can create a property that updates the "concealed" instance variable. This is a bit more complex than the example above:</p><pre><code><code>    @classification.setter
    def classification(self, value: str) -&gt; None:
        if self.purpose == Purpose.Testing:
            self._classification = value
        else:
            raise AttributeError(
                f"Training samples cannot be classified")</code></code></pre><p>The initial <code>@property</code> definition for <code>classification</code> is called a "getter." It gets the value of an attribute. (The implementation uses the <code>__get__()</code> method of a descriptor object that was created for us.) The <code>@property</code> definition for <code>classification</code> also creates an additional decorator, <code>@classification.setter</code>. The method decorated by the setter is used by assignment statements.</p><p>Note that the method names for these two properties are both <code>classification</code>. This is the attribute name to be used.</p><p>Now a statement like <code>s2.classification = h.classify(self)</code> will change the classification from a particular <code>Hyperparameter</code> object. This assignment statement will use the method to examine the purpose of this sample. If the purpose is testing, the value will be saved. If the purpose is not <code>Purpose.Testing</code>, then attempting to set a classification raises an <code>AttributeError</code> exception, and identifies a place where something's wrong in our application.</p><h2>Repeated if statements</h2><p>We have a number of <code>if</code> statements checking for specific <code>Purpose</code> values. This is a suggestion that this design is not optimal. The variant behavior is not encapsulated in a single class; instead, multiple behaviors are combined into a class.</p><p>The presence of a <code>Purpose</code> enumeration and <code>if</code> statements to check for the enumerated values is a suggestion that we have multiple classes. The "simplification" here isn't desirable.</p><p>In the <em>Input partitioning</em> section of this case study, we suggested there were two paths forward. One was to try and simplify the classes by setting the <code>purpose</code> attribute to separate testing from training data. This seems to have added <code>if</code> statements, without really simplifying the design.</p><p>This means we'll have to search for a better partitioning algorithm in a later chapter's case study. For now, we have the capability of creating valid data, but we also have code that's cluttered with <code>if</code> statements. The reader is encouraged to try alternative designs to examine the resulting code to see what seems simpler and easier to read.</p><div><hr></div><h1>Recall</h1><p>Here are some of the key points in this chapter:</p><ul><li><p>When we have both data and behavior, this is the sweet spot for object-oriented design. We can leverage Python's generic collections and ordinary functions for many things. When it becomes complex enough that we need to be sure that pieces are all defined together, then we need to start using classes.</p></li><li><p>When an attribute value is a reference to another object, the Pythonic approach is to allow direct access to the attribute; we don't write elaborate setter and getter functions. When an attribute value is computed, we have two choices: we can compute it eagerly or lazily. A property lets us be lazy and do the computation just in time.</p></li><li><p>We'll often have cooperating objects; the behavior of the application emerges from the cooperation. This can often lead to manager objects that combine behaviors from component class definitions to create an integrated, working whole.</p></li></ul><div><hr></div><h1>Exercises</h1><p>We've looked at various ways that objects, data, and methods can interact with each other in an object-oriented Python program. As usual, your first thoughts should be how you can apply these principles to your own work. Do you have any messy scripts lying around that could be rewritten using an object-oriented manager? Look through some of your old code and look for methods that are not actions. If the name isn't a verb, try rewriting it as a property.</p><p>Think about code you've written in any language. Does it break the DRY principle? Is there any duplicate code? Did you copy and paste code? Did you write two versions of similar pieces of code because you didn't feel like understanding the original code? Go back over some of your recent code now and see whether you can refactor the duplicate code using inheritance or composition. Try to pick a project you're still interested in maintaining, not code so old that you never want to touch it again. That will help to keep you interested when you do the improvements!</p><p>Now, look back over some of the examples we looked at in this chapter. Start with the cached web page example that uses a property to cache the retrieved data. An obvious problem with this example is that the cache is never refreshed. Add a timeout to the property's getter, and only return the cached page if the page has been requested before the timeout has expired. You can use the <code>time</code> module (<code>time.time() - an_old_time</code> returns the number of seconds that have elapsed since <code>an_old_time</code>) to determine whether the cache has expired.</p><p>Also look at the inheritance-based <code>ZipProcessor</code>. It might be reasonable to use composition instead of inheritance here. Instead of extending the class in the <code>ZipReplace</code> and <code>ScaleZip</code> classes, you could pass instances of those classes into the <code>ZipProcessor</code> constructor and call them to do the processing part. Implement this.</p><p>Which version do you find easier to use? Which is more elegant? What is easier to read? These are subjective questions; the answer varies for each of us. Knowing the answer, however, is important. If you find you prefer inheritance over composition, you need to pay attention that you don't overuse inheritance in your daily coding. If you prefer composition, make sure you don't miss opportunities to create an elegant inheritance-based solution.</p><p>Finally, add some error handlers to the various classes we created in the case study. How should one bad sample be handled? Should the model be inoperable? Or should the row be skipped? There are profound data science and statistical consequences to a seemingly small technical implementation choice. Can we define a class that permits either alternative behavior?</p><p>In your daily coding, pay attention to the copy and paste commands. Every time you use them in your editor, consider whether it would be a good idea to improve your program's organization so that you only have one version of the code you are about to copy.</p><div><hr></div><h1>Summary</h1><p>In this chapter, we focused on identifying objects, especially objects that are not immediately apparent; objects that manage and control. Objects should have both data and behaviors, but properties can be used to blur the distinction between the two. The DRY principle is an important indicator of code quality, and inheritance and composition can be applied to reduce code duplication.</p><p>In the next chapter, we'll look at Python's methods for defining abstract base classes. This lets us define a class that's a kind of template; it must be extended with subclasses that add narrowly-defined implementation features. This lets us build families of related classes, confident that they will work together properly.</p><div><hr></div><p><strong>About the Author:</strong></p><p><strong>Steven Lott</strong> has been programming since computers were large, expensive, and rare. Working for decades in high tech has given him exposure to a lot of ideas and techniques, some bad, but most are helpful to others. Since the 1990s, Steven has been engaged with Python, crafting an array of indispensable tools and applications. His profound expertise has led him to contribute significantly to Packt Publishing, penning notable titles like <em><strong><a href="https://www.packtpub.com/en-us/product/mastering-object-oriented-python-9781789531404">Mastering Object-Oriented Python</a> </strong></em>(Packt, 2019), <em><strong><a href="https://www.packtpub.com/en-us/product/modern-python-cookbook-9781835466384">Modern Python Cookbook</a></strong></em> (Packt, 2024), and <em><strong><a href="https://www.packtpub.com/en-us/product/python-real-world-projects-9781803246765">Python Real-World Projects</a> </strong></em>(Packt, 2023). A self-proclaimed technomad, Steven's unconventional lifestyle sees him residing on a boat, often anchored along the vibrant east coast of the US. He tries to live by the words &#8220;Don&#8217;t come home until you have a story.&#8221;</p><div><hr></div><p>Lott is currently working on the <strong>5th Ed.</strong> of <em><strong>Python Object-Oriented Programming</strong> </em>due later this year. If you liked this chapter and want more right now, you can check out <strong><a href="https://www.packtpub.com/en-us/product/python-object-oriented-programming-9781801075237">Python Object-Oriented Programming, 4th Ed. (Packt, 2021)</a>.</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-us/product/python-object-oriented-programming-9781801075237" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!S24_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0059565-ab59-4421-90dd-de189e63939f_810x1000 424w, https://substackcdn.com/image/fetch/$s_!S24_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0059565-ab59-4421-90dd-de189e63939f_810x1000 848w, https://substackcdn.com/image/fetch/$s_!S24_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0059565-ab59-4421-90dd-de189e63939f_810x1000 1272w, https://substackcdn.com/image/fetch/$s_!S24_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0059565-ab59-4421-90dd-de189e63939f_810x1000 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!S24_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0059565-ab59-4421-90dd-de189e63939f_810x1000" width="340" height="419.75308641975306" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b0059565-ab59-4421-90dd-de189e63939f_810x1000&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1000,&quot;width&quot;:810,&quot;resizeWidth&quot;:340,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Python Object-Oriented Programming&quot;,&quot;title&quot;:&quot;Python Object-Oriented Programming&quot;,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-us/product/python-object-oriented-programming-9781801075237&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Python Object-Oriented Programming" title="Python Object-Oriented Programming" srcset="https://substackcdn.com/image/fetch/$s_!S24_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0059565-ab59-4421-90dd-de189e63939f_810x1000 424w, https://substackcdn.com/image/fetch/$s_!S24_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0059565-ab59-4421-90dd-de189e63939f_810x1000 848w, https://substackcdn.com/image/fetch/$s_!S24_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0059565-ab59-4421-90dd-de189e63939f_810x1000 1272w, https://substackcdn.com/image/fetch/$s_!S24_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0059565-ab59-4421-90dd-de189e63939f_810x1000 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here is what some readers have said:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!B_ZW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0076979-6368-4943-8ac8-1595f1bdb737_1078x432.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!B_ZW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0076979-6368-4943-8ac8-1595f1bdb737_1078x432.png 424w, https://substackcdn.com/image/fetch/$s_!B_ZW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0076979-6368-4943-8ac8-1595f1bdb737_1078x432.png 848w, https://substackcdn.com/image/fetch/$s_!B_ZW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0076979-6368-4943-8ac8-1595f1bdb737_1078x432.png 1272w, https://substackcdn.com/image/fetch/$s_!B_ZW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0076979-6368-4943-8ac8-1595f1bdb737_1078x432.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!B_ZW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0076979-6368-4943-8ac8-1595f1bdb737_1078x432.png" width="1078" height="432" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f0076979-6368-4943-8ac8-1595f1bdb737_1078x432.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:432,&quot;width&quot;:1078,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:95317,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/169634247?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0076979-6368-4943-8ac8-1595f1bdb737_1078x432.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!B_ZW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0076979-6368-4943-8ac8-1595f1bdb737_1078x432.png 424w, https://substackcdn.com/image/fetch/$s_!B_ZW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0076979-6368-4943-8ac8-1595f1bdb737_1078x432.png 848w, https://substackcdn.com/image/fetch/$s_!B_ZW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0076979-6368-4943-8ac8-1595f1bdb737_1078x432.png 1272w, https://substackcdn.com/image/fetch/$s_!B_ZW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0076979-6368-4943-8ac8-1595f1bdb737_1078x432.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QhcA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d263b1-d57e-44df-bbb1-6d36e59a7807_1097x495.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QhcA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d263b1-d57e-44df-bbb1-6d36e59a7807_1097x495.png 424w, https://substackcdn.com/image/fetch/$s_!QhcA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d263b1-d57e-44df-bbb1-6d36e59a7807_1097x495.png 848w, https://substackcdn.com/image/fetch/$s_!QhcA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d263b1-d57e-44df-bbb1-6d36e59a7807_1097x495.png 1272w, https://substackcdn.com/image/fetch/$s_!QhcA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d263b1-d57e-44df-bbb1-6d36e59a7807_1097x495.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QhcA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d263b1-d57e-44df-bbb1-6d36e59a7807_1097x495.png" width="1097" height="495" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c6d263b1-d57e-44df-bbb1-6d36e59a7807_1097x495.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:495,&quot;width&quot;:1097,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:120161,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/169634247?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d263b1-d57e-44df-bbb1-6d36e59a7807_1097x495.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!QhcA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d263b1-d57e-44df-bbb1-6d36e59a7807_1097x495.png 424w, https://substackcdn.com/image/fetch/$s_!QhcA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d263b1-d57e-44df-bbb1-6d36e59a7807_1097x495.png 848w, https://substackcdn.com/image/fetch/$s_!QhcA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d263b1-d57e-44df-bbb1-6d36e59a7807_1097x495.png 1272w, https://substackcdn.com/image/fetch/$s_!QhcA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d263b1-d57e-44df-bbb1-6d36e59a7807_1097x495.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bMcY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f8102a-90fa-4688-b30c-92128ca51d95_1070x365.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bMcY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f8102a-90fa-4688-b30c-92128ca51d95_1070x365.png 424w, https://substackcdn.com/image/fetch/$s_!bMcY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f8102a-90fa-4688-b30c-92128ca51d95_1070x365.png 848w, https://substackcdn.com/image/fetch/$s_!bMcY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f8102a-90fa-4688-b30c-92128ca51d95_1070x365.png 1272w, https://substackcdn.com/image/fetch/$s_!bMcY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f8102a-90fa-4688-b30c-92128ca51d95_1070x365.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bMcY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f8102a-90fa-4688-b30c-92128ca51d95_1070x365.png" width="1070" height="365" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c9f8102a-90fa-4688-b30c-92128ca51d95_1070x365.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:365,&quot;width&quot;:1070,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:81939,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/169634247?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f8102a-90fa-4688-b30c-92128ca51d95_1070x365.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!bMcY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f8102a-90fa-4688-b30c-92128ca51d95_1070x365.png 424w, https://substackcdn.com/image/fetch/$s_!bMcY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f8102a-90fa-4688-b30c-92128ca51d95_1070x365.png 848w, https://substackcdn.com/image/fetch/$s_!bMcY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f8102a-90fa-4688-b30c-92128ca51d95_1070x365.png 1272w, https://substackcdn.com/image/fetch/$s_!bMcY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f8102a-90fa-4688-b30c-92128ca51d95_1070x365.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>]]></content:encoded></item><item><title><![CDATA[Go Concurrency]]></title><description><![CDATA[The complete &#8220;Chapter 8: Go Concurrency&#8221; from the book Mastering Go, Fourth Edition by Mihalis Tsoukalos (Packt, March 2024).]]></description><link>https://deepengineering.net/p/go-concurrency</link><guid isPermaLink="false">https://deepengineering.net/p/go-concurrency</guid><dc:creator><![CDATA[Mihalis Tsoukalos]]></dc:creator><pubDate>Wed, 20 Aug 2025 10:47:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!VBef!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The key component of the Go concurrency model is the goroutine, which is the <em><strong>minimum executable entity</strong></em> in Go. To create a new goroutine, we must use the <code>go</code> keyword followed by a function call or an anonymous function&#8212;the two methods are equivalent. For a goroutine or a function to terminate the entire Go application, it should call <code>os.Exit()</code> instead of <code>return</code>. However, most of the time, we exit a goroutine or a function using <code>return</code> because what we really want is to exit that specific goroutine or function and not stop the entire application.</p><p>Everything in Go is executed as a goroutine, either transparently or consciously. Each executable Go program has at least one goroutine, which is used for running the <code>main()</code> function of the <code>main</code> package. Each goroutine is executed on a single OS thread according to the instructions of the Go scheduler, which is responsible for the execution of goroutines&#8212;the developer has no control over the amount of memory allocated to a goroutine. The OS scheduler does not dictate how many threads the Go runtime is going to create because the Go runtime will spawn enough threads to ensure that <code>GOMAXPROCS</code> threads are available to run the Go code.</p><p>However, <em><strong>goroutines cannot communicate with each other directly</strong></em>. Data sharing in Go is implemented using either channels, local sockets, or shared memory. <em>Channels</em> act as the glue that connects multiple goroutines. On the other hand, channels cannot process data or execute code but they can send data to and receive data from goroutines and have a special purpose like acting as signals or specifying the order of execution for goroutines.</p><p>When I first learned about channels, I thought that they were a great idea, much better than shared memory, and I wanted to use channels everywhere! However, nowadays<em><strong> I only use channels when I have no other alternative</strong></em>. Look at the implementation of the concurrent statistical application at the end of the chapter to realize that there exist designs that do not require the use of channels.</p><p>Although the use of channels to communicate and synchronize between goroutines is very typical and expected, channels might introduce deadlocks, overhead, and complexity to the design, as well as performance considerations, especially when low-latency communication is a priority.</p><p>When you combine multiple channels and goroutines, you can create data flows, which, in Go terminology, are called pipelines. So, you might have a goroutine that reads data from a database and sends it to a channel and a second goroutine that reads from that channel, processes that data, and sends it to another channel to be read from another goroutine, before making modifications to the data and storing it in another database.</p><p>This chapter covers:</p><ul><li><p>Processes, threads, and goroutines</p></li><li><p>The Go scheduler</p></li><li><p>Goroutines</p></li><li><p>Channels</p></li><li><p>Race conditions are bad</p></li><li><p>The <code>select</code> keyword</p></li><li><p>Timing out a goroutine</p></li><li><p>Go channels revisited</p></li><li><p>Handling UNIX signals</p></li><li><p>Shared memory and shared variables</p></li><li><p>Closured variables and the <code>go</code> statement</p></li><li><p>The <code>context</code> package</p></li><li><p>The <code>semaphore</code> package</p></li><li><p>Making the statistics application concurrent</p></li></ul><div><hr></div><h1>Go Concurrency</h1><p>The key component of the Go concurrency model is the goroutine, which is the <em><strong>minimum executable entity</strong></em> in Go. To create a new goroutine, we must use the <code>go</code> keyword followed by a function call or an anonymous function&#8212;the two methods are equivalent. For a goroutine or a function to terminate the entire Go application, it should call <code>os.Exit()</code> instead of <code>return</code>. However, most of the time, we exit a goroutine or a function using <code>return</code> because what we really want is to exit that specific goroutine or function and not stop the entire application.</p><p>Everything in Go is executed as a goroutine, either transparently or consciously. Each executable Go program has at least one goroutine, which is used for running the <code>main()</code> function of the <code>main</code> package. Each goroutine is executed on a single OS thread according to the instructions of the Go scheduler, which is responsible for the execution of goroutines&#8212;the developer has no control over the amount of memory allocated to a goroutine. The OS scheduler does not dictate how many threads the Go runtime is going to create because the Go runtime will spawn enough threads to ensure that <code>GOMAXPROCS</code> threads are available to run the Go code.</p><p>However, <em><strong>goroutines cannot communicate with each other directly</strong></em>. Data sharing in Go is implemented using either channels, local sockets, or shared memory. <em>Channels</em> act as the glue that connects multiple goroutines. On the other hand, channels cannot process data or execute code but they can send data to and receive data from goroutines and have a special purpose like acting as signals or specifying the order of execution for goroutines.</p><p>When I first learned about channels, I thought that they were a great idea, much better than shared memory, and I wanted to use channels everywhere! However, nowadays<em><strong> I only use channels when I have no other alternative</strong></em>. Look at the implementation of the concurrent statistical application at the end of the chapter to realize that there exist designs that do not require the use of channels.</p><p>Although the use of channels to communicate and synchronize between goroutines is very typical and expected, channels might introduce deadlocks, overhead, and complexity to the design, as well as performance considerations, especially when low-latency communication is a priority.</p><p>When you combine multiple channels and goroutines, you can create data flows, which, in Go terminology, are called pipelines. So, you might have a goroutine that reads data from a database and sends it to a channel and a second goroutine that reads from that channel, processes that data, and sends it to another channel to be read from another goroutine, before making modifications to the data and storing it in another database.</p><p>This chapter covers:</p><ul><li><p>Processes, threads, and goroutines</p></li><li><p>The Go scheduler</p></li><li><p>Goroutines</p></li><li><p>Channels</p></li><li><p>Race conditions are bad</p></li><li><p>The <code>select</code> keyword</p></li><li><p>Timing out a goroutine</p></li><li><p>Go channels revisited</p></li><li><p>Handling UNIX signals</p></li><li><p>Shared memory and shared variables</p></li><li><p>Closured variables and the <code>go</code> statement</p></li><li><p>The <code>context</code> package</p></li><li><p>The <code>semaphore</code> package</p></li><li><p>Making the statistics application concurrent</p></li></ul><div><hr></div><h1>Processes, threads, and goroutines</h1><p>A process is an OS representation of a running program, while a program is a binary file on a disk that contains all the information necessary for creating an OS process. The binary file is written in a specific format and contains all the instructions the CPU is going to run, as well as a plethora of other required sections. That program is loaded into memory and the instructions are executed, creating a running process. So, a process carries with it additional resources such as memory, opened file descriptions, and user data, as well as other types of resources that are obtained during runtime.</p><p>A thread is a smaller and lighter entity than a process. Processes consist of one or more threads that have their own flow of control and stack. A quick and simplistic way to differentiate a thread from a process is to consider a process as the running binary file and a thread as a subset of a process.</p><p>A goroutine is the minimum Go entity that can be executed concurrently. The use of the word <em>minimum</em> is very important here, as goroutines are not autonomous entities like UNIX processes&#8212;<em><strong>goroutines live in OS threads that live in OS processes</strong></em>. The good thing is that goroutines are lighter than threads, which, in turn, are lighter than processes&#8212;running thousands or hundreds of thousands of goroutines on a single machine is not a problem. Among the reasons that goroutines are lighter than threads are that they have a smaller stack that can grow, they have a faster startup time, and they can communicate with each other through channels with low latency. In practice, this means that a process can have multiple threads and lots of goroutines, whereas a goroutine needs the environment of a process to exist. So, to create a goroutine, you need to have a process with at least one thread. The OS takes care of the process and thread scheduling, while Go creates the necessary threads and the developer creates the desired number of goroutines.</p><p>Now that you know the basics of processes, programs, threads, and goroutines, let us talk a little bit about the Go scheduler.</p><div><hr></div><h1>The Go scheduler</h1><p>The OS kernel scheduler is responsible for the execution of the threads of a program. Similarly, the Go runtime has its own scheduler, which is responsible for the execution of the goroutines using a technique known as <em>m:n scheduling</em>, where m goroutines are executed using n OS threads using multiplexing. The Go scheduler is the Go component responsible for the way and the order in which the goroutines of a Go program get executed. This makes the Go scheduler a really important part of the Go programming language. The Go scheduler is also executed as a goroutine.</p><p>Be aware that as the Go scheduler only deals with the goroutines of a single program, its operation is much simpler, cheaper, and faster than the operation of the OS kernel scheduler.</p><p>Go uses the fork-join concurrency model. The <em>fork part</em> of the model, which should not be confused with the <code>fork(2)</code> system call, states that a child branch can be created at any point of a program. Analogously, the <em>join part</em> of the Go concurrency model is where the child branch ends and joins with its parent. Keep in mind that both <code>sync.Wait()</code> statements and channels that collect the results of goroutines are join points, whereas each new goroutine creates a child branch.</p><p><em><strong>The fair scheduling strategy shares all load evenly among the available processors</strong></em>. At first, this might look like the perfect strategy because it does not have to take many things into consideration while keeping all processors equally occupied. However, it turns out that this is not exactly the case because most distributed tasks usually depend on other tasks. Therefore, some processors are underutilized or, equivalently, some processors are utilized more than others.</p><p>A goroutine is a task, whereas everything after the calling statement of a goroutine is a continuation. <em><strong>In the work-stealing strategy used by the Go scheduler, a (logical) processor that is underutilized looks for additional work from other processors.</strong></em> When it finds such jobs, it steals them from the other processor or processors, hence the name. Additionally, the work-stealing algorithm of Go queues and steals continuations. A stalling join, as is suggested by its name, is a point where a thread of execution stalls at a join and starts looking for other work to do.</p><p>Although both task stealing and continuation stealing have stalling joins, continuations happen more often than tasks; therefore, the Go scheduling algorithm works with continuations rather than tasks. The main disadvantage of continuation stealing is that it requires extra work from the compiler of the programming language. Fortunately, Go provides that extra help and, therefore, uses continuation stealing in its work-stealing algorithm. One of the benefits of continuation stealing is that you get the same results when using function calls instead of goroutines or a single thread with multiple goroutines. This makes perfect sense, as only one thing is executed at any given point in both cases.</p><p>The Go scheduler works using three main kinds of entities: OS threads (M), which are related to the OS in use, goroutines (G), and logical processors (P). The number of processors that can be used by a Go program is specified by the value of the <code>GOMAXPROCS</code> environment variable&#8212;at any given time, there are, at most, <code>GOMAXPROCS</code> processors. Now, let us return to the <code>m:n</code> scheduling algorithm used in Go. Strictly speaking, at any time, you have <code>m</code> goroutines that are executed and, therefore, scheduled to run, on <code>n</code> OS threads using, at most, <code>GOMAXPROCS</code> number of logical processors. You will learn more about <code>GOMAXPROCS</code> shortly.</p><p>Each goroutine can be in one of the following three stages: <em>executing</em>, <em>runnable</em>, or <em>waiting</em>. In the executing stage, the instructions of the goroutine are executed on an OS thread. In the runnable stage, the goroutine waits to be assigned to an OS thread for execution. Finally, in the waiting stage, the goroutine is blocked for some reason like waiting for a resource or a mutex to become available to go into one of the other two stages.</p><p>The following figure shows that there are two different kinds of queues&#8212;a global run queue and a local run queue&#8212;attached to each logical processor. Goroutines from the global queue are assigned to the queue of a logical processor in order to get executed at some point in the future.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cfJX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76c15a98-fdcc-4810-a793-b6e91542061d_472x316.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cfJX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76c15a98-fdcc-4810-a793-b6e91542061d_472x316.png 424w, https://substackcdn.com/image/fetch/$s_!cfJX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76c15a98-fdcc-4810-a793-b6e91542061d_472x316.png 848w, https://substackcdn.com/image/fetch/$s_!cfJX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76c15a98-fdcc-4810-a793-b6e91542061d_472x316.png 1272w, https://substackcdn.com/image/fetch/$s_!cfJX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76c15a98-fdcc-4810-a793-b6e91542061d_472x316.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cfJX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76c15a98-fdcc-4810-a793-b6e91542061d_472x316.png" width="472" height="316" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/76c15a98-fdcc-4810-a793-b6e91542061d_472x316.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:316,&quot;width&quot;:472,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!cfJX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76c15a98-fdcc-4810-a793-b6e91542061d_472x316.png 424w, https://substackcdn.com/image/fetch/$s_!cfJX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76c15a98-fdcc-4810-a793-b6e91542061d_472x316.png 848w, https://substackcdn.com/image/fetch/$s_!cfJX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76c15a98-fdcc-4810-a793-b6e91542061d_472x316.png 1272w, https://substackcdn.com/image/fetch/$s_!cfJX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76c15a98-fdcc-4810-a793-b6e91542061d_472x316.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Figure 8.1: The operation of the Go scheduler</em></p><p>Each logical processor can have multiple threads, and the stealing occurs between the local queues of the available logical processors. Finally, keep in mind that the Go scheduler is allowed to create more OS threads when needed. OS threads are expensive in terms of resources and going from one status to another (<em>context switching</em>), which means that dealing too much with OS threads might slow down your Go applications.</p><p>Next, we discuss the meaning and the use of <code>GOMAXPROCS</code>.</p><h2>The GOMAXPROCS environment variable</h2><p>The <code>GOMAXPROCS</code> environment variable allows you to set the number of OS threads that can execute user-level Go code simultaneously; this does not limit the number of threads created but it does limit the number of threads that are actively running. Starting with Go version 1.5, the default value of <code>GOMAXPROCS</code> should be the number of logical cores available in your machine. There is also the <code>runtime.GOMAXPROCS()</code> function, which allows you to set and get the value of <code>GOMAXPROCS</code> programmatically.</p><p>If you decide to assign a value to <code>GOMAXPROCS</code> that is smaller than the number of cores in your machine, you might affect the performance of your program. However, using a <code>GOMAXPROCS</code> value that is larger than the number of available cores does not necessarily make your Go programs run faster due to the context switching of threads.</p><p>As mentioned earlier in this subsection, you can programmatically set and get the value of the <code>GOMAXPROCS</code> environment variable&#8212;this is illustrated in <code>maxprocs.go</code>, which will also show additional capabilities of the runtime package. The <code>main()</code> function is implemented as follows:</p><pre><code><code>func main() {
    fmt.Print("You are using ", runtime.Compiler, " ")
    fmt.Println("on a", runtime.GOARCH, "machine")
    fmt.Println("Using Go version", runtime.Version())</code></code></pre><p>The <code>runtime.Compiler</code> variable holds the compiler toolchain used for building the running binary. The two most well-known values are <code>gc</code> and <code>gccgo</code>. The <code>runtime.GOARCH</code> variable holds the current architecture and <code>runtime.Version()</code> returns the current version of the Go compiler. This information is not necessary for using <code>runtime.GOMAXPROCS()</code> but it is good to have a better knowledge of your system.</p><pre><code><code>    fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))
}</code></code></pre><p>What happens with the <code>runtime.GOMAXPROCS(0)</code> call? It always returns the previous value of the maximum number of CPUs that can be executed simultaneously. When the parameter of <code>runtime.GOMAXPROCS()</code> is equal to or bigger than <code>1</code>, then <code>runtime.GOMAXPROCS()</code> also changes the current setting. As we are using <code>0</code>, our call does not alter the current setting.</p><p>Running <code>maxprocs.go</code> produces the following output:</p><pre><code><code>You are using gc on a arm64 machine
Using Go version go1.21.0
GOMAXPROCS: 10</code></code></pre><p>You can change the value of <code>GOMAXPROCS</code> on the fly using the following technique:</p><pre><code><code>$ GOMAXPROCS=100; go run maxprocs.go
You are using gc on a amd64 machine
Using Go version go1.21.0
GOMAXPROCS: 100</code></code></pre><p>The previous command temporarily changes the value of <code>GOMAXPROCS</code> to <code>100</code> and runs <code>maxprocs.go</code>.</p><p>Apart from testing the performance of your code using fewer cores, you will most likely not need to change <code>GOMAXPROCS</code>. The next subsection will explain the similarities and differences between concurrency and parallelism.</p><h2>Concurrency and parallelism</h2><p>It is a common misconception that concurrency is the same thing as parallelism&#8212;this is not true! Parallelism is the simultaneous execution of multiple entities of some kind, whereas concurrency is a way of structuring your components so that they can be executed independently when possible.</p><p>It is only when you build software components concurrently that you can safely execute them in parallel, when and if your OS and your hardware permit it. The Erlang programming language did this a long time ago&#8212;long before CPUs had multiple cores and computers had lots of RAM.</p><p>In a valid concurrent design, adding concurrent entities makes the whole system run faster because more things can be executed in parallel. So, the desired parallelism comes from a better concurrent expression and implementation of the problem. The developer is responsible for taking concurrency into account during the design phase of a system and will benefit from a potential parallel execution of the components of the system. So, the developer should not think about parallelism but about breaking things into independent components that solve the initial problem when combined.</p><p><em><strong>Even if you cannot run your functions in parallel on your machine, a valid concurrent design still improves the design, data flow, and maintainability of your programs</strong></em>. In other words, concurrency is better than parallelism! Let us now talk about goroutines before looking into channels, which are the main components of the Go concurrency model.</p><div><hr></div><h1>Goroutines</h1><p>You can define, create, and execute a new goroutine using the <code>go</code> keyword followed by a named function or an anonymous function call. The <code>go</code> keyword makes the function call return immediately, while the function starts running in the background as a goroutine and the rest of the program continues its execution. You cannot control or make any assumptions about the order in which your goroutines are going to be executed because that depends on the scheduler of the OS, the Go scheduler, and the load of the OS.</p><h2>Creating a goroutine</h2><p>In this subsection, you will learn how to create goroutines. The program that illustrates the technique is called <code>create.go</code>. The implementation of the <code>main()</code> function is as follows:</p><pre><code><code>func main() {
    go func(x int) {
        fmt.Printf("%d ", x)
    }(10)</code></code></pre><p>This is how you run an anonymous function as a goroutine. The <code>(10)</code> at the end is how you pass a parameter to an anonymous function. The previous anonymous function just prints a value. In general, it is more readable to pass parameters explicitly than to have the function close over the variables it uses.</p><pre><code><code>    go printme(15)</code></code></pre><p>This is how you execute a function as a goroutine. As a rule of thumb, the functions that you execute as goroutines should not return any values directly. <em><strong>Exchanging data with goroutines happens via the use of shared memory or channels or some other mechanism</strong></em>.</p><pre><code><code>    time.Sleep(time.Second)
    fmt.Println("Exiting...")
}</code></code></pre><p>As a Go program does not wait for its goroutines to end before exiting, we need to delay it manually, which is the purpose of the <code>time.Sleep()</code> call. We correct that shortly in order to wait for all goroutines to finish before exiting.</p><p>Running <code>create.go </code>produces the following output:</p><pre><code><code>$ go run create.go 
10 * 15
Exiting...</code></code></pre><p>The <code>10</code> part in the output is from the anonymous function, whereas the <code>* 15</code> part is from the <code>go printme(15)</code> statement. However, if you run <code>create.go</code> more than once, you might get a different output because the two goroutines are not always executed in the same order, as this depends on the Go scheduler:</p><pre><code><code>$ go run create.go
* 15
10 Exiting...</code></code></pre><p>The next subsection shows how to run a variable number of goroutines.</p><h2>Creating multiple goroutines</h2><p>In this subsection, you will learn how to create a variable number of goroutines. The program that illustrates the technique is called <code>multiple.go</code>. The number of goroutines is given as a command line argument to the program. The important code from the implementation of the <code>main()</code> function is the following:</p><pre><code><code>fmt.Printf("Going to create %d goroutines.\n", count)
for i := 0; i &lt; count; i++ {</code></code></pre><p>There is nothing prohibiting you from using a <code>for</code> loop to create multiple goroutines, especially when you want to create lots of them.</p><pre><code><code>    go func(x int) {
        fmt.Printf("%d ", x)
    }(i)
}
time.Sleep(time.Second)
fmt.Println("\nExiting...")</code></code></pre><p>Once again, <code>time.Sleep()</code> prevents the <code>main()</code> function from exiting immediately.</p><p>Running <code>multiple.go</code> generates the following kind of output:</p><pre><code><code>$ go run multiple.go 15
Going to create 15 goroutines.
3 0 8 4 5 6 7 11 9 12 14 13 1 2 10 
Exiting...</code></code></pre><p>If you run <code>multiple.go</code> many times, you are going to get different outputs. So, there is still room for improvement. The next subsection shows how to remove the call to <code>time.Sleep()</code> and make your programs wait for the goroutines to finish.</p><h2>Waiting for all goroutines to finish</h2><p>It is not enough to create multiple goroutines&#8212;you also need to wait for them to finish before the <code>main()</code> function ends. Therefore, this subsection shows a very popular technique that improves the code of <code>multiple.go</code>&#8212;the improved version is called <code>varGoroutines.go</code>. But first, we need to explain how this works.</p><p>The synchronization process begins by defining a <code>sync.WaitGroup</code> variable and using the <code>Add()</code>, <code>Done()</code>, and <code>Wait()</code> methods. If you look at the source code of the <code>sync</code> Go package, and more specifically at the <code>waitgroup.go</code> file, you see that the <code>sync.WaitGroup</code> type is nothing more than a structure with two fields:</p><pre><code><code>type WaitGroup struct {
    noCopy noCopy
    state1 [3]uint32
}</code></code></pre><p>Each call to <code>sync.Add()</code> increases a counter in the <code>state1</code> field, which is an array with three <code>uint32</code> elements. Notice that it is really important to call <code>sync.Add()</code> before the <code>go</code> statement in order to prevent any <em>race conditions</em>&#8212;we will learn about race conditions in the <em>Race conditions are bad</em> section. When each goroutine finishes its job, the <code>sync.Done()</code> function should be executed in order to decrease the same counter by one. Behind the scenes, <code>sync.Done()</code> runs an <code>Add(-1)</code> call. The <code>Wait()</code> method waits until that counter becomes <code>0</code> in order to return. The return of <code>Wait()</code> inside the <code>main()</code> function means that <code>main()</code> is going to return and the program ends.</p><p>You can call <code>Add()</code> with a positive integer value other than 1 in order to avoid calling <code>Add(1)</code> multiple times. This can be handy when you know the number of goroutines you are going to create in advance. <code>Done()</code> does not support that functionality.</p><p>The important part of <code>varGoroutines.go</code> is the following:</p><pre><code><code>var waitGroup sync.WaitGroup
fmt.Printf("%#v\n", waitGroup)</code></code></pre><p>This is where you create a <code>sync.WaitGroup</code> variable that you are going to use. The <code>fmt.Printf()</code> call prints the contents of the <code>sync.WaitGroup</code> structure&#8212;you do not usually do that but it is good for learning more about the <code>sync.WaitGroup</code> structure.</p><pre><code><code>for i := 0; i &lt; count; i++ {
    waitGroup.Add(1)</code></code></pre><p>We call <code>Add(1)</code> just before we create the goroutine in order to avoid race conditions.</p><pre><code><code>    go func(x int) {
        defer waitGroup.Done()</code></code></pre><p>The <code>Done()</code> call is going to be executed just before the anonymous function returns because of the <code>defer</code> keyword.</p><pre><code><code>        fmt.Printf("%d ", x)
    }(i)
}
fmt.Printf("%#v\n", waitGroup)
waitGroup.Wait()</code></code></pre><p>The <code>Wait()</code> function waits for the counter in the <code>waitGroup</code> variable to become <code>0</code> before it returns, which is what we want to achieve.</p><pre><code><code>fmt.Println("\nExiting...")</code></code></pre><p>When the <code>Wait()</code> function returns, the <code>fmt.Println()</code> statement is executed. No need to call <code>time.Sleep()</code> any more!</p><p>Running <code>varGoroutines.go</code> produces the following output:</p><pre><code><code>$ go run varGoroutines.go 15
Going to create 10 goroutines.
sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}
sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0xa00000000}, sema:0x0}
14 8 9 10 11 5 0 4 1 2 3 6 13 12 7 
Exiting...</code></code></pre><p>Remember that using more goroutines in a program is not a panacea for performance, as more goroutines, in addition to the various calls to <code>sync.Add()</code>, <code>sync.Wait()</code>, and <code>sync.Done()</code>, might slow down your program due to the extra housekeeping that needs to be done by the Go scheduler and the Go garbage collector.</p><h2>What if the number of Add() and Done() calls differ?</h2><p>When the number of <code>sync.Add()</code> calls and <code>sync.Done()</code> calls are equal, everything is going to be fine in your programs. However, this subsection tells you what happens when these two numbers do not agree with each other.</p><p>Without giving any command line parameters to <code>addDone.go</code>, the number of <code>Add()</code> calls is going to be smaller than the number of <code>Done()</code> calls. With at least one command line argument, the number of <code>Done()</code> calls is going to be smaller than the number of <code>Add()</code> calls. You can look at the Go code of <code>addDone.go</code> on your own. What is important is the output it generates. Running <code>addDone.go</code> without any command line arguments produces the following error message:</p><pre><code><code>$ go run addDone.go
Going to create 20 goroutines.
sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}
sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x1300000000}, sema:0x0}
19 3 4 5 6 7 8 9 10 11 12 13 14 15 16 2 1 17 18 0
Exiting...
panic: sync: negative WaitGroup counter
goroutine 20 [running]:
sync.(*WaitGroup).Add(0x1?, 0x1?)
    /opt/homebrew/Cellar/go/1.21.0/libexec/src/sync/waitgroup.go:62 +0x108
sync.(*WaitGroup).Done(0x0?)
    /opt/homebrew/Cellar/go/1.21.0/libexec/src/sync/waitgroup.go:87 +0x20
main.main.func1(0x0?)
    ~/go/src/github.com/mactsouk/mGo4th/ch08/addDone.go:26 +0x9c
created by main.main in goroutine 1
    ~/go/src/github.com/mactsouk/mGo4th/ch08/addDone.go:23 +0xec
exit status 2</code></code></pre><p>The cause of the error message can be found in the output: <code>panic: sync: negative WaitGroup counter</code>.</p><p>Sometimes, <code>addDone.go</code> does not produce any error messages and terminates just fine&#8212;<em><strong>this mainly happens when the system is already busy</strong></em>. This is an issue with concurrent programs in general&#8212;they do not always crash or misbehave as the order of execution can change, which might change the behavior of the program. This makes the debugging of concurrent software even more difficult.</p><p>Running <code>addDone.go</code> with one command line argument produces the following error message:</p><pre><code><code>$ go run addDone.go 1
Going to create 20 goroutines.
sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}
sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x1500000000}, sema:0x0}
19 1 2 11 12 13 14 15 16 17 18 6 3 4 5 8 7 9 0 10 fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x0?)
    /opt/homebrew/Cellar/go/1.21.0/libexec/src/runtime/sema.go:62 +0x2c
sync.(*WaitGroup).Wait(0x14000128030)
    /opt/homebrew/Cellar/go/1.21.0/libexec/src/sync/waitgroup.go:116 +0x78
main.main()
    ~/go/src/github.com/mactsouk/mGo4th/code/ch08/addDone.go:38 +0x230
exit status 2
</code></code></pre><p>Once again, the reason for the crash is printed on the screen: <code>fatal error: all goroutines are asleep - deadlock!</code>. This means that the program should wait indefinitely for a goroutine to finish&#8212;that is, for a <code>Done()</code> call that is never going to happen.</p><h2>Creating multiple files with goroutines</h2><p>As a practical example of the use of goroutines, this subsection presents a command line utility that creates multiple files populated with randomly generated data&#8212;such files can be used for testing file systems or generating data for testing. The crucial code of <code>randomFiles.go</code> is the following:</p><pre><code><code>var waitGroup sync.WaitGroup
for i := start; i &lt;= end; i++ {
    waitGroup.Add(1)
    
    go func(n int) {
        filepath := filepath.Join(path, fmt.Sprintf("%s%d", filename, n))
        defer waitGroup.Done()
        createFile(filepath)
    }(i)
}
waitGroup.Wait()
</code></code></pre><p>We first create a <code>sync.WaitGroup</code> variable in order to wait for all goroutines to finish in the right way. Each file is created by a single goroutine only. What is important here is that each file has a unique filename&#8212;this is implemented with the <code>filepath</code> variable that contains the value of the <code>for</code> loop counter. Multiple <code>createFile()</code> functions executed as goroutines create the files. This is a simple yet very efficient way of creating multiple files. Running <code>randomFiles.go</code> generates the following output:</p><pre><code><code>$ go run randomFiles.go 
Usage: randomFiles firstInt lastInt filename directory</code></code></pre><p>So, the utility requires four parameters, which are the first and last values of the <code>for</code> loop as well as the filename and the directory in which the files are going to be written. So, let us run the utility with the correct number of parameters:</p><pre><code><code>$ go run randomFiles.go 3 5 masterGo /tmp
/tmp/masterGo3 created!
/tmp/masterGo5 created!
/tmp/masterGo4 created!</code></code></pre><p>Everything looks fine, and four files have been created according to our instructions! Now that we know about goroutines, let us continue with channels.</p><div><hr></div><h1>Channels</h1><p>A channel is a communication mechanism that, among other things, allows goroutines to exchange data. Firstly, each channel allows the exchange of a particular data type, which is also called the element type of the channel, and secondly, for a channel to operate properly, you need someone to receive what is sent via the channel. You should declare a new channel using <code>make()</code> and the <code>chan</code> keyword (<code>make(chan int)</code>), and you can close a channel using the <code>close()</code> function. You can declare the size of a channel by writing something similar to <code>make(chan int, 1)</code>. This statement creates a <em><strong>buffered channel</strong></em> that has a different use&#8212;buffered channels are explained later in this chapter.</p><p>Just because we can use channels, it does not mean that we should. If a simpler solution exists that allows goroutines to get executed and save the generated information, we should also consider that. The purpose of every developer should be to create a simple design, not to use all the features of a programming language.</p><p>A pipeline is a virtual method for connecting goroutines and channels so that the output of one goroutine becomes the input of another goroutine using channels to transfer your data. One of the benefits that you get from using pipelines is that there is a constant data flow in your program, as no goroutine or channel has to wait for everything to be completed in order to start their execution. Additionally, you use fewer variables and, therefore, less memory space because you do not have to save everything as a variable. Finally, the use of pipelines simplifies the design of the program and improves its maintainability.</p><h2>Writing to and reading from a channel</h2><p>Writing a value (<code>val</code>) to a channel (<code>ch</code>) is as easy as writing <code>ch &lt;- val</code>. The arrow shows the direction of the value, and you will have no problem with this statement as long as both <code>var</code> and <code>ch</code> are of the same data type.</p><p>You can read a single value from a channel named <code>c</code> by executing <code>&lt;-c</code>. In this case, the direction is from the channel to the outer world. You can save that value into a new variable using <code>aVar := &lt;-c</code>.</p><p>Both channel reading and writing are illustrated in <code>channels.go</code>, which comes with the following code:</p><pre><code><code>package main
import (
    "fmt"
    "sync"
)
func writeToChannel(c chan int, x int) {
    c &lt;- x
    close(c)
}</code></code></pre><p>This function just writes a value to the channel and immediately closes it.</p><pre><code><code>func printer(ch chan bool) {
    ch &lt;- true
}</code></code></pre><p>This function just sends the <code>true</code> value to a <code>bool</code> channel.</p><pre><code><code>func main() {
    c := make(chan int, 1)</code></code></pre><p>This channel is buffered with a size of 1. This means that as soon as we fill that buffer, we can close the channel and the goroutine is going to continue its execution and return. A channel that is unbuffered has a different behavior: when you try to send a value to that channel, it blocks forever because it is waiting for someone to fetch that value. In this case, we definitely want a buffered channel in order to avoid any blocking.</p><pre><code><code>    var waitGroup sync.WaitGroup
    waitGroup.Add(1)
    go func(c chan int) {
        defer waitGroup.Done()
        writeToChannel(c, 10)
        fmt.Println("Exit.")
    }(c)
    fmt.Println("Read:", &lt;-c)</code></code></pre><p>Here, we read from the channel and print the value without storing it in a separate variable.</p><pre><code><code>    _, ok := &lt;-c
    if ok {
        fmt.Println("Channel is open!")
    } else {
        fmt.Println("Channel is closed!")
    }</code></code></pre><p>The previous code shows a technique for determining whether a channel is closed or not. In this case, we are ignoring the read value&#8212;if the channel was open, then the read value would be discarded.</p><pre><code><code>    waitGroup.Wait()
    var ch chan bool = make(chan bool)
    for i := 0; i &lt; 5; i++ {
        go printer(ch)
    }</code></code></pre><p>Here, we make an unbuffered channel and create five goroutines without any synchronization as we do not use any <code>Add()</code> calls.</p><pre><code><code>    // Range on channels
    // IMPORTANT: As the channel ch is not closed,
    // the range loop does not exit on its own.
    n := 0
    for i := range ch {</code></code></pre><p>The <code>range</code> keyword works with channels! However, a <code>range</code> loop on a channel only exits when the channel is closed or uses the <code>break</code> keyword.</p><pre><code><code>        fmt.Println(i)
        if i == true {
            n++
        }
        if n &gt; 2 {
            fmt.Println("n:", n)
            close(ch)
            break
        }
    }</code></code></pre><p>We close the <code>ch</code> channel when a condition is met and exit the <code>for</code> loop using <code>break</code>. Note that it is never a good idea to close a channel on the receiving end&#8212;this is presented here for the sake of the example. You are going to see the consequences of this decision in a while.</p><pre><code><code>    for i := 0; i &lt; 5; i++ {
        fmt.Println(&lt;-ch)
    }
}</code></code></pre><p>When trying to read from a closed channel, we get the zero value of its data type, so this <code>for</code> loop works just fine and does not cause any issues.</p><p>Running <code>channels.go</code> generates the following output:</p><pre><code><code>Exit.
Read: 10</code></code></pre><p>After writing the value <code>10</code> to the channel using <code>writeToChannel(c, 10)</code>, we read that value back.</p><pre><code><code>Channel is closed!
true
true
true</code></code></pre><p>The <code>for</code> loop with the <code>range</code> exits after three iterations&#8212;each iteration prints <code>true</code> on screen.</p><pre><code><code>n: 3
false
false
false
false
false</code></code></pre><p>These five <code>false</code> values are printed by the last <code>for</code> loop of the program.</p><p>Although everything looks fine with <code>channels.go</code>, there is a logical issue with it, which we will explain and resolve in the <em>Race conditions are bad</em> section. Additionally, if we run <code>channels.go</code> multiple times, it might crash. However, most of the time, it does not, which makes debugging even more challenging.</p><h2>Receiving from a closed channel</h2><p><em><strong>Reading from a closed channel returns the zero value of its data type</strong></em>. However, if you try to write to a closed channel, your program is going to crash in a bad way (panic). These two situations are explored in <code>readCloseCh.go</code> and, more specifically, in the implementation of the <code>main()</code> function:</p><pre><code><code>func main() {
    willClose := make(chan complex64, 10)</code></code></pre><p>If you make that an unbuffered channel, the program is going to crash.</p><pre><code><code>    // Write some data to the channel
    willClose &lt;- -1
    willClose &lt;- 1i</code></code></pre><p>We write two values to the <code>willClose</code> channel.</p><pre><code><code>    // Read data and empty channel
    &lt;-willClose
    &lt;-willClose
    close(willClose)</code></code></pre><p>Then, we read and discard these two values and we close the channel.</p><pre><code><code>    // Read again - this is a closed channel
    read := &lt;-willClose
    fmt.Println(read)
}</code></code></pre><p>The last value that we read from the channel is the zero value of a <code>complex64</code> data type. Running <code>readCloseCh.go</code> generates the following output:</p><pre><code><code>(0+0i)</code></code></pre><p>So, we got back the zero value of the <code>complex64</code> data type. Now let us continue and discuss how to work with functions that accept channels as parameters.</p><h2>Channels as function parameters</h2><p>When using a channel as a function parameter, you can specify its direction&#8212;that is, whether it is going to be used for sending or receiving data only. In my opinion, if you know the purpose of a channel in advance, you should use this capability because it makes your programs more robust. You will not be able to send data accidentally to a channel from which you should only receive data, or receive data from a channel to which you should only be sending data.</p><p>If you declare that a channel function parameter is going to be used for reading only and you try to write to it, you are going to get a compile error message that will most likely save you from nasty bugs in the future. This is the major benefit of this approach!</p><p>All these are illustrated in <code>channelFunc.go</code>&#8212;the implementation of the functions that accept channel parameters are the following:</p><pre><code><code>func printer(ch chan&lt;- bool) {
    ch &lt;- true
}</code></code></pre><p>The above function accepts a channel parameter that is available for writing only.</p><pre><code><code>func writeToChannel(c chan&lt;- int, x int) {
    fmt.Println("1", x)
    c &lt;- x
    fmt.Println("2", x)
}</code></code></pre><p>The channel parameter of the above function is available for reading only.</p><pre><code><code>func f2(out &lt;-chan int, in chan&lt;- int) {
    x := &lt;-out
    fmt.Println("Read (f2):", x)
    in &lt;- x
    return
}</code></code></pre><p>The last function accepts two channel parameters. However, <code>out</code> is available for reading, whereas <code>in</code> is offered for writing. If you try to perform an operation on a channel parameter that is not allowed, the Go compiler is going to complain. This happens even if the function is not being used.</p><p>The subject of the next section is race conditions&#8212;read it carefully in order to avoid undefined behaviors and unpleasant situations when working with multiple goroutines.</p><h1>Race conditions are bad</h1><p>A <em>data race condition</em> is a situation where two or more running elements, such as threads and goroutines, try to take control of or modify a shared resource or shared variable of a program. Strictly speaking, a data race occurs when two or more instructions access the same memory address, where at least one of them performs a write (change) operation. If all operations are read operations, then there is no race condition. In practice, this means that you might get different output if you run your program multiple times, and that is a bad thing.</p><p>Using the <code>-race</code> flag when running or building Go source files executes the Go race detector, which makes the compiler create a modified version of a typical executable file. This modified version can record all accesses to shared variables as well as all synchronization events that take place, including calls to <code>sync.Mutex</code> and <code>sync.WaitGroup</code>, which are presented later on in this chapter. After analyzing the relevant events, the race detector prints a report that can help you identify potential problems so that you can correct them.</p><h2>The Go race detector</h2><p>You can run the race detector tool with <code>go run -race</code>. If we test <code>channels.go</code> using <code>go run -race</code>, we are going to get the following output:</p><pre><code><code>$ go run -race channels.go 
Exit.
Read: 10
Channel is closed!
true
true
true
n: 3
==================
WARNING: DATA RACE
Write at 0x00c000094010 by main goroutine:
  runtime.recvDirect()
      /opt/homebrew/Cellar/go/1.21.0/libexec/src/runtime/chan.go:348 +0x7c
  main.main()
      ~/go/src/github.com/mactsouk/mGo4th/ch08/channels.go:54 +0x444
Previous read at 0x00c000094010 by goroutine 10:
  runtime.chansend1()
      /opt/homebrew/Cellar/go/1.21.0/libexec/src/runtime/chan.go:146 +0x2c
  main.printer()
      ~/go/src/github.com/mactsouk/mGo4th/ch08/channels.go:14 +0x34
  main.main.func3()
      ~/go/src/github.com/mactsouk/mGo4th/ch08/channels.go:40 +0x34
Goroutine 10 (running) created at:
  main.main()
      ~/go/src/github.com/mactsouk/mGo4th/ch08/channels.go:40 +0x2b8
==================
false
false
false
false
false
panic: send on closed channel
goroutine 36 [running]:
main.printer(0x0?)
    ~/go/src/github.com/mactsouk/mGo4th/ch08/channels.go:14 +0x38
created by main.main in goroutine 1
    ~/go/src/github.com/mactsouk/mGo4th/ch08/channels.go:40 +0x2bc
exit status 2</code></code></pre><p>Therefore, although <code>channels.go</code> looks fine at first, there is a race condition waiting to happen. Let us now discuss where the problem with <code>channels.go</code> lies based on the previous output. There is a closing of a channel at <code>channels.go</code> on line 54, and there is a write to the same channel on line 14 that looks to be the root of the race condition situation.</p><p>Line 54 is <code>close(ch)</code>, whereas line 14 is <code>ch &lt;- true</code>. The issue is that we cannot be sure about what is going to happen and in which order&#8212;this is the race condition. If you execute <code>channels.go</code> without the race detector, it might work, but if you try it multiple times, you might get a <code>panic: send on closed channel</code> error message&#8212;this mainly has to do with the order in which the Go scheduler is going to run the goroutines of the program. So, if the closing of the channel happens first, then writing to that channel is going to fail&#8212;race condition!</p><p>Fixing <code>channels.go</code> requires changing the code and, more specifically, the implementation of the <code>printer()</code> function. The corrected version of <code>channels.go</code> is named <code>chRace.go</code> and comes with the following code:</p><pre><code><code>func printer(ch chan&lt;- bool, times int) {
    for i := 0; i &lt; times; i++ {
        ch &lt;- true
    }
    close(ch)
}
func main() {
    // This is an unbuffered channel
    var ch chan bool = make(chan bool)
    // Write 5 values to channel with a single goroutine
    go printer(ch, 5)
    // IMPORTANT: As the channel ch is closed,
    // the range loop is going to exit on its own.
    for val := range ch {
        fmt.Print(val, " ")
    }
    fmt.Println()
    for i := 0; i &lt; 15; i++ {
        fmt.Print(&lt;-ch, " ")
    }
    fmt.Println()
}</code></code></pre><p>The first thing to notice is that instead of using multiple goroutines for writing to the desired channel, we use a single goroutine. <em><strong>A single goroutine writing to a channel followed by the closing of that channel cannot create any race conditions because things happen sequentially</strong></em>.</p><p>Running <code>go run -race chRace.go</code> produces the following output, which means that there is no longer a race condition:</p><pre><code><code>true true true true true 
false false false false false false false false false false false false false false false</code></code></pre><p>The next section is about the important and powerful <code>select</code> keyword.</p><div><hr></div><h1>The select keyword</h1><p>The <code>select</code> keyword is really important because it allows you to <em><strong>listen to multiple channels at the same time</strong></em>. A <code>select</code> block can have multiple cases and an optional <code>default</code> case, which mimics the <code>switch</code> statement. It is good for <code>select</code> blocks to have a timeout option just in case. Lastly, a <code>select</code> without any cases (<code>select{}</code>) waits forever.</p><p>In practice, this means that <code>select</code> allows a goroutine to wait on multiple communication operations. So, <code>select</code> gives you the power to listen to multiple channels using a single select block. Consequently, you can have non-blocking operations on channels, provided that you have implemented your <code>select</code> blocks appropriately.</p><p>A <code>select</code> statement is <em><strong>not evaluated sequentially</strong></em>, as all of its channels are examined simultaneously. If none of the channels in a <code>select</code> statement are ready, the <code>select</code> statement blocks (waits) until one of the channels is ready. If multiple channels of a <code>select</code> statement are ready, then the Go runtime <em><strong>makes a random selection</strong></em> from the set of these ready channels.</p><p>The code in <code>select.go</code> presents a simple use of <code>select</code> running in a goroutine that has three cases. But first, let us see how the goroutine that contains <code>select</code> is executed:</p><pre><code><code>    wg.Add(1)
    go func() {
        gen(0, 2*n, createNumber, end)
        wg.Done()
    }()</code></code></pre><p>The previous code tells us that for <code>wg.Done()</code> to get executed, <code>gen()</code> should return first. So, let us see the implementation of <code>gen()</code>:</p><pre><code><code>func gen(min, max int, createNumber chan int, end chan bool) {
    time.Sleep(time.Second)
    for {
        select {
        case createNumber &lt;- rand.Intn(max-min) + min:
        case &lt;-end:
            fmt.Println("Ended!")
            // return</code></code></pre><p>The right thing to do here is to add the <code>return</code> statement for <code>gen()</code> to finish. Let us imagine that you have forgotten to add the <code>return</code> statement. This means that the function is not going to finish after the <code>select</code> branch associated with the end channel parameter is executed&#8212;<code>createNumber</code> is not going to end the function as it has no <code>return</code> statement. Therefore, the <code>select</code> block keeps waiting for more. The solution can be found in the code that follows:</p><pre><code><code>        case &lt;-time.After(4 * time.Second):
            fmt.Println("time.After()!")
            return
        }
    }
}</code></code></pre><p>So, what is really happening in the code of the entire select block? This particular <code>select</code> statement has three cases. As stated earlier, <code>select</code> does not require a <code>default</code> branch. You can consider the third branch of the <code>select</code> statement as a clever <code>default</code> branch. This happens because <code>time.After()</code> waits for the specified duration (<code>4 * time.Second</code>) to elapse and then prints a message and properly ends <code>gen()</code> with <code>return</code>. This unblocks the <code>select</code> statement in case all of the other channels are blocked for some reason. Although omitting <code>return</code> from the second branch is a bug, this shows that having an exit strategy is always a good thing.</p><p>Running <code>select.go</code> produces the following output:</p><pre><code><code>$ go run select.go 10
Going to create 10 random numbers.
13 0 2 8 12 4 13 15 14 19 Ended!
time.After()!
Exiting...</code></code></pre><p>We are going to see <code>select</code> in action in the remainder of the chapter, starting from the next section, which discusses how to time out goroutines. What you should remember is that <code>select</code> allows us to <em><strong>listen to multiple channels from a single point</strong></em>.</p><div><hr></div><h1>Timing out a goroutine</h1><p>There are times when goroutines take more time than expected to finish&#8212;in such situations, we want to time out the goroutines so that we can unblock the program. This section presents two such techniques.</p><h2>Timing out a goroutine inside main()</h2><p>This subsection presents a simple technique for timing out a goroutine. The relevant code can be found in the <code>main()</code> function of <code>timeOut1.go</code>:</p><pre><code><code>func main() {
    c1 := make(chan string)
    go func() {
        time.Sleep(3 * time.Second)
        c1 &lt;- "c1 OK"
    }()</code></code></pre><p>The <code>time.Sleep()</code> call is used for emulating the time it normally takes for a function to finish its operation. In this case, the anonymous function that is executed as a goroutine takes about three seconds before writing a message to the <code>c1</code> channel.</p><pre><code><code>    select {
    case res := &lt;-c1:
        fmt.Println(res)
    case &lt;-time.After(time.Second):
        fmt.Println("timeout c1")
    }</code></code></pre><p>The purpose of the <code>time.After()</code> call is to wait for the desired time before being executed&#8212;if another branch is executed, the waiting time resets. In this case, we are not interested in the actual value returned by <code>time.After()</code> but in the fact that the <code>time.After()</code> branch was executed, which means that the waiting time has passed. In this case, as the value passed to the <code>time.After()</code> function is smaller than the value used in the <code>time.Sleep()</code> call that was executed previously, you will most likely get a timeout message. The reason for saying &#8220;most likely&#8221; is that Linux is not a real-time OS and, sometimes, the OS scheduler plays strange games, especially when it has to deal with a high load and has to schedule lots of tasks&#8212;this means that you should not make any assumptions about the operation of the OS scheduler.</p><pre><code><code>    c2 := make(chan string)
    go func() {
        time.Sleep(3 * time.Second)
        c2 &lt;- "c2 OK"
    }()
    select {
    case res := &lt;-c2:
        fmt.Println(res)
    case &lt;-time.After(4 * time.Second):
        fmt.Println("timeout c2")
    }
}</code></code></pre><p>The preceding code executes a goroutine that takes about three seconds to execute because of the <code>time.Sleep()</code> call and defines a timeout period of four seconds in select using <code>time.After(4 * time.Second)</code>. If the <code>time.After(4 * time.Second)</code> call returns after you get a value from the <code>c2</code> channel found in the first case of the <code>select</code> block, then there will be no timeout; otherwise, you will get a timeout. However, in this case, the value of the <code>time.After()</code> call provides enough time for the <code>time.Sleep()</code> call to return, so you will most likely not get a timeout message here.</p><p>Let us now verify our thoughts. Running <code>timeOut1.go</code> produces the following output:</p><pre><code><code>$ go run timeOut1.go 
timeout c1
c2 OK</code></code></pre><p>As expected, the first goroutine timed out, whereas the second one did not. The subsection that follows presents another timeout technique.</p><h2>Timing out a goroutine outside main()</h2><p>This subsection illustrates another technique for timing out goroutines. The <code>select</code> statement can be found in a separate function. Additionally, the timeout period is given as a command line argument.</p><p>The interesting part of <code>timeOut2.go</code> is the implementation of <code>timeout()</code>:</p><pre><code><code>func timeout(t time.Duration) {
    temp := make(chan int)
    go func() {
        time.Sleep(5 * time.Second)
        defer close(temp)
    }()
    select {
    case &lt;-temp:
        result &lt;- false
    case &lt;-time.After(t):
        result &lt;- true
    }
}</code></code></pre><p>In <code>timeout()</code>, the time duration that is used in the <code>time.After()</code> call is a function parameter, which means that it can vary. Once again, the <code>select</code> block supports the logic of the timeout. Any timeout period longer than 5 seconds will most likely give the goroutine enough time to finish. If <code>timeout()</code> writes <code>false</code> to the <code>result</code> channel, then there is no timeout, whereas if it writes <code>true</code>, there is a timeout. Running <code>timeOut2.go</code> produces the following output:</p><pre><code><code>$ go run timeOut2.go 100
Timeout period is 100ms
Time out!</code></code></pre><p>The timeout period is 100 milliseconds, which means that the goroutine did not have enough time to finish, hence the timeout message.</p><pre><code><code>$ go run timeOut2.go 5500 
Timeout period is 5.5s
OK</code></code></pre><p>This time, the timeout is 5,500 milliseconds, which means that the goroutine had enough time to finish.</p><p>The next section revisits and presents advanced concepts related to channels.</p><div><hr></div><h1>Go channels revisited</h1><p>So far, we have seen the basic usages of channels&#8212;this section presents the definition and the usage of <code>nil</code> channels, signal channels, and buffered channels.</p><p>Although channels seem like an interesting concept, they are not the answer to every concurrency problem as there exist times when they can be replaced by mutexes and shared memory. So, <em><strong>do not force the use of channels</strong></em>.</p><p>It helps to remember that the zero value of the channel type is <code>nil</code>, and if you send a message to a closed channel, the program panics. However, if you try to read from a closed channel, you get the zero value of the type of that channel. So, after closing a channel, you can no longer write to it but you can still read from it. To be able to close a channel, the channel must not be receive-only.</p><p>Additionally, a <code>nil</code> channel always blocks, which means that both reading and writing from <code>nil</code> channels block. This property of channels can be very useful when you want to disable a branch of a <code>select</code> statement by assigning the <code>nil</code> value to a channel variable. Finally, if you try to close a <code>nil</code> channel, your program is going to panic. This is best illustrated in the <code>closeNil.go</code> program:</p><pre><code><code>package main
func main() {
    var c chan string</code></code></pre><p>The previous statement defines a <code>nil</code> channel named <code>c</code> of the type <code>string</code>.</p><pre><code><code>    close(c)
}</code></code></pre><p>Running <code>closeNil.go</code> generates the following output:</p><pre><code><code>panic: close of nil channel
goroutine 1 [running]:
main.main()
    ~/go/src/github.com/mactsouk/mGo4th/ch08/closeNil.go:5 +0x20
exit status 2</code></code></pre><p>The previous output shows the message you are going to get if you try to close a <code>nil</code> channel. Let us now discuss buffered channels.</p><h2>Buffered channels</h2><p>Unlike unbuffered channels, which have a capacity of 0 and require a sender to have a corresponding receiver ready at the other end, buffered channels allow a certain number of values to be sent into the channel before a receiver is needed.</p><p>These channels allow us to put jobs in a queue quickly to be able to deal with more requests and process requests later on. Moreover, you can use buffered channels as semaphores to limit the throughput of your application.</p><p>The presented technique works as follows: all incoming requests are forwarded to a channel, which processes them one by one. When the channel is done processing a request, it sends a message to the original caller saying that it is ready to process a new one. So, <em><strong>the capacity of the buffer of the channel restricts the number of simultaneous requests that it can keep</strong></em>. Bear in mind that it is not the channel that processes the requests or sends the messages.</p><p>Also, bear in mind that a buffered channel keeps accepting data until it blocks due to its limited capacity. However, in the presented example, the implementation is what cancels remaining requests after the channel buffer is full due to the <code>select</code> statement, not the channel itself. The source file that implements the technique is named <code>bufChannel.go</code> and contains the following code:</p><pre><code><code>package main
import (
    "fmt"
)
func main() {
    numbers := make(chan int, 5)</code></code></pre><p>The <code>numbers</code> channel can store up to five integers because it is a buffer channel with a capacity of 5.</p><pre><code><code>    counter := 10
    for i := 0; i &lt; counter; i++ {
        select {
        // This is where the processing takes place
        case numbers &lt;- i * i:
            fmt.Println("About to process", i)
        default:
            fmt.Print("No space for ", i, " ")
        }</code></code></pre><p>We begin putting data into <code>numbers</code>&#8212;however, when the channel is full, it is not going to store more data and the <code>default</code> branch is going to be executed. This is not because of the way channels work but because of the specific implementation with <code>select</code>.</p><pre><code><code>    }
    fmt.Println()
    for {
        select {
        case num := &lt;-numbers:
            fmt.Print("*", num, " ")
        default:
            fmt.Println("Nothing left to read!")
            return
        }
    }
}</code></code></pre><p>Similarly, we try to read data from numbers using a <code>for</code> loop. When all data from the channel is read, the <code>default</code> branch is going to be executed and will terminate the program with its <code>return</code> statement&#8212;when <code>main()</code> returns, the entire program will be terminated.</p><p>Running <code>bufChannel.go</code> produces the following output:</p><pre><code><code>$ go run bufChannel.go 
About to process 0
. . .
About to process 4
No space for 5 No space for 6 No space for 7 No space for 8 No space for 9 
*0 *1 *4 *9 *16 Nothing left to read!
</code></code></pre><p>Let us now discuss <code>nil</code> channels.</p><h2>nil channels</h2><p><code>nil</code> channels <em><strong>always block</strong></em>! Therefore, you should use them when you want that behavior on purpose! The code that follows illustrates <code>nil</code> channels:</p><pre><code><code>package main
import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)
var wg sync.WaitGroup</code></code></pre><p>We are making <code>wg</code> a global variable in order to be available from anywhere in the code and avoid passing it to every function that needs it as a parameter. This is not idiomatic Go and some people might dislike that approach, despite its simpler implementation. An alternative would be to declare <code>wg</code> in <code>main()</code> and pass a pointer to each function that needs it&#8212;you can implement that as an exercise.</p><pre><code><code>func add(c chan int) {
    sum := 0
    t := time.NewTimer(time.Second)
    for {
        select {
        case input := &lt;-c:
            sum = sum + input
        case &lt;-t.C:
            c = nil
            fmt.Println(sum)
            wg.Done()
        }
    }
}</code></code></pre><p>The <code>send()</code> function keeps sending random numbers to channel <code>c</code>. Do not confuse channel <code>c</code>, which is a (channel) function parameter, with channel <code>t.C</code>, which is part of timer <code>t</code>&#8212;you can change the name of the <code>c</code> variable but not the name of the <code>C</code> field of a timer. When the time of timer <code>t</code> expires, the timer sends a value to the <code>t.C</code> channel.</p><p>This triggers the execution of the relevant branch of the <code>select</code> statement, which assigns the value <code>nil</code> to channel <code>c</code> and prints the value of the <code>sum</code> variable, and <code>wg.Done()</code> is executed, which is going to unblock <code>wg.Wait()</code> found in the <code>main()</code> function. Additionally, as <code>c</code> becomes <code>nil</code>, it stops/blocks <code>send()</code> from sending more data to it.</p><pre><code><code>func send(c chan int) {
    for {
        c &lt;- rand.Intn(10)
    }
}
func main() {
    c := make(chan int)
    rand.Seed(time.Now().Unix())
    wg.Add(1)
    go add(c)
    go send(c)
    wg.Wait()
}</code></code></pre><p>Running <code>nilChannel.go</code> produces the following output:</p><pre><code><code>$ go run nilChannel.go 
11168960</code></code></pre><p>Since the number of times that the first branch of the <code>select</code> statement in <code>add()</code> is going to be executed is not fixed, you get different results each time you execute <code>nilChannel.go</code>.</p><p>The next subsection discusses worker pools.</p><h2>Worker pools</h2><p>A worker pool is a <em><strong>set of threads that process jobs assigned to them</strong></em>. The Apache web server and the <code>net/http</code> package of Go more or less work this way: the main process accepts all incoming requests, which are forwarded to worker processes to get served. Once a worker process has finished its job, it is ready to serve a new client.</p><p>As Go does not have threads, the presented implementation is going to use goroutines instead of threads. Additionally, threads do not usually die after serving a request because the cost of ending a thread and creating a new one is too high, whereas goroutines do die after finishing their job. Worker pools in Go are implemented with the help of buffered channels, as they allow you to limit the number of goroutines running at the same time.</p><p>The presented utility implements a simple task: it processes integers and prints their square values using a single goroutine for serving each request. The code of <code>wPools.go</code> is as follows:</p><pre><code><code>package main
import (
    "fmt"
    "os"
    "runtime"
    "strconv"
    "sync"
    "time"
)
type Client struct {
    id      int
    integer int
}</code></code></pre><p>The <code>Client</code> structure is used for keeping track of the requests that the program is going to process.</p><pre><code><code>type Result struct {
    job    Client
    square int
}</code></code></pre><p>The <code>Result</code> structure is used for keeping the data of each <code>Client</code> as well as the results generated by the client. Put simply, the <code>Client</code> structure holds the input data of each request, whereas <code>Result</code> holds the results of a request&#8212;if you want to process more complex data, you should modify these structures.</p><pre><code><code>var size = runtime.GOMAXPROCS(0)
var clients = make(chan Client, size)
var data = make(chan Result, size)</code></code></pre><p>The <code>clients</code> and <code>data</code> buffered channels are used to get new client requests and write the results, respectively. If you want your program to run faster, you can increase the value of <code>size</code>.</p><pre><code><code>func worker(wg *sync.WaitGroup) {
    for c := range clients {
        square := c.integer * c.integer
        output := Result{c, square}
        data &lt;- output
        time.Sleep(time.Second)
    }
    wg.Done()
}</code></code></pre><p>The <code>worker()</code> function processes requests by reading the <code>clients</code> channel. Once the processing is complete, the result is written to the <code>data</code> channel. The delay that is introduced with <code>time.Sleep()</code> is not necessary but it gives you a better sense of the way that the generated output is printed.</p><pre><code><code>func create(n int) {
    for i := 0; i &lt; n; i++ {
        c := Client{i, i}
        clients &lt;- c
    }
    close(clients)
}</code></code></pre><p>The purpose of the <code>create()</code> function is to create all requests properly and then send them to the <code>clients</code> buffered channel for processing. Note that the <code>clients</code> channel is read by <code>worker()</code>.</p><pre><code><code>func main() {
    if len(os.Args) != 3 {
        fmt.Println("Need #jobs and #workers!")
        return
    }
    nJobs, err := strconv.Atoi(os.Args[1])
    if err != nil {
        fmt.Println(err)
        return
    }
    nWorkers, err := strconv.Atoi(os.Args[2])
    if err != nil {
        fmt.Println(err)
        return
    }</code></code></pre><p>In the preceding code, you read the command line parameters that define the number of jobs and workers. If the number of jobs is greater than the number of workers, the jobs are served in smaller chunks.</p><pre><code><code>    go create(nJobs)</code></code></pre><p>The <code>create()</code> call mimics the client requests that you are going to process.</p><pre><code><code>    finished := make(chan interface{})</code></code></pre><p>The <code>finished</code> channel is used for blocking the program and, therefore, needs no particular data type.</p><pre><code><code>    go func() {
        for d := range data {
            fmt.Printf("Client ID: %d\tint: ", d.job.id)
            fmt.Printf("%d\tsquare: %d\n", d.job.integer, d.square)
        }
        finished &lt;- true</code></code></pre><p>The <code>finished &lt;- true</code> statement is used for unblocking the program as soon as the <code>for</code> <code>range</code> loop ends. The <code>for range</code> loop ends when the <code>data</code> channel is closed, which happens after <code>wg.Wait()</code>, which means after all workers have finished.</p><pre><code><code>    }()
    var wg sync.WaitGroup
    for i := 0; i &lt; nWorkers; i++ {
        wg.Add(1)
        go worker(&amp;wg)
    }
    wg.Wait()
    close(data)</code></code></pre><p>The purpose of the previous <code>for</code> loop is to generate the required number of <code>worker()</code> goroutines to process all requests.</p><pre><code><code>    fmt.Printf("Finished: %v\n", &lt;-finished)
}</code></code></pre><p>The <code>&lt;-finished</code> statement in <code>fmt.Printf()</code> blocks until the <code>finished</code> channel is closed.</p><p>Running <code>wPools.go</code> creates the following kind of output:</p><pre><code><code>$ go run wPools.go 8 5
Client ID: 0    int: 0    square: 0
Client ID: 1    int: 1    square: 1
Client ID: 2    int: 2    square: 4
Client ID: 3    int: 3    square: 9
Client ID: 4    int: 4    square: 16
Client ID: 5    int: 5    square: 25
Client ID: 6    int: 6    square: 36
Finished: true</code></code></pre><p>The previous output shows that all requests were processed. This technique allows you to serve a given number of requests, which saves you from server overload. The price you pay for that is having to write more code.</p><p>The next subsection introduces signal channels and shows a technique for using them to define the order of execution for a small number of goroutines.</p><h2>Signal channels</h2><p>A signal channel is one that is used just for signaling. Put simply, you can use a signal channel when you want to inform another goroutine about something. Signal channels should not be used for data transferring. You are going to see signal channels in action in the next subsection where we specify the order of execution of goroutines.</p><h2>Specifying the order of execution for your goroutines</h2><p>This subsection presents a technique for specifying the order of execution of goroutines using signal channels. However, keep in mind that this technique works best when you are dealing with a small number of goroutines. The presented code example has four goroutines that we want to execute in the desired order&#8212;first, the goroutine for function <code>A()</code>, then function <code>B()</code>, then <code>C()</code>, and finally, <code>D()</code>.</p><p>The code of <code>defineOrder.go</code> without the <code>package</code> statement and <code>import</code> block is the following:</p><pre><code><code>var wg sync.WaitGroup
func A(a, b chan struct{}) {
    &lt;-a
    fmt.Println("A()!")
    time.Sleep(time.Second)
    close(b)
}</code></code></pre><p>Function <code>A()</code> is going to be blocked until channel <code>a</code>, which is passed as a parameter, is closed. Just before it ends, it closes channel <code>b</code>, which is passed as a parameter. This is going to unblock the next goroutine, which is going to be function <code>B()</code>.</p><pre><code><code>func B(a, b chan struct{}) {
    &lt;-a
    fmt.Println("B()!")
    time.Sleep(3 * time.Second)
    close(b)
}</code></code></pre><p>Similarly, function <code>B()</code> is going to be blocked until channel <code>a</code>, which is passed as a parameter, is closed. Just before <code>B()</code> ends, it closes channel <code>b</code>, which is passed as a parameter. As before, this is going to unblock the following function:</p><pre><code><code>func C(a, b chan struct{}) {
    &lt;-a
    fmt.Println("C()!")
    close(b)
}</code></code></pre><p>As it happened with functions <code>A()</code> and <code>B()</code>, the execution of function <code>C()</code> is blocked by channel <code>a</code>. Just before it ends, it closes channel <code>b</code>.</p><pre><code><code>func D(a chan struct{}) {
    &lt;-a
    fmt.Println("D()!")
    wg.Done()
}</code></code></pre><p>This is the last function that is going to be executed. Therefore, although it is blocked, it does not close any channels before exiting. Additionally, being the last function means that it can be executed more than once, which is not true for functions <code>A()</code>, <code>B()</code>, and <code>C()</code> because a channel can be closed only once.</p><pre><code><code>func main() {
    x := make(chan struct{})
    y := make(chan struct{})
    z := make(chan struct{})
    w := make(chan struct{})</code></code></pre><p>We need to have as many channels as the number of functions we want to execute as goroutines.</p><pre><code><code>    wg.Add(1)
    go func() {
        D(w)
    }()</code></code></pre><p>This proves that the order of execution dictated by the Go code does not matter as <code>D()</code> is going to be executed last.</p><pre><code><code>    wg.Add(1)
    go func() {
        D(w)
    }()
    go A(x, y)
    wg.Add(1)
    go func() {
        D(w)
    }()
    go C(z, w)
    go B(y, z)</code></code></pre><p>Although we run <code>C()</code> before <code>B()</code>, <code>C()</code> is going to finish after <code>B()</code> has finished.</p><pre><code><code>    wg.Add(1)
    go func() {
        D(w)
    }()
    // This triggers the process
    close(x)</code></code></pre><p>The closing of the first channel is what triggers the execution of the goroutines because this unblocks <code>A()</code>.</p><pre><code><code>    wg.Wait()
}</code></code></pre><p>Running <code>defineOrder.go</code> produces the following output:</p><pre><code><code>$ go run defineOrder.go
A()!
B()!
C()!
D()! D()! D()! D()!</code></code></pre><p>So, the four functions, which are executed as goroutines, are executed in the desired order, and, in the case of the last function, the desired number of times.</p><h1>Handling UNIX signals</h1><p>UNIX signals offer a very handy way of interacting asynchronously with applications and server processes. UNIX signal handling in Go requires the use of channels that are used exclusively for this task. The presented program handles <code>SIGINT</code> (which is called <code>syscall.SIGINT</code> in Go) and <code>SIGINFO</code> separately and uses a <code>default</code> case in a <code>switch</code> block for handling the remaining signals. The implementation of that <code>switch</code> block allows you to differentiate between the various signals according to your needs.</p><p>There exists a dedicated channel that receives all signals, as defined by the <code>signal.Notify()</code> function. Go channels can have a capacity&#8212;the capacity of this particular channel is 1 in order to be able to receive and keep <em><strong>one signal at a time</strong></em>. This makes perfect sense as a signal can terminate a program and there is no need to try to handle another signal at the same time. There is usually an anonymous function that is executed as a goroutine and performs the signal handling and nothing else. The main task of that goroutine is to listen to the channel for data. Once a signal is received, it is sent to that channel, read by the goroutine, and stored into a variable&#8212;at this point, the channel can receive more signals. That variable is processed by a <code>switch</code> statement.</p><p>Some signals cannot be caught, and the operating system cannot ignore them. So, the <code>SIGKILL</code> and <code>SIGSTOP</code> signals cannot be blocked, caught, or ignored; the reason for this is that they allow privileged users as well as the UNIX kernel to terminate any process they desire.</p><p>Create a text file by typing the following code&#8212;a good filename for it would be <code>signals.go</code>.</p><pre><code><code>package main
import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)
func handleSignal(sig os.Signal) {
    fmt.Println("handleSignal() Caught:", sig)
}</code></code></pre><p><code>handleSignal()</code> is a separate function for handling signals. However, you can also handle signals inline, in the branches of a <code>switch</code> statement.</p><pre><code><code>func main() {
    fmt.Printf("Process ID: %d\n", os.Getpid())
    sigs := make(chan os.Signal, 1)</code></code></pre><p>We create a channel with data of the type <code>os.Signal</code> because all channels must have a type.</p><pre><code><code>    signal.Notify(sigs)</code></code></pre><p>The previous statement means handling all signals that can be handled.</p><pre><code><code>    start := time.Now()
    go func() {
        for {
            sig := &lt;-sigs</code></code></pre><p>Wait until you read data (&lt;-) from the <code>sigs</code> channel and store it in the <code>sig</code> variable.</p><pre><code><code>            switch sig {</code></code></pre><p>Depending on the read value, act accordingly. This is how you differentiate between signals.</p><pre><code><code>            case syscall.SIGINT:
                duration := time.Since(start)
                fmt.Println("Execution time:", duration)</code></code></pre><p>For the handling of <code>syscall.SIGINT</code>, we calculate the time that has passed since the beginning of the program execution and print it on the screen.</p><pre><code><code>            case syscall.SIGINFO:
                handleSignal(sig)</code></code></pre><p>The code of the <code>syscall.SIGINFO</code> case calls the <code>handleSignal()</code> function&#8212;it is up to the developer to decide on the details of the implementation.</p><p>On Linux machines, you should replace <code>syscall.SIGINFO</code> with another signal such as <code>syscall.SIGUSR1</code> or <code>syscall.SIGUSR2</code> because <code>syscall.SIGINFO</code> is not available on Linux (<a href="https://github.com/golang/go/issues/1653">https://github.com/golang/go/issues/1653</a>).</p><pre><code><code>                // do not use return here because the goroutine exits
                // but the time.Sleep() will continue to work!
                os.Exit(0)
            default:
                fmt.Println("Caught:", sig)
            }</code></code></pre><p>If there is no match, the <code>default</code> case handles the rest of the values and just prints a message.</p><pre><code><code>        }
    }()
    for {
        fmt.Print("+")
        time.Sleep(10 * time.Second)
    }
}</code></code></pre><p>The endless for loop at the end of the <code>main()</code> function is for emulating the operation of a real program. Without an endless <code>for</code> loop, the program exits almost immediately.</p><p>Running <code>signals.go</code> and interacting with it creates the following kind of output:</p><pre><code><code>$ go run signals.go
Process ID: 70153
+^CExecution time: 631.533125ms
+Caught: user defined signal 1
+Caught: urgent I/O condition
+signal: killed</code></code></pre><p>The second line of output was generated by pressing <em>Ctrl</em> + <em>C</em> on the keyboard, which, on UNIX machines, sends the <code>syscall.SIGINT</code> signal to the program. The third line of output was caused by executing <code>kill -USR1 74252</code> on a different terminal. The last line of the output was generated by the <code>kill -9 74252</code> command. As the <code>KILL</code> signal (which is also represented by the number 9) cannot be handled, it terminates the program, and the shell prints the <code>killed</code> message.</p><h2>Handling two signals</h2><p>If you want to handle a limited number of signals instead of all of them, you should replace the <code>signal.Notify(sigs)</code> statement with a statement like the following:</p><pre><code><code>signal.Notify(sigs, syscall.SIGINT, syscall.SIGINFO)</code></code></pre><p>After that, you need to make the appropriate changes to the code of the goroutine responsible for signal handling in order to identify and handle <code>syscall.SIGINT</code> and <code>syscall.SIGINFO</code>&#8212;the current version (<code>signals.go</code>) already handles both of them.</p><p>The next section talks about shared memory and shared variables, which is a very handy way of making goroutines communicate with each other with the use of channels.</p><div><hr></div><h1>Shared memory and shared variables</h1><p>Shared memory and shared variables are huge topics in concurrent programming and the most common ways for UNIX threads to communicate with each other. The same principles apply to Go and goroutines, which is what this section is about. <em><strong>A mutex variable, which is the abbreviation for a mutual exclusion variable, is mainly used for thread synchronization and for protecting shared data when multiple writes or a write and a read can occur at the same time</strong></em>. A mutex works like a buffered channel with a capacity of one, which allows, at most, one goroutine to access a shared variable at any given time. This means that there is no way for two or more goroutines to be able to update that variable simultaneously. Go offers the <code>sync.Mutex</code> and <code>sync.RWMutex</code> data types.</p><p>A <em>critical section</em> of a concurrent program is the code that cannot be executed simultaneously by all processes, threads, or, in this case, goroutines. It is the code that needs to be protected by mutexes. Therefore, identifying the critical sections of your code makes the whole programming process so much simpler that you should pay particular attention to this task. A critical section cannot be embedded into another critical section when both critical sections use the same <code>sync.Mutex</code> or <code>sync.RWMutex</code> variable. However, <em><strong>avoid at almost any cost the spreading of mutexes across functions because that makes it really hard to see whether you are embedding or not</strong></em>.</p><h2>The sync.Mutex type</h2><p>The <code>sync.Mutex</code> type is the Go implementation of a mutex. Its definition, which can be found in the <code>mutex.go</code> file of the <code>sync</code> directory, is as follows (you do not need to know the definition of <code>sync.Mutex</code> in order to use it):</p><pre><code><code>type Mutex struct {
    state int32
    sema  uint32
}</code></code></pre><p>The definition of <code>sync.Mutex</code> is nothing special. All of the interesting work is done by the <code>sync.Lock()</code> and <code>sync.Unlock()</code> functions, which can lock and unlock a <code>sync.Mutex</code> variable, respectively. Locking a mutex means that nobody else can lock it until it has been released using the <code>sync.Unlock()</code> function. All these are illustrated in <code>mutex.go</code>, which contains the following code:</p><pre><code><code>package main
import (
    "fmt"
    "os"
    "strconv"
    "sync"
    "time"
)
var m sync.Mutex
var v1 int
func change() {
    m.Lock()
    defer m.Unlock()</code></code></pre><p>This function makes changes to the value of <code>v1</code>. The critical section begins here.</p><pre><code><code>    time.Sleep(time.Second)
    v1 = v1 + 1
    if v1 == 10 {
        v1 = 0
        fmt.Print("* ")
    }</code></code></pre><p>This is the end of the critical section. Now, another goroutine can lock the mutex.</p><pre><code><code>}
func read() int {
    m.Lock()
    a := v1
    defer m.Unlock()
    return a
}</code></code></pre><p>This function is used for reading the value of <code>v1</code>&#8212;therefore, it should use a mutex to make the process concurrently safe. Most specifically, we want to make sure that nobody is going to change the value of <code>v1</code> while we are reading it. The rest of the program contains the implementation of the <code>main()</code> function&#8212;feel free to see the entire code of <code>mutex.go</code> in the GitHub repository of the book.</p><p>Running <code>mutex.go</code> produces the following output:</p><pre><code><code>$ go run -race mutex.go 10
0 -&gt; 1-&gt; 2-&gt; 3-&gt; 4-&gt; 5-&gt; 6-&gt; 7-&gt; 8-&gt; 9* -&gt; 0-&gt; 0</code></code></pre><p>The previous output shows that due to the use of a mutex, goroutines cannot access shared data and, therefore, there are no hidden race conditions.</p><p>The next subsection shows what could happen if we forget to unlock a mutex.</p><h2>What happens if you forget to unlock a mutex?</h2><p>Forgetting to unlock a <code>sync.Mutex</code> mutex creates a panic situation, even in the simplest kind of a program. The same applies to the <code>sync.RWMutex</code> mutex, which is presented in the next section. Let us now see a code example to understand this unpleasant situation a lot better&#8212;this is part of <code>forgetMutex.go</code>:</p><pre><code><code>var m sync.Mutex
var w sync.WaitGroup
func function() {
    m.Lock()
    fmt.Println("Locked!")
}</code></code></pre><p>Here, we lock a mutex without releasing it afterward. This means that if we run <code>function()</code> as a goroutine more than once, all instances after the first one are going to be blocked waiting to <code>Lock()</code> the shared mutex. In our case, we run two goroutines&#8212;feel free to see the entire code of <code>forgetMutex.go</code> for more details. Running <code>forgetMutex.go</code> generates the following output:</p><pre><code><code>Locked!
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x140000021a0?)
    /opt/homebrew/Cellar/go/1.21.0/libexec/src/runtime/sema.go:62 +0x2c
sync.(*WaitGroup).Wait(0x100fa1710)
    /opt/homebrew/Cellar/go/1.21.0/libexec/src/sync/waitgroup.go:116 +0x74
main.main()
    ~/go/src/github.com/mactsouk/mGo4th/ch08/forgetMutex.go:29 +0x5c
goroutine 34 [sync.Mutex.Lock]:
sync.runtime_SemacquireMutex(0x0?, 0x0?, 0x0?)
    /opt/homebrew/Cellar/go/1.21.0/libexec/src/runtime/sema.go:77 +0x28
sync.(*Mutex).lockSlow(0x100fa1520)
    /opt/homebrew/Cellar/go/1.21.0/libexec/src/sync/mutex.go:171 +0x174
sync.(*Mutex).Lock(...)
    /opt/homebrew/Cellar/go/1.21.0/libexec/src/sync/mutex.go:90
main.function()
    ~/go/src/github.com/mactsouk/mGo4th/ch08/forgetMutex.go:12 +0x84
main.main.func1()
    ~/go/src/github.com/mactsouk/mGo4th/ch08/forgetMutex.go:20 +0x50
created by main.main in goroutine 1
    ~/go/src/github.com/mactsouk/mGo4th/ch08/forgetMutex.go:18 +0x34
exit status 2</code></code></pre><p>As expected, the program crashes because of the deadlock. To avoid such situations, always remember to unlock any mutexes created in your program as soon as possible.</p><p>Let us now discuss <code>sync.RWMutex</code>, which is an improved version of <code>sync.Mutex</code>.</p><h2>The sync.RWMutex type</h2><p>The <code>sync.RWMutex</code> data type is an improved version of <code>sync.Mutex</code> and is defined in the <code>rwmutex.go</code> file of the <code>sync</code> directory of the Go Standard library as follows:</p><pre><code><code>type RWMutex struct {
    w           Mutex
    writerSem   uint32
    readerSem   uint32
    readerCount int32
    readerWait  int32
}</code></code></pre><p>In other words, <code>sync.RWMutex</code> is based on <code>sync.Mutex</code> with the necessary additions and improvements. So, you might ask, how does <code>sync.RWMutex</code> improve <code>sync.Mutex</code>? Although a single function is allowed to perform write operations with a <code>sync.RWMutex</code> mutex, you can have multiple readers owning a <code>sync.RWMutex</code> mutex&#8212;this means that read operations are usually faster with <code>sync.RWMutex</code>. However, there is one important detail that you should be aware of: until all of the readers of a <code>sync.RWMutex</code> mutex unlock it, you cannot lock it for writing, which is the small price you have to pay for the performance improvement you get for allowing multiple readers.</p><p>The functions that can help you work with <code>sync.RWMutex</code> are <code>RLock()</code> and <code>RUnlock()</code>, which are used for locking and unlocking the mutex for reading purposes, respectively. The <code>Lock()</code> and <code>Unlock()</code> functions used in <code>sync.Mutex</code> should still be used when you want to lock and unlock a <code>sync.RWMutex</code> mutex for writing purposes. Finally, it should be apparent that you should not make changes to any shared variables inside an <code>RLock()</code> and <code>RUnlock()</code> block of code.</p><p>All these are illustrated in <code>rwMutex.go</code>&#8212;the important code is the following:</p><pre><code><code>var Password *secret
var wg sync.WaitGroup
type secret struct {
    RWM      sync.RWMutex
    password string
}</code></code></pre><p>This is the shared variable of the program&#8212;you can share any type of variable you want.</p><pre><code><code>func Change(pass string) {
    if Password == nil {
        fmt.Println("Password is nil!")
        return
    }
    fmt.Println("Change() function")
    Password.RWM.Lock()</code></code></pre><p>This is the beginning of the critical section.</p><pre><code><code>    fmt.Println("Change() Locked")
    time.Sleep(4 * time.Second)
    Password.password = pass
    Password.RWM.Unlock()</code></code></pre><p>This is the end of the critical section.</p><pre><code><code>    fmt.Println("Change() UnLocked")
}</code></code></pre><p>The <code>Change()</code> function makes changes to the shared variable <code>Password</code> and, therefore, needs to use the <code>Lock()</code> function, which can be held by a single writer only.</p><pre><code><code>func show () {
    defer wg.Done()
    defer Password.RWM.RUnlock()
    Password.RWM.RLock()
    fmt.Println("Show function locked!")
    time.Sleep(2 * time.Second)
    fmt.Println("Pass value:", Password.password)
}</code></code></pre><p>The <code>show()</code> function reads the shared variable <code>Password</code> and therefore it is allowed to use the <code>RLock()</code> function, which can be held by multiple readers. Inside <code>main()</code>, three <code>show()</code> functions are executed as goroutines before a call to the <code>Change()</code> function, which also runs as a goroutine. The key point here is that no race conditions are going to happen. Running <code>rwMutex.go</code> produces the following output:</p><pre><code><code>$ go run rwMutex.go
Change() function</code></code></pre><p>The <code>Change()</code> function is executed but cannot acquire the mutex because it is already taken by one or more <code>show()</code> goroutines.</p><pre><code><code>Show function locked!
Show function locked!</code></code></pre><p>The previous output verifies that two <code>show()</code> goroutines have successfully taken the mutex for reading.</p><pre><code><code>Change() function</code></code></pre><p>Here, we can see a second <code>Change()</code> function running and waiting to get the mutex.</p><pre><code><code>Pass value: myPass
Pass value: myPass</code></code></pre><p>This is the output from the two <code>show()</code> goroutines.</p><pre><code><code>Change() Locked
Change() UnLocked</code></code></pre><p>Here we see that one <code>Change()</code> goroutine finishes its job.</p><pre><code><code>Show function locked!
Pass value: 54321</code></code></pre><p>After that another <code>show()</code> goroutine finishes.</p><pre><code><code>Change() Locked
Change() UnLocked
Current password value: 123456</code></code></pre><p>Lastly, the second <code>Change()</code> goroutine finishes. The last output line is for making sure that the password value has changed&#8212;please look at the full code of <code>rwMutex.go</code> for more details.</p><p>Bear in mind that the output you are going to get might be different due to the way the scheduler works. This is the nature of concurrent programming and these programs do not have any mechanism to ensure the <code>show()</code> function should be scheduled first.</p><p>The next subsection discusses the use of the <code>atomic</code> package for avoiding race conditions.</p><h2>The atomic package</h2><p>An atomic operation is an operation that is completed in a single step relative to other threads or, in this case, to other goroutines. <em><strong>This means that an atomic operation cannot be interrupted in the middle of it.</strong></em> The Go Standard library offers the <code>atomic</code> package, which, in some simple cases, can help you avoid using a mutex. With the <code>atomic</code> package, you can have atomic counters accessed by multiple goroutines without synchronization issues and without worrying about race conditions. However, mutexes are more versatile than atomic operations.</p><p>As illustrated in the code that follows, when using an atomic variable, all reading and writing operations of an atomic variable must be done using the functions provided by the <code>atomic</code> package in order to avoid race conditions.</p><p>The code in <code>atomic.go</code> is as follows, which is made smaller by hardcoding some values:</p><pre><code><code>package main
import (
    "fmt"
    "sync"
    "sync/atomic"
)
type atomCounter struct {
    val int64
}</code></code></pre><p>This is a structure for holding the desired <code>int64</code> atomic variable.</p><pre><code><code>func (c *atomCounter) Value() int64 {
    return atomic.LoadInt64(&amp;c.val)
}</code></code></pre><p>This is a helper function that returns the current value of an <code>int64</code> atomic variable using <code>atomic.LoadInt64()</code>.</p><pre><code><code>func main() {
    X := 100
    Y := 4
    var waitGroup sync.WaitGroup
    counter := atomCounter{}
    for i := 0; i &lt; X; i++ {</code></code></pre><p>We are creating lots of goroutines that change the shared variable&#8212;as stated before, the use of the <code>atomic</code> package for working with the shared variable offers a simple way of avoiding race conditions when changing the value of the shared variable.</p><pre><code><code>        waitGroup.Add(1)
        go func() {
            defer waitGroup.Done()
            for i := 0; i &lt; Y; i++ {
                atomic.AddInt64(&amp;counter.val, 1)
            }</code></code></pre><p>The <code>atomic.AddInt64()</code> function changes the value of the <code>val</code> field of the <code>counter</code> structure variable in a safe way.</p><pre><code><code>        }()
    }
    waitGroup.Wait()
    fmt.Println(counter.Value())
}</code></code></pre><p>Running <code>atomic.go</code> while checking for race conditions produces the following kind of output:</p><pre><code><code>$ go run -race atomic.go
400</code></code></pre><p>So, the atomic variable is modified by multiple goroutines without any issues.</p><p>The next subsection shows how to share memory using goroutines.</p><h2>Sharing memory using goroutines</h2><p>This subsection illustrates how to share data using a dedicated goroutine. Although shared memory is the traditional way that threads communicate with each other, Go comes with built-in synchronization features that allow a single goroutine to own a shared piece of data. This means that other goroutines must send messages to this single goroutine that owns the shared data, which prevents the corruption of the data. Such a goroutine is called a monitor goroutine. In Go terminology, <em><strong>this is sharing by communicating instead of communicating by sharing</strong></em>.</p><p>Personally, I prefer to use a monitor goroutine instead of traditional shared memory techniques because the implementation with the monitor goroutine is safer, closer to the Go philosophy, and easier to understand.</p><p>The logic of the program can be found in the implementation of the <code>monitor()</code> function. More specifically, the <code>select</code> statement orchestrates the operation of the entire program. When you have a read request, the <code>read()</code> function attempts to read from the <code>readValue</code> channel, which is controlled by the <code>monitor()</code> function.</p><p>This returns the current value of the value variable. On the other hand, when you want to change the stored value, you call <code>set()</code>. This writes to the <code>writeValue</code> channel, which is also handled by the same <code>select</code> statement. As a result, no one can deal with the shared variable without using the <code>monitor()</code> function, which is in charge.</p><p>The code of <code>monitor.go</code> is as follows:</p><pre><code><code>package main
import (
    "fmt"
    "math/rand"
    "os"
    "strconv"
    "sync"
    "time"
)
var readValue = make(chan int)
var writeValue = make(chan int)
func set(newValue int) {
    writeValue &lt;- newValue
}</code></code></pre><p>This function sends data to the <code>writeValue</code> channel.</p><pre><code><code>func read() int {
    return &lt;-readValue
}</code></code></pre><p>When the <code>read()</code> function is called, it reads from the <code>readValue</code> channel&#8212;this reading happens inside the <code>monitor()</code> function.</p><pre><code><code>func monitor() {
    var value int
    for {
        select {
        case newValue := &lt;-writeValue:
            value = newValue
            fmt.Printf("%d ", value)
        case readValue &lt;- value:
        }
    }
}</code></code></pre><p>The <code>monitor()</code> function contains the logic of the program with the endless <code>for</code> loop and the <code>select</code> statement. The first case receives data from the <code>writeValue</code> channel, sets the <code>value</code> variable accordingly, and prints that new value. The second case sends the value of the <code>value</code> variable to the <code>readValue</code> channel. As all traffic goes through <code>monitor()</code> and its <code>select</code> block, there is no way to have a race condition because there is a single instance of <code>monitor()</code> running.</p><pre><code><code>func main() {
    if len(os.Args) != 2 {
        fmt.Println("Please give an integer!")
        return
    }
    n, err := strconv.Atoi(os.Args[1])
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("Going to create %d random numbers.\n", n)
    rand.Seed(time.Now().Unix())
    go monitor()</code></code></pre><p>It is important that the <code>monitor()</code> function is executed first because that is the goroutine that orchestrates the flow of the program.</p><pre><code><code>    var wg sync.WaitGroup
    for r := 0; r &lt; n; r++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            set(rand.Intn(10 * n))
        }()
    }</code></code></pre><p>When the <code>for</code> loop ends, it means that we have created the desired number of random numbers.</p><pre><code><code>    wg.Wait()
    fmt.Printf("\nLast value: %d\n", read())
}</code></code></pre><p>Lastly, we wait for all <code>set()</code> goroutines to finish before printing the last random number.</p><p>Running <code>monitor.go</code> produces the following output:</p><pre><code><code>$ go run monitor.go 10
Going to create 10 random numbers.
98 22 5 84 20 26 45 36 0 16 
Last value: 16</code></code></pre><p>So, 10 random numbers are created by 10 goroutines and all these goroutines send their output to the <code>monitor()</code> function, which is also executed as a goroutine. Apart from receiving the results, the <code>monitor()</code> function prints them on the screen, so all this output is generated by <code>monitor()</code>.</p><p>The next section discusses the <code>go</code> statement in more detail.</p><div><hr></div><h1>Closured variables and the go statement</h1><p>In this section, we are going to talk about closured variables, which are variables inside closures, and the <code>go</code> statement. Notice that closured variables in goroutines are evaluated when the goroutine actually runs and when the <code>go</code> statement is executed in order to create a new goroutine. This means that closured variables are going to be replaced by their values when the Go scheduler decides to execute the relevant code. This is illustrated in the <code>main()</code> function of <code>goClosure.go</code>:</p><pre><code><code>func main() {
    for i := 0; i &lt;= 20; i++ {
        go func() {
            fmt.Print(i, " ")
        }()
    }
    time.Sleep(time.Second)
    fmt.Println()
}</code></code></pre><p>Running <code>goClosure.go</code> produces the following output:</p><pre><code><code>$ go run goClosure.go 
3 7 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21</code></code></pre><p>The program mostly prints the number 21, which is the last value of the variable of the <code>for</code> loop, and not the other numbers. As <code>i</code> is a closured variable, it is evaluated at the time of execution. As the goroutines begin but wait for the Go scheduler to allow them to get executed, the <code>for</code> loop ends, so the value of <code>i</code> that is being used is 21. Lastly, the same issue also applies to Go channels, so be careful.</p><p>Running <code>goClosure.go</code> with the Go race detector reveals the issue:</p><pre><code><code>$ go run -race goClosure.go
5 4 5 5 ==================
WARNING: DATA RACE
Read at 0x00c00011e028 by goroutine 6:
  main.main.func1()
      ~/go/src/github.com/mactsouk/mGo4th/ch08/goClosure.go:11 +0x34
Previous write at 0x00c00011e028 by main goroutine:
  main.main()
      ~/go/src/github.com/mactsouk/mGo4th/ch08/goClosure.go:9 +0x5c
Goroutine 6 (running) created at:
  main.main()
      ~/go/src/github.com/mactsouk/mGo4th/ch08/goClosure.go:10 +0x44
==================
8 8 6 10 12 11 15 15 15 18 20 20 21 15 21 21 21
Found 1 data race(s)
exit status 66</code></code></pre><p>Now, let us correct <code>goClosure.go</code> and present it to you&#8212;the new name is <code>goClosureCorrect.go</code> and its <code>main()</code> function is as follows:</p><pre><code><code>func main() {
    for i := 0; i &lt;= 20; i++ {
        i := i
        go func() {
            fmt.Print(i, " ")
        }()
    }</code></code></pre><p>This is one way of correcting the issue. The valid yet bizarre <code>i := i</code> statement creates a new instance of the variable for the goroutine that holds the correct value. Although this is a valid approach, this kind of variable shadowing is not considered a good practice.</p><p>Variable shadowing in Go occurs when a variable declared in a nested scope has the same name as a variable in an outer scope. While variable shadowing can be intentional and useful in certain situations, it can also lead to confusion and introduce subtle bugs. In practice, it is recommended to avoid unnecessary variable shadowing and choose meaningful variable names that minimize the likelihood of unintentional shadowing.</p><pre><code><code>    time.Sleep(time.Second)
    fmt.Println()
    for i := 0; i &lt;= 20; i++ {
        go func(x int) {
            fmt.Print(x, " ")
        }(i)
    }</code></code></pre><p>This is a totally different way of correcting the race condition: pass the current value of <code>i</code> to the anonymous function as a parameter and everything is OK. As explained in <em>Chapter 15</em>, <em>Changes in Recent Go Versions</em>, this issue does not exist in Go 1.22.</p><pre><code><code>    time.Sleep(time.Second)
    fmt.Println()
}</code></code></pre><p>Testing <code>goClosureCorrect.go</code> with the race detector generates the expected output:</p><pre><code><code>$ go run -race goClosureCorrect.go
0 1 2 4 3 5 6 9 8 7 10 11 13 12 14 16 15 17 18 20 19
0 1 2 3 4 5 6 7 8 10 9 12 13 11 14 15 16 17 18 19 20</code></code></pre><p>The next section presents the functionality of the <code>context</code> package.</p><div><hr></div><h1>The context package</h1><p>The main purpose of the <code>context</code> package is to define the <code>Context</code> type and support cancellation. Yes, you heard that right; there are times when, for some reason, you want to abandon what you are doing. However, it would be very helpful to be able to include some extra information about your cancellation decisions. The <code>context</code> package allows you to do exactly that.</p><p>If you take a look at the source code of the <code>context</code> package, you will realize that its implementation is pretty simple&#8212;even the implementation of the <code>Context</code> type is pretty simple, yet the <code>context</code> package is very important.</p><p>The <code>Context</code> type is an interface with four methods: <code>Deadline()</code>, <code>Done()</code>, <code>Err()</code>, and <code>Value()</code>. The good news is that you do not need to implement all of these functions of the <code>Context</code> interface&#8212;you just need to modify a <code>Context</code> variable using methods such as <code>context.WithCancel()</code>, <code>context.WithDeadline()</code>, and <code>context.WithTimeout()</code>.</p><p>All three of these methods return a derived <code>Context</code> (the child) and a <code>CancelFunc()</code> function. Calling the <code>CancelFunc()</code> function removes the parent&#8217;s reference to the child and stops any associated timers. As a side effect, this means that the Go garbage collector is free to garbage collect the child goroutines that no longer have associated parent goroutines. For garbage collection to work correctly, the parent goroutine needs to keep a reference to each child goroutine. If a child goroutine ends without the parent knowing about it, then a memory leak occurs until the parent is canceled as well.</p><p>The example that follows showcases the use of the <code>context</code> package. The program contains four functions, including the <code>main()</code> function. Functions <code>f1()</code>, <code>f2()</code>, and <code>f3()</code> each require just one parameter (which is a time delay) because everything else they need is defined inside their function body. In this example, we use <code>context.Background()</code> to initialize an empty <code>Context</code>. The other function that can create an empty <code>Context</code> is <code>context.TODO()</code>, which is presented later on in this chapter.</p><pre><code><code>package main
import (
    "context"
    "fmt"
    "os"
    "strconv"
    "time"
)
func f1(t int) {
    c1 := context.Background()
    c1, cancel := context.WithCancel(c1)
    defer cancel()</code></code></pre><p>The <code>WithCancel()</code> method returns a copy of the parent context with a new <code>Done</code> channel. Notice that the <code>cancel</code> variable, which is a function, is one of the return values of <code>context.CancelFunc()</code>. The <code>context.WithCancel()</code> function uses an existing <code>Context</code> and creates a child with cancellation. The <code>context.WithCancel()</code> function also returns a <code>Done</code> channel that can be closed, either when the <code>cancel()</code> function is called, as shown in the preceding code, or when the <code>Done</code> channel of the parent context is closed.</p><pre><code><code>    go func() {
        time.Sleep(4 * time.Second)
        cancel()
    }()
    select {
    case &lt;-c1.Done():
        fmt.Println("f1() Done:", c1.Err())
        return
    case r := &lt;-time.After(time.Duration(t) * time.Second):
        fmt.Println("f1():", r)
    }
    return
}</code></code></pre><p>The <code>f1()</code> function creates and executes a goroutine. The <code>time.Sleep()</code> call simulates the time it would take a real goroutine to do its job. In this case, it is 4 seconds, but you can put any time period you want. If the <code>c1</code> context calls the <code>Done()</code> function in less than 4 seconds, the goroutine will not have enough time to finish.</p><pre><code><code>func f2(t int) {
    c2 := context.Background()
    c2, cancel := context.WithTimeout(c2, time.Duration(t)*time.Second)
    defer cancel()</code></code></pre><p>The cancel variable in <code>f2()</code> comes from <code>context.WithTimeout()</code>, which requires two parameters: a <code>Context</code> parameter and a <code>time.Duration</code> parameter. When the timeout period expires, the <code>cancel()</code> function is called automatically.</p><pre><code><code>    go func() {
        time.Sleep(4 * time.Second)
        cancel()
    }()
    select {
    case &lt;-c2.Done():
        fmt.Println("f2() Done:", c2.Err())
        return
    case r := &lt;-time.After(time.Duration(t) * time.Second):
        fmt.Println("f2():", r)
    }
    return
}
func f3(t int) {
    c3 := context.Background()
    deadline := time.Now().Add(time.Duration(2*t) * time.Second)
    c3, cancel := context.WithDeadline(c3, deadline)
    defer cancel()</code></code></pre><p>The <code>cancel</code> variable in <code>f3()</code> comes from <code>context.WithDeadline()</code>, which requires two parameters: a <code>Context</code> variable and a time in the future that signifies the deadline of the operation. When the deadline passes, the <code>cancel()</code> function is called automatically.</p><pre><code><code>    go func() {
        time.Sleep(4 * time.Second)
        cancel()
    }()
    select {
    case &lt;-c3.Done():
        fmt.Println("f3() Done:", c3.Err())
        return
    case r := &lt;-time.After(time.Duration(t) * time.Second):
        fmt.Println("f3():", r)
    }
    return
}</code></code></pre><p>The logic of <code>f3()</code> is the same as in <code>f1()</code> and <code>f2()</code>&#8212;the <code>select</code> block orchestrates the process.</p><pre><code><code>func main() {
    if len(os.Args) != 2 {
        fmt.Println("Need a delay!")
        return
    }
    delay, err := strconv.Atoi(os.Args[1])
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("Delay:", delay)
    f1(delay)
    f2(delay)
    f3(delay)
}</code></code></pre><p>The three functions are executed in sequence by the <code>main()</code> function. Running <code>useContext.go</code> produces the following kind of output:</p><pre><code><code>$ go run useContext.go 3
Delay: 3
f1(): 2023-08-28 16:23:22.300595 +0300 EEST m=+3.001225751
f2(): 2023-08-28 16:23:25.302122 +0300 EEST m=+6.002730959
f3(): 2023-08-28 16:23:28.303326 +0300 EEST m=+9.00391262</code></code></pre><p>The long lines of the output are the return values of <code>time.After()</code> and show the times that <code>After()</code> sent the current time on the returned channel. All of them denote a normal operation of the program.</p><p>If you define a bigger delay, then the output is going to be similar to the following:</p><pre><code><code>$ go run useContext.go 13
Delay: 13
f1() Done: context canceled
f2() Done: context canceled
f3() Done: context canceled</code></code></pre><p>The point here is that the operation of the program is canceled when there are delays in its execution.</p><h2>About context.WithCancelCause</h2><p>The <code>context.WithCancelCause()</code> method was introduced in Go 1.21. Its main advantage is that it gives you customization capabilities, which are not offered by the other methods of the <code>context</code> package. Apart from that, it behaves like <code>WithCancel()</code>.</p><p>Similar to <code>context.WithCancelCause()</code>, there exists <code>context.WithTimeoutCause()</code> and <code>context.WithDeadlineCause()</code>.</p><p>The <code>withCancelCause.go</code> program illustrates the use of <code>context.WithCancelCause()</code>.</p><pre><code><code>func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancelCause(ctx)
    cancel(errors.New("Canceled by timeout"))
    err := takingTooLong(ctx)
    if err != nil {
        fmt.Println(err)
        return
    }
}</code></code></pre><p>The implementation of <code>main()</code> contains two important elements. First, we call <code>context.WithCancelCause()</code>, which returns a context, and a <code>CancelCauseFunc()</code> function, which behaves like <code>CancelFunc()</code> while allowing us to define and customize the cancellation cause giving clearer context to the error situation&#8212;in this case, the cancellation cause is defined as <code>errors.New("Canceled by timeout")</code>. After that, we call <code>takingTooLong()</code> with the context that we have just defined. If <code>takingTooLong()</code> returns an error that is not <code>nil</code>, we print that error.</p><pre><code><code>func takingTooLong(ctx context.Context) error {
    select {
    case &lt;-time.After(3 * time.Second):
        fmt.Println("Done!")
        return nil
    case &lt;-ctx.Done():
        fmt.Println("Canceled!")
        return context.Cause(ctx)
    }
}</code></code></pre><p>The previous function returns either <code>nil</code> or <code>context.Cause(ctx)</code>.</p><p>Running <code>withCancelCause.go</code> produces the following output:</p><pre><code><code>$ go run withCancelCause.go
Canceled!
Canceled by timeout
</code></code></pre><p>So, as the second part of the <code>select</code> block is executed, <code>takingTooLong()</code> prints <code>Canceled!</code> and <code>main()</code> prints the cancellation reason according to the initialization of <code>context.WithCancelCause()</code>.</p><p>We are not completely done with <code>context</code> as the next chapter is going to use it to timeout HTTP interactions on the client side of the connection. The next section discusses the <code>semaphore</code> package, which is not part of the standard library.</p><div><hr></div><h1>The semaphore package</h1><p>This last section of this chapter presents the <code>semaphore</code> package, which is provided by the Go team. A semaphore is a construct that can limit or control the access to a shared resource. As we are talking about Go, <em><strong>a semaphore can limit the access of goroutines to a shared resource</strong></em> but, originally, semaphores were used for limiting access to threads. Semaphores can have weights that limit the number of threads or goroutines that can have access to a resource.</p><p>The process is supported via the <code>Acquire()</code> and <code>Release()</code> methods, which are defined as follows:</p><pre><code><code>func (s *Weighted) Acquire(ctx context.Context, n int64) error
func (s *Weighted) Release(n int64)</code></code></pre><p>The second parameter of <code>Acquire()</code> defines the weight of the semaphore. As we are going to use an external package, we need to put the code inside <code>~/go/src</code> in order to use Go modules: <code>~/go/src/github.com/mactsouk/mGo4th/ch08/semaphore</code>.</p><p>Now, let us present the code of <code>semaphore.go</code>, which shows an implementation of a worker pool using semaphores:</p><pre><code><code>package main
import (
    "context"
    "fmt"
    "os"
    "strconv"
    "time"
    "golang.org/x/sync/semaphore"
)
var Workers = 4</code></code></pre><p>The <code>Workers</code> variable specifies the maximum number of goroutines that can be executed by this program.</p><pre><code><code>var sem = semaphore.NewWeighted(int64(Workers))</code></code></pre><p>This is where we define the semaphore with a weight identical to the maximum number of goroutines that can be executed concurrently. This means that no more than <code>Workers</code> goroutines can acquire the semaphore at the same time.</p><pre><code><code>func worker(n int) int {
    square := n * n
    time.Sleep(time.Second)
    return square
}</code></code></pre><p>The <code>worker()</code> function is run as part of a goroutine. However, as we are using a semaphore, there is no need to return the results to a channel.</p><pre><code><code>func main() {
    if len(os.Args) != 2 {
        fmt.Println("Need #jobs!")
        return
    }
    nJobs, err := strconv.Atoi(os.Args[1])
    if err != nil {
        fmt.Println(err)
        return
    }</code></code></pre><p>The previous code reads the number of jobs that we want to run.</p><pre><code><code>    // Where to store the results
    var results = make([]int, nJobs)
    // Needed by Acquire()
    ctx := context.TODO()
    for i := range results {
        err = sem.Acquire(ctx, 1)
        if err != nil {
            fmt.Println("Cannot acquire semaphore:", err)
            break
        }</code></code></pre><p>In this part, we try to acquire the semaphore as many times as the number of jobs defined by <code>nJobs</code>. If <code>nJobs</code> is bigger than <code>Workers</code>, then the <code>Acquire()</code> call is going to block and wait for <code>Release()</code> calls in order to unblock.</p><pre><code><code>        go func(i int) {
            defer sem.Release(1)
            temp := worker(i)
            results[i] = temp
        }(i)
    }</code></code></pre><p>This is where we run the goroutines that do the job and write the results to the <code>results</code> slice. As each goroutine writes to a different slice element, there are not any race conditions.</p><pre><code><code>    err = sem.Acquire(ctx, int64(Workers))
    if err != nil {
        fmt.Println(err)
    }</code></code></pre><p>This is a clever trick: we acquire all of the tokens so that the <code>sem.Acquire()</code> call blocks until all workers/goroutines have finished. This is similar in functionality to a <code>Wait()</code> call.</p><pre><code><code>    for k, v := range results {
        fmt.Println(k, "-&gt;", v)
    }
}</code></code></pre><p>The last part of the program is about printing the results. After writing the code, we need to run the following commands to get the required Go modules:</p><pre><code><code>$ go mod init
$ go mod tidy
$ mod download golang.org/x/sync</code></code></pre><p>Apart from the first command, these commands were indicated by the output of <code>go mod init</code>, so you do not have to remember anything.</p><p>Lastly, running <code>semaphore.go</code> produces the following output:</p><pre><code><code>$ go run semaphore.go 3
0 -&gt; 0
1 -&gt; 1
2 -&gt; 4</code></code></pre><p>Each line in the output shows the input value and the output value separated by -&gt;. The use of the semaphore keeps things in order.</p><div><hr></div><h1>Making the statistics application concurrent</h1><p>In this section of this chapter, we are going to convert the statistics application into a concurrent application that uses goroutines. However, instead of using channels, we are going to use a different approach that prevents deadlocks, while making the overall design of the program much simpler. Apart from that, there is also a version of <code>stats.go</code> named <code>statsNC.go</code> that does not create any goroutines and processes the input files sequentially.</p><p>We are only going to present the implementation of the <code>main()</code> function of <code>stats.go</code> because this is where the logic of the utility is found. However, minor additional changes exist for taking advantage of goroutines. The most time-consuming part of <code>stats.go</code> is the normalization of the time series.</p><p>What is impressive is that we converted <code>stats.go</code> into a concurrent application using a minimal amount of changes that mainly have to do with goroutine synchronization&#8212;this is a good indication of great design.</p><p>The implementation of <code>main()</code> is the following:</p><pre><code><code>func main() {
    if len(os.Args) == 1 {
        fmt.Println("Need one or more file paths!")
        return
    }
    var waitGroup sync.WaitGroup
    files = make(DFslice, len(os.Args))</code></code></pre><p>So far, we have a <code>sync.WaitGroup</code> variable for synchronizing the goroutines. Additionally, we have a slice variable named <code>files</code> that has as many elements as the length of the <code>os.Args</code> slice&#8212;<code>files[0]</code> is not going to be used.</p><p>The remaining code of <code>main()</code> is the following:</p><pre><code><code>    for i := 1; i &lt; len(os.Args); i++ {
        waitGroup.Add(1)
        go func(x int) {
            process(os.Args[x], x)
            defer waitGroup.Done()
        }(i)
    }
    waitGroup.Wait()
}</code></code></pre><p>What do we have here? There is an anonymous function that runs as a goroutine. That anonymous function requires a single parameter, which is <em><strong>the index of the command line argument that is being processed</strong></em>. There is a handy property that comes with that index: that index is unique, which means that we can use that unique index when we put data into the <code>files</code> slice&#8212;this process takes place inside <code>process()</code>. This resolves any potential race conditions as each goroutine uses a different place in <code>files</code>. Keep in mind that <code>files[0]</code> is not being used but we have decided to make <code>files</code> bigger than needed in order to put the data for the first command line argument in <code>files[1]</code>, and so on.</p><p>Apart from that, we use <code>sync</code> to wait for all goroutines to finish before exiting the program.</p><p>In order to compare <code>stats.go</code> with <code>statsNC.go</code>, we are going to use bigger datasets, which are all stored in the <code>./ch08/dataset</code> directory. The sizes of the three data files can be seen in the following output:</p><pre><code><code>$ wc dataset/*
 1518653 1518653 4119086 dataset/1.5M
 2531086 2531086 6918628 dataset/2.5M
 4049739 4049739 11037714 dataset/4.0M
 8099478 8099478 22075428 total</code></code></pre><p>A quick and dirty way to calculate the execution time of a program is using the <code>time(1)</code> UNIX utility. Using that utility, we are going to compare the execution time of <code>./ch05/stats.go</code> with <code>./ch05/stats.go</code> and see what happens:</p><pre><code><code>$ time go run stats.go ./dataset/* ./dataset/* ./dataset/*
real    0m1.240s
user    0m6.259s
sys     0m0.528s
$ time go run statsNC.go ./dataset/* ./dataset/* ./dataset/*
real    0m3.267s
user    0m7.766s
sys     0m0.535s</code></code></pre><p>What is important in the output is the values in the lines that begin with <code>real</code>. The concurrent version is about <em><strong>three times faster</strong></em> than the non-concurrent version when processing nine files. Imagine using even bigger datasets and having to process 1,000 datasets instead of just nine!</p><div><hr></div><h1>Summary</h1><p>In this important chapter, we talked about Go concurrency, goroutines, channels, the <code>select</code> keyword, shared memory, and mutexes, as well as timing out goroutines and the use of the <code>context</code> package. Bear in mind that although goroutines can process data and execute commands, they cannot communicate with each other directly but they can communicate in other ways, including channels, local sockets, and shared memory.</p><p>Remember that OS threads are controlled by the OS scheduler, whereas goroutines executed in one or more OS threads are controlled by the Go runtime. The correct terminology for when a goroutine or an OS thread is executed and then paused is <em>context-switched on and off</em>, respectively. Keep in mind that the Go scheduler checks the global queue from time to time in order to find out whether there are any goroutines waiting to be assigned to a local queue. If both the global queue and a given local queue are empty, then <em>work-stealing</em> takes place.</p><p>The main advantage of concurrency is that it allows the splitting of bigger tasks into smaller ones and the execution of each smaller task concurrently. Additionally, concurrency does a great job in distributing multiple HTTP requests among different goroutines. Lastly, concurrency makes better use of modern CPUs with multiple cores and virtual environments. However, concurrency adds complexity to the software design and the code, which affects readability and maintainability. For that reason, you might need to add concurrency last in your code, as we did with the statistics application. One other concern of concurrency is the risk of consuming all available resources making other services unreliable or even unavailable. Lastly, concurrent code is harder to benchmark&#8212;if you want to compare two concurrent implementations, it is better to compare their sequential versions that tell more about the actual algorithms and code efficiency.</p><p>What is important to remember is that the rational use of concurrency and goroutines is going to allow you to write powerful Go applications. Feel free to experiment with the concepts and the examples of this chapter to better understand goroutines, channels, and shared memory.</p><p>The next chapter is all about web services and working with the HTTP protocol in Go. Among other things, we are going to convert the statistics application into a web service.</p><div><hr></div><h1>Exercises</h1><ul><li><p>Try to implement a concurrent version of <code>wc(1)</code> that uses a buffered channel.</p></li><li><p>Try to implement a concurrent version of <code>wc(1)</code> that uses shared memory.</p></li><li><p>Try to implement a concurrent version of <code>wc(1)</code> that uses semaphores.</p></li><li><p>Try to implement a concurrent version of <code>wc(1)</code> that saves its output to a file.</p></li></ul><div><hr></div><p>To dive deeper into concurrency design, profiling techniques, generics, and Go&#8217;s evolving runtime, check out <em><strong><a href="https://www.packtpub.com/en-in/product/mastering-go-9781805127147">Mastering Go, Fourth Edition</a></strong></em> by Mihalis Tsoukalos, available from Packt. This 740-page guide is fully updated with coverage of web services, TCP/IP, REST APIs, fuzz testing, and observability, and pairs detailed explanations with real-world exercises to help you build high-performance servers, robust command-line tools, and production-grade Go systems.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-ch/product/mastering-go-9781805122647" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VBef!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!VBef!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!VBef!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!VBef!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VBef!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775" width="422" height="520.5439560439561" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:422,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Mastering Go&quot;,&quot;title&quot;:&quot;Mastering Go&quot;,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-ch/product/mastering-go-9781805122647&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Mastering Go" title="Mastering Go" srcset="https://substackcdn.com/image/fetch/$s_!VBef!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!VBef!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!VBef!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!VBef!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here is what some readers have said:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!o3Vt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!o3Vt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 424w, https://substackcdn.com/image/fetch/$s_!o3Vt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 848w, https://substackcdn.com/image/fetch/$s_!o3Vt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 1272w, https://substackcdn.com/image/fetch/$s_!o3Vt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!o3Vt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png" width="1067" height="341" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:341,&quot;width&quot;:1067,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:72150,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/171441031?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!o3Vt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 424w, https://substackcdn.com/image/fetch/$s_!o3Vt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 848w, https://substackcdn.com/image/fetch/$s_!o3Vt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 1272w, https://substackcdn.com/image/fetch/$s_!o3Vt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6xEb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6xEb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 424w, https://substackcdn.com/image/fetch/$s_!6xEb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 848w, https://substackcdn.com/image/fetch/$s_!6xEb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 1272w, https://substackcdn.com/image/fetch/$s_!6xEb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6xEb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png" width="1073" height="435" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/64b48c91-d866-4a96-befe-521fea38005c_1073x435.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:435,&quot;width&quot;:1073,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:98847,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/171441031?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!6xEb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 424w, https://substackcdn.com/image/fetch/$s_!6xEb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 848w, https://substackcdn.com/image/fetch/$s_!6xEb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 1272w, https://substackcdn.com/image/fetch/$s_!6xEb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!f0qJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!f0qJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 424w, https://substackcdn.com/image/fetch/$s_!f0qJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 848w, https://substackcdn.com/image/fetch/$s_!f0qJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 1272w, https://substackcdn.com/image/fetch/$s_!f0qJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!f0qJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png" width="1072" height="375" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:375,&quot;width&quot;:1072,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82503,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://deepengineering.substack.com/i/171441031?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!f0qJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 424w, https://substackcdn.com/image/fetch/$s_!f0qJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 848w, https://substackcdn.com/image/fetch/$s_!f0qJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 1272w, https://substackcdn.com/image/fetch/$s_!f0qJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>]]></content:encoded></item></channel></rss>