<?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[slys.dev]]></title><description><![CDATA[For depth-first engineers - the ones who go down, not around. AI is moving the easy work down the stack; the judgment above it stays yours. Build the three literacies it can't replace: Coding, Systems, and AI Literacy, from first principles.]]></description><link>https://iam.slys.dev</link><image><url>https://substackcdn.com/image/fetch/$s_!9q0E!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3b67508-e73c-434b-89ae-fde2c72e8fa4_256x256.png</url><title>slys.dev</title><link>https://iam.slys.dev</link></image><generator>Substack</generator><lastBuildDate>Fri, 21 Aug 2026 23:45:41 GMT</lastBuildDate><atom:link href="https://iam.slys.dev/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Anna & Jakub Slys]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[jakub@slys.dev]]></webMaster><itunes:owner><itunes:email><![CDATA[jakub@slys.dev]]></itunes:email><itunes:name><![CDATA[Jakub Slys]]></itunes:name></itunes:owner><itunes:author><![CDATA[Jakub Slys]]></itunes:author><googleplay:owner><![CDATA[jakub@slys.dev]]></googleplay:owner><googleplay:email><![CDATA[jakub@slys.dev]]></googleplay:email><googleplay:author><![CDATA[Jakub Slys]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Random Forests — wisdom of the crowd in Machine Learning]]></title><description><![CDATA[AI Literacy]]></description><link>https://iam.slys.dev/p/random-forests-wisdom-of-the-crowd</link><guid isPermaLink="false">https://iam.slys.dev/p/random-forests-wisdom-of-the-crowd</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 17 Aug 2026 20:28:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!N-e7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff880929e-e0a7-4c42-8211-451f3f267186_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N-e7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff880929e-e0a7-4c42-8211-451f3f267186_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N-e7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff880929e-e0a7-4c42-8211-451f3f267186_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!N-e7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff880929e-e0a7-4c42-8211-451f3f267186_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!N-e7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff880929e-e0a7-4c42-8211-451f3f267186_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!N-e7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff880929e-e0a7-4c42-8211-451f3f267186_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N-e7!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff880929e-e0a7-4c42-8211-451f3f267186_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f880929e-e0a7-4c42-8211-451f3f267186_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/042cab71-e5cb-4f51-a286-1d00105878bc_1536x1024.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Headline image&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;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="Headline image" title="Headline image" srcset="https://substackcdn.com/image/fetch/$s_!N-e7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff880929e-e0a7-4c42-8211-451f3f267186_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!N-e7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff880929e-e0a7-4c42-8211-451f3f267186_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!N-e7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff880929e-e0a7-4c42-8211-451f3f267186_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!N-e7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff880929e-e0a7-4c42-8211-451f3f267186_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p style="text-align: justify;">There&#8217;s a particular kind of relief you feel when you stop trying to be <em>&#8220;the genius&#8221;</em> and instead try to be <em>&#8220;the organizer of good judgments&#8221;</em>.</p><p style="text-align: justify;">You see it in everyday decisions. Picking a restaurant in a new neighborhood: one confident friend might push you into the loud, trendy place that photographs well but tastes like disappointment. A small group of friends - none of them perfect, some of them picky in different ways - often lands on something reliably good. Debugging a weird production issue: the one person who speaks first may sound persuasive, but a handful of engineers each tugging on a different thread (logs, metrics, recent deploys, networking, database behavior) is usually what gets you to the truth. Estimating a project timeline: the <em>&#8220;optimist with certainty&#8221;</em> is common; the <em>&#8220;group that remembers different past failures&#8221;</em> is useful.</p><p style="text-align: justify;">Random forests are that pattern, turned into an algorithm.</p><p style="text-align: justify;">A single decision tree can feel like a sharp mind: crisp rules, confident splits, clean explanations. And on training data, it often looks brilliant. But that brilliance is fragile - because a tree can accidentally turn a coincidence into a law. The random forest idea is to take many trees - each one trained under slightly different constraints - so that they disagree in productive ways. Then you combine them, and the disagreement cancels out a lot of the brittleness.</p><p style="text-align: justify;">This post is about building a mental model you can actually use: not <em>&#8220;random forest = many trees&#8221;</em>, but <em>&#8220;random forest = engineered diversity + aggregation&#8221;</em>. If you understand that, the knobs, the trade-offs, and even the limitations become much easier to reason about without superstition.</p><div><hr></div><p style="text-align: justify;"><strong>&#127795; If you've ever wondered why "</strong><em><strong>just use a random forest</strong></em><strong>" is such reliable advice on tabular data, this is the mental model - pass it to someone still fighting a single overfit tree.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/random-forests-wisdom-of-the-crowd?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/random-forests-wisdom-of-the-crowd?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h1>A messy jury beats a single &#8220;genius&#8221;</h1><p style="text-align: justify;">Imagine you&#8217;re making a tough call: where to eat tonight, whether a bug is caused by caching or a race condition, how long a migration will take. One person speaks with confidence and tells a coherent story. It&#8217;s tempting to follow them - not because they&#8217;re correct, but because coherence is psychologically satisfying. A single narrative feels like understanding.</p><p style="text-align: justify;">Now picture a small group instead. Each person has blind spots. One person always underestimates time. Another overweights aesthetics. Someone else is allergic to risk. But crucially, they&#8217;re not all wrong in the same direction. When you average their perspectives, you often get something calmer: fewer extreme decisions, fewer <em>&#8220;we bet everything on one story&#8221;</em> outcomes. The group&#8217;s strength isn&#8217;t that any member is perfect; it&#8217;s that their imperfections are varied.</p><p style="text-align: justify;">That&#8217;s the core intuition behind random forests. A decision tree is a highly flexible model. It can carve the input space into lots of little regions and give each region its own prediction. If you let it, it will happily build a long chain of <em>&#8220;if this, then that&#8221;</em> rules that perfectly explain your training set. The problem is that the world doesn&#8217;t owe you the same quirks tomorrow.</p><p style="text-align: justify;">A random forest takes many <em>&#8220;pretty good&#8221;</em> trees, encourages them to be different, and then combines their outputs. The combination step is important, but it&#8217;s not the whole story. The real magic is that the trees are pushed to become <em>different kinds of wrong</em>. Once you have that, averaging (for regression) or voting (for classification) turns the forest into a more reliable decision-maker than any single tree you could point to as <em>&#8220;the best one&#8221;</em>.</p><h1>Why a single decision tree feels smart &#8212; and why it often isn&#8217;t</h1><p style="text-align: justify;">Decision trees are one of the most beginner-friendly models in machine learning, and that&#8217;s not an accident. They match the way humans naturally explain decisions: <em>&#8220;If the user is new and their first session is short, show onboarding; otherwise, show the dashboard&#8221;</em>. The logic is local, discrete, and narratable. You can draw it. You can walk a teammate through it. You can look at a split and argue about whether it <em>&#8220;makes sense&#8221;</em>.</p><p style="text-align: justify;">They&#8217;re also expressive. A tree can model non-linear relationships without you manually inventing feature interactions. It can handle mixed feature types (continuous, categorical, binary) with minimal preprocessing. And if you keep splitting, it can fit complicated patterns very quickly.</p><p style="text-align: justify;">The trap is that the same flexibility that makes trees expressive also makes them eager to <em>over-explain</em> the training data.</p><p style="text-align: justify;">A tree is built by repeatedly choosing splits that improve some purity criterion (like reducing Gini impurity or entropy in classification, or reducing squared error in regression). Each split is a locally greedy choice. If you keep going deep, the tree keeps finding ways to isolate small pockets of the training set. Eventually, it can create leaves that contain only a handful of points - or even one point. At that depth, the tree is no longer learning a general rule; it&#8217;s learning a memory.</p><p style="text-align: justify;">And memories feel like rules when you look at them as a flowchart.</p><p style="text-align: justify;">This is why single trees are often high-variance models: small changes in the training data can produce a noticeably different tree. Change a few samples, and a key split might flip from <em>&#8220;square footage&#8221;</em> to <em>&#8220;zipcode&#8221;</em>, which then changes everything downstream. The tree didn&#8217;t become <em>&#8220;worse&#8221;</em> because it&#8217;s weak; it became fragile because it&#8217;s too responsive to the particularities of what it saw.</p><p style="text-align: justify;">A forest keeps the appealing part - trees can capture complex patterns - while directly attacking that fragility.</p><h1>The real problem isn&#8217;t weakness &#8212; it&#8217;s sameness</h1><p style="text-align: justify;">Ensembles are sometimes described as <em>&#8220;many weak learners make a strong learner&#8221;</em>. That&#8217;s not wrong, but it can hide the more important point: combining models only helps if they don&#8217;t all make the <em>same mistakes</em>.</p><p style="text-align: justify;">If you have ten people estimating a project timeline, and they all use the same flawed assumption (<em>&#8220;the API team will respond within a day&#8221;</em>), averaging their estimates doesn&#8217;t fix the assumption. You just get a more confident wrong number. Volume is not wisdom. Diversity is.</p><p style="text-align: justify;">In random forests, diversity matters because aggregation only reduces the parts of error that are not perfectly aligned across trees. If every tree latches onto the same misleading shortcut in the training data, voting and averaging just amplify that shortcut.</p><p style="text-align: justify;">It&#8217;s worth making this intuition a little more concrete with a small piece of math - not as a proof to memorize, but as a way to see <em>why correlation of errors is the real enemy</em>.</p><h2>A small variance calculation (with numbers you can redo)</h2><p style="text-align: justify;">Suppose each tree outputs a regression prediction. Write the prediction error of tree i as a random variable <code>X&#7522;</code>. Think of <code>X&#7522;</code> as <em>&#8220;how far off tree i is&#8221;</em> on a typical test point. If we average n trees, the averaged error is:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\bar{X}=\\frac{1}{n}\\sum_i X_i&quot;,&quot;id&quot;:&quot;ZPPZUVGMVB&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">Now we ask: how variable is <code>X&#772;</code>? Start with:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;Var(\\bar{X})= \\frac{1}{n^2}Var(\\sum_i X_i)&quot;,&quot;id&quot;:&quot;AOIRUVNAQJ&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">Let <code>S=&#931;&#7522; X&#7522;</code>. Then:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;Var(S)=\\sum_i Var(X_i)+2\\sum_{i<j}Cov(X_i,X_j)&quot;,&quot;id&quot;:&quot;YPQEVDGBIZ&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">Assume each tree has the same error variance <code>&#963;&#178;</code>, and any pair has correlation <code>&#961;</code>. Then:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{aligned}\nVar(X_i) &amp;= \\sigma^2 \\\\\nCov(X_i,X_j) &amp;= \\rho\\sigma^2\n\\end{aligned}&quot;,&quot;id&quot;:&quot;MCCJPEKCIV&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">There are n variance terms and <code>n(n-1)/2</code> covariance pairs, so:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;Var(S)=n\\sigma^2+n(n-1)\\rho\\sigma^2&quot;,&quot;id&quot;:&quot;NJMSDKRNEJ&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">Divide by <code>n&#178;</code>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;Var(\\bar{X})= \\sigma^2\\frac{1+(n-1)\\rho}{n}&quot;,&quot;id&quot;:&quot;LWIKTVQCEJ&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">Now plug in numbers.</p><p style="text-align: justify;"><strong>Example 1 (independent-ish trees).</strong> Given: <code>&#963;&#178;=9</code>, <code>n=100</code>, <code>&#961;=0</code>.</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{aligned}\nVar(\\bar{X}) &amp;= 9\\cdot\\frac{1+99\\cdot 0}{100} \\\\\nVar(\\bar{X}) &amp;= 9\\cdot\\frac{1}{100} \\\\\nVar(\\bar{X}) &amp;= 0.09\n\\end{aligned}&quot;,&quot;id&quot;:&quot;VQDTMBRBDF&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">So the variance shrinks from <code>9</code> to <code>0.09</code>. That&#8217;s a <code>100&#215;</code> reduction.</p><p style="text-align: justify;"><strong>Example 2 (samey trees).</strong> Given: <code>&#963;&#178;=9</code>, <code>n=100</code>, <code>&#961;=0.2</code>.</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{aligned}\nVar(\\bar{X}) &amp;= 9\\cdot\\frac{1+99\\cdot 0.2}{100} \\\\\nVar(\\bar{X}) &amp;= 9\\cdot\\frac{1+19.8}{100} \\\\\nVar(\\bar{X}) &amp;= 9\\cdot\\frac{20.8}{100} \\\\\nVar(\\bar{X}) &amp;= 1.872\n\\end{aligned}&quot;,&quot;id&quot;:&quot;PRRFXLOIEG&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">Still better than <code>9</code>, but nowhere near <code>0.09</code>. The forest gained far less because the trees were too correlated - too similar in their errors.</p><p style="text-align: justify;">That&#8217;s the heart of random forests: not <em>&#8220;more trees&#8221;</em>, but <em>&#8220;less correlation between trees&#8221;</em>.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jEVC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcbfd1421-d290-4c01-8db3-7c22baa1af14_1530x860.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jEVC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcbfd1421-d290-4c01-8db3-7c22baa1af14_1530x860.png 424w, https://substackcdn.com/image/fetch/$s_!jEVC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcbfd1421-d290-4c01-8db3-7c22baa1af14_1530x860.png 848w, https://substackcdn.com/image/fetch/$s_!jEVC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcbfd1421-d290-4c01-8db3-7c22baa1af14_1530x860.png 1272w, https://substackcdn.com/image/fetch/$s_!jEVC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcbfd1421-d290-4c01-8db3-7c22baa1af14_1530x860.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jEVC!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcbfd1421-d290-4c01-8db3-7c22baa1af14_1530x860.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cbfd1421-d290-4c01-8db3-7c22baa1af14_1530x860.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2285a161-ab68-4ded-94f4-8081ecf094f1_1530x860.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Averaged-error variance vs number of trees, for independent (&#961;=0) and correlated (&#961;=0.2) errors&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;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="Averaged-error variance vs number of trees, for independent (&#961;=0) and correlated (&#961;=0.2) errors" title="Averaged-error variance vs number of trees, for independent (&#961;=0) and correlated (&#961;=0.2) errors" srcset="https://substackcdn.com/image/fetch/$s_!jEVC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcbfd1421-d290-4c01-8db3-7c22baa1af14_1530x860.png 424w, https://substackcdn.com/image/fetch/$s_!jEVC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcbfd1421-d290-4c01-8db3-7c22baa1af14_1530x860.png 848w, https://substackcdn.com/image/fetch/$s_!jEVC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcbfd1421-d290-4c01-8db3-7c22baa1af14_1530x860.png 1272w, https://substackcdn.com/image/fetch/$s_!jEVC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcbfd1421-d290-4c01-8db3-7c22baa1af14_1530x860.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h1>How you get diversity without hand-holding the model</h1><p style="text-align: justify;">So how do we make trees disagree in useful ways, without manually designing different architectures or micromanaging each tree?</p><p style="text-align: justify;">Random forests use two simple sources of randomness that are surprisingly effective.</p><p style="text-align: justify;">First, each tree is trained on a slightly different dataset. You start with your training set of N rows. For each tree, you sample N rows <em>with replacement</em> - so some rows appear multiple times, and some rows are missing. Each tree sees a different slice of <em>&#8220;reality&#8221;</em>, including different quirks. A single outlier might strongly influence one tree but be absent from another tree&#8217;s sample. This naturally creates variation in the learned rules.</p><p style="text-align: justify;">Second, even when two trees see similar data, you prevent them from making the same early decisions by restricting their choices at each split. At a given node, instead of letting the tree consider <em>all</em> features, you randomly choose a subset of features and force the split to be chosen only from that subset. This is a gentle form of <em>&#8220;anti-dominance&#8221;</em>. If one feature is extremely strong (say, a zipcode that correlates with price in the training set), a standard tree will grab it early and build most of its logic downstream from that. Many trees built that way will look alike. By limiting candidate features, you make it harder for a single <em>&#8220;obvious&#8221;</em> feature to hijack the whole forest.</p><p style="text-align: justify;">Once the intuition is clear, it&#8217;s useful to name the standard mechanics:</p><ul><li><p style="text-align: justify;">The row-sampling step is typically called <strong>bootstrap sampling</strong> (and the training sets are <em>&#8220;bootstrap samples&#8221;</em>).</p></li><li><p style="text-align: justify;">The feature-subset-per-split step is the hallmark of <strong>random feature selection</strong> (often controlled by a parameter like <code>max_features</code>).</p></li></ul><p style="text-align: justify;">The reason these work is not mystical randomness. It&#8217;s targeted: both mechanisms reduce correlation between trees, which is exactly what the variance calculation told us we need.</p><h1>From a pile of opinions to one prediction you can trust</h1><p style="text-align: justify;">Diversity gives you a committee. Aggregation turns that committee into a decision.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2lOu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4672b2d4-8518-4c66-8f55-7d65aa1d228f_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2lOu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4672b2d4-8518-4c66-8f55-7d65aa1d228f_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!2lOu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4672b2d4-8518-4c66-8f55-7d65aa1d228f_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!2lOu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4672b2d4-8518-4c66-8f55-7d65aa1d228f_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!2lOu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4672b2d4-8518-4c66-8f55-7d65aa1d228f_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2lOu!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4672b2d4-8518-4c66-8f55-7d65aa1d228f_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4672b2d4-8518-4c66-8f55-7d65aa1d228f_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/49b17070-d420-4fdc-bbed-a7178692e89d_1536x1024.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Infographic&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;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="Infographic" title="Infographic" srcset="https://substackcdn.com/image/fetch/$s_!2lOu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4672b2d4-8518-4c66-8f55-7d65aa1d228f_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!2lOu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4672b2d4-8518-4c66-8f55-7d65aa1d228f_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!2lOu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4672b2d4-8518-4c66-8f55-7d65aa1d228f_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!2lOu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4672b2d4-8518-4c66-8f55-7d65aa1d228f_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p style="text-align: justify;">Random forests aggregate in a very direct way:</p><ul><li><p style="text-align: justify;">For <strong>classification</strong>, each tree outputs a class label, and the forest predicts the class with the most votes (sometimes using predicted probabilities and averaging those).</p></li><li><p style="text-align: justify;">For <strong>regression</strong>, each tree outputs a numeric value, and the forest predicts the average of those values.</p></li></ul><p style="text-align: justify;">If you pause and reflect, both are doing the same conceptual thing: they are dampening the influence of any single tree&#8217;s overconfident, idiosyncratic judgment.</p><p style="text-align: justify;">A deep decision tree can be extremely certain in tiny regions of the feature space, because its leaves may be defined by a long chain of splits that isolate only a few training points. If one of those points is a fluke, the leaf&#8217;s prediction can be wildly off - but the tree will still commit to it. When you average across many trees, those extreme leaf-level quirks tend not to align. One tree&#8217;s <em>&#8220;this house is definitely worth $1.2M&#8221;</em> gets balanced by another tree&#8217;s <em>&#8220;I&#8217;ve never seen this pattern; I&#8217;m closer to $850k&#8221;</em>. The forest ends up less jumpy.</p><p style="text-align: justify;">It&#8217;s also helpful to see the voting effect numerically in a small, reproducible example.</p><h2>Worked example: majority vote can amplify correctness</h2><p style="text-align: justify;">Assume a binary classification task. Suppose each tree is correct with probability p, and (to keep the math simple) the trees&#8217; errors are independent. Let <code>n=11</code> trees, and let <code>K</code> be the number of trees that vote correctly. Then:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;K\\sim Binomial(n,p)&quot;,&quot;id&quot;:&quot;TPARTJYEPV&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">The forest is correct if a majority votes correctly, i.e. <code>K&#8805; 6</code>.</p><p style="text-align: justify;">Take <code>p=0.6</code>. Then:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;P(\\text{forest correct}) =\\sum_{k=6}^{11} \\binom{11}{k} p^k(1-p)^{11-k}&quot;,&quot;id&quot;:&quot;RKHRNYZWEP&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">Let <code>q=1-p=0.4</code>. Now compute each term:</p><ul><li><p style="text-align: justify;"><code>k=6: C(11, 6)=462</code>, <code>0.6&#8310;=0.046656</code>, <code>0.4&#8309;=0.01024</code>, Term <code>=462&#183;0.046656&#183;0.01024&#8776; 0.2207</code></p></li><li><p style="text-align: justify;"><code>k=7: C(11, 7)=330</code>, <code>0.6&#8311;=0.0279936</code>, <code>0.4&#8308;=0.0256</code>, Term <code>&#8776; 0.2365</code></p></li><li><p style="text-align: justify;"><code>k=8: C(11, 8)=165</code>, <code>0.6&#8312;=0.01679616</code>, <code>0.4&#179;=0.064</code>, Term <code>&#8776; 0.1774</code></p></li><li><p style="text-align: justify;"><code>k=9: C(11, 9)=55</code>, <code>0.6&#8313;=0.010077696</code>, <code>0.4&#178;=0.16</code>, Term <code>&#8776; 0.0887</code></p></li><li><p style="text-align: justify;"><code>k=10: C(11, 10)=11</code>, <code>0.6&#185;&#8304;=0.0060466176</code>, <code>0.4&#185;=0.4</code>, Term <code>&#8776; 0.0266</code></p></li><li><p style="text-align: justify;"><code>k=11: C(11, 11)=1</code>, <code>0.6&#185;&#185;=0.00362797056</code>, Term <code>&#8776; 0.0036</code></p></li></ul><p style="text-align: justify;">Sum them:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;0.2207+0.2365+0.1774+0.0887+0.0266+0.0036 \\approx 0.7535&quot;,&quot;id&quot;:&quot;UIZCIPUMUR&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">So we went from a single-tree accuracy of <code>0.6</code> to a forest accuracy of about <code>0.75</code>, <em>because</em> the errors weren&#8217;t aligned. If the trees were highly correlated, this gain would shrink - again reinforcing that sameness is the real problem.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zv9m!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc864cc-62ea-43b5-a18e-be9fc3c6f8f4_1530x860.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zv9m!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc864cc-62ea-43b5-a18e-be9fc3c6f8f4_1530x860.png 424w, https://substackcdn.com/image/fetch/$s_!zv9m!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc864cc-62ea-43b5-a18e-be9fc3c6f8f4_1530x860.png 848w, https://substackcdn.com/image/fetch/$s_!zv9m!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc864cc-62ea-43b5-a18e-be9fc3c6f8f4_1530x860.png 1272w, https://substackcdn.com/image/fetch/$s_!zv9m!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc864cc-62ea-43b5-a18e-be9fc3c6f8f4_1530x860.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zv9m!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc864cc-62ea-43b5-a18e-be9fc3c6f8f4_1530x860.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ecc864cc-62ea-43b5-a18e-be9fc3c6f8f4_1530x860.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Majority-vote accuracy P(forest correct) vs number of independent trees at p=0.6, with n=11 marked&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;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="Majority-vote accuracy P(forest correct) vs number of independent trees at p=0.6, with n=11 marked" title="Majority-vote accuracy P(forest correct) vs number of independent trees at p=0.6, with n=11 marked" srcset="https://substackcdn.com/image/fetch/$s_!zv9m!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc864cc-62ea-43b5-a18e-be9fc3c6f8f4_1530x860.png 424w, https://substackcdn.com/image/fetch/$s_!zv9m!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc864cc-62ea-43b5-a18e-be9fc3c6f8f4_1530x860.png 848w, https://substackcdn.com/image/fetch/$s_!zv9m!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc864cc-62ea-43b5-a18e-be9fc3c6f8f4_1530x860.png 1272w, https://substackcdn.com/image/fetch/$s_!zv9m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fecc864cc-62ea-43b5-a18e-be9fc3c6f8f4_1530x860.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h1>A concrete story: predicting house prices with many imperfect rules</h1><p style="text-align: justify;">Let&#8217;s tell a story rather than do more math.</p><p style="text-align: justify;">Suppose you&#8217;re predicting house prices. Your dataset includes square footage, number of bedrooms, age, a neighborhood label, distance to downtown, school rating, and maybe a few messy proxies like <em>&#8220;has renovated kitchen&#8221;</em> extracted from listings.</p><p style="text-align: justify;">A single decision tree might do something like this: it notices that in the training data, houses labeled <em>&#8220;Neighborhood A&#8221;</em> are expensive. Maybe that neighborhood label is genuinely meaningful. Or maybe you scraped data during a period when only high-end listings from Neighborhood A were active, or perhaps the label is inconsistently applied by agents. The tree doesn&#8217;t know any of that. It just sees a feature that reduces error quickly, so it splits early on neighborhood and commits.</p><p style="text-align: justify;">Downstream, it adds more rules. If Neighborhood A and square footage above 2000, predict $1.1M. If Neighborhood A and square footage below 2000 but renovated kitchen, predict $950k. Soon the leaves become very specific. And because the tree is deep, it can create tiny regions that look extremely <em>&#8220;pure&#8221;</em> in the training set.</p><p style="text-align: justify;">Now imagine you build a forest.</p><p style="text-align: justify;">One tree&#8217;s bootstrap sample happens to include more mid-range Neighborhood A homes. That tree decides neighborhood is less decisive and splits first on square footage. Another tree&#8217;s feature subset at the root doesn&#8217;t even include neighborhood, so it splits on distance to downtown. A third tree splits on school rating. A fourth tree does use neighborhood early, but later splits differ because it sees a slightly different mix of examples.</p><p style="text-align: justify;">So when you feed in a new house - say, a 1900 sq ft home in Neighborhood A with a renovated kitchen - the forest produces a set of opinions. Some trees anchored on neighborhood might still go high. Others anchored on size and distance might be more conservative. The average ends up reflecting a broader set of patterns: it&#8217;s less likely to be dominated by one brittle rule like <em>&#8220;Neighborhood A implies luxury&#8221;</em>, because not every tree was allowed to build that same story.</p><p style="text-align: justify;">This is the <em>&#8220;calmness&#8221;</em> random forests are famous for in tabular data. They&#8217;re not magic; they&#8217;re a system that makes it harder for any single coincidence to become the whole explanation.</p><h1>What &#8220;weak&#8221; really means here (and what it doesn&#8217;t)</h1><p style="text-align: justify;">It&#8217;s easy to get confused by ensemble vocabulary, especially if you&#8217;ve heard phrases like <em>&#8220;weak learners&#8221;</em> in boosting.</p><p style="text-align: justify;">In random forests, the individual trees are often <strong>not</strong> weak in the sense of being shallow or simplistic. In fact, many random forest implementations grow trees quite deep - sometimes until leaves are small or pure, unless you explicitly stop them with parameters like maximum depth or minimum leaf size.</p><p style="text-align: justify;">So why do people talk about weakness at all?</p><p style="text-align: justify;">Because each tree is trained under conditions that make it <em>noisy</em>:</p><ul><li><p style="text-align: justify;">It sees a bootstrap-resampled dataset, so it may miss some important examples and over-see others.</p></li><li><p style="text-align: justify;">At each split, it may be forced to choose among a limited subset of features, which can push it toward a <em>&#8220;pretty good&#8221;</em> split rather than the globally best one.</p></li><li><p style="text-align: justify;">If trees are allowed to grow deep, they can memorize peculiarities of their own resampled data.</p></li></ul><p style="text-align: justify;">A single such tree might be a strange character: confident, specific, and occasionally very wrong. It&#8217;s <em>&#8220;weak&#8221;</em> not because it lacks capacity, but because it&#8217;s not trained to be a stable, globally optimal predictor. It&#8217;s trained to be one member of a crowd.</p><p style="text-align: justify;">This is a subtle but important mental shift. In many ML problems, you try to train one model that is as good as possible given the data. With random forests, you&#8217;re intentionally creating a <em>distribution of models</em> - a set of perspectives - so that aggregation does the stabilizing work.</p><p style="text-align: justify;">The strength is in the system design: the forest uses high-capacity components (trees) but arranges them so that their high variance becomes a resource rather than a liability. That design only pays off when you preserve diversity and then combine predictions in a way that cancels idiosyncrasies.</p><h1>When the crowd fails: limits and trade-offs you should expect</h1><p style="text-align: justify;">Random forests are often a strong default for tabular data, but they&#8217;re not a free lunch. The <em>&#8220;wisdom of the crowd&#8221;</em> metaphor is useful here too, because real crowds have costs and failure modes.</p><p style="text-align: justify;">First, forests can be <strong>heavier</strong> than a single tree. Training involves building many trees, and inference involves running a new example through all of them. If you have hundreds or thousands of trees and strict latency constraints, this can matter. In practice, forests are often still quite usable - but the cost is real, and you should measure it rather than assume it&#8217;s negligible.</p><p style="text-align: justify;">Second, you give up <strong>interpretability</strong>. A single decision tree can be read like a flowchart. You can audit it, argue with it, and sometimes present it to non-technical stakeholders. A forest is a committee. You can inspect individual trees, but the final decision is not <em>&#8220;one path&#8221;</em>. It&#8217;s an aggregate of many paths, and that&#8217;s harder to narrate honestly.</p><p style="text-align: justify;">Third, random forests do not solve <strong>dataset shift</strong> or <strong>missing signal</strong>. If your training data does not represent the deployment environment, a forest will confidently generalize the wrong lessons - just more stably. Similarly, if the signal is extremely subtle relative to noise, diversity cannot invent information. The crowd can cancel noise; it cannot conjure a pattern that isn&#8217;t encoded in the features.</p><p style="text-align: justify;">Finally, forests can struggle in settings where structure matters more than tabular heuristics - think high-dimensional sparse text, images, or problems where smooth, differentiable representation learning is the right tool. You <em>can</em> use forests there, but you&#8217;re often fighting the problem&#8217;s geometry.</p><p style="text-align: justify;">The right expectation is: random forests are a dependable, pragmatic method for many supervised learning tasks, especially on structured data - but they inherit the limitations of <em>&#8220;learning from examples&#8221;</em> and add computational and interpretability costs.</p><h1>The &#8220;more trees is always better&#8221; myth</h1><p style="text-align: justify;">There&#8217;s a common instinct when you first learn about random forests: if 100 trees are good, 1000 must be better, and 10,000 must be amazing.</p><p style="text-align: justify;">The truth is more nuanced, and it mirrors the earlier variance math.</p><p style="text-align: justify;">As you add trees, the forest&#8217;s predictions usually become more stable. In regression, the averaged prediction variance tends to decrease; in classification, the vote becomes less sensitive to any one tree&#8217;s quirks. But this improvement has <strong>diminishing returns</strong>. After some point, each additional tree mostly agrees with the existing crowd, and you&#8217;re paying extra computation to shave off tiny fluctuations.</p><p style="text-align: justify;">Two practical realities show up:</p><ol><li><p style="text-align: justify;"><strong>Validation performance plateaus.</strong> You&#8217;ll often see accuracy (or RMSE, or AUC) improve quickly from, say, 10 to 100 trees, improve a bit more from 100 to 300, and then barely move after that. The exact numbers depend on your dataset, but the shape of the curve is common.</p></li><li><p style="text-align: justify;"><strong>Inference cost is linear in the number of trees.</strong> Doubling trees doubles the number of tree traversals per prediction. If you care about latency or throughput, you can&#8217;t ignore this.</p></li></ol><p style="text-align: justify;">A good habit is to treat <code>n_estimators</code> (number of trees) as primarily a <strong>stability knob</strong>. Add trees until your validation metric is stable and your latency budget is still happy. If you have plenty of compute and you want maximum performance, sure - push it further. But do it with measurement, not faith.</p><p style="text-align: justify;">One subtle point: if your trees are too correlated (for example, because your feature randomness is too low or your data is very dominant along one feature), adding more trees may plateau <em>even earlier</em>. In that case the fix is often not <em>&#8220;more trees&#8221;</em>, but <em>&#8220;less sameness&#8221;</em>.</p><h1>The &#8220;feature importance&#8221; trap: what the forest can and can&#8217;t explain</h1><p style="text-align: justify;">People love asking random forests what they <em>&#8220;learned&#8221;</em>, and feature importance is the most common way this shows up. Most libraries will give you some measure of importance - often based on how much each feature reduces impurity across splits, or based on how much shuffling a feature hurts performance.</p><p style="text-align: justify;">These signals can be genuinely helpful. If a feature consistently matters across many trees, that often indicates it&#8217;s predictive. In messy real-world datasets, feature importance can guide debugging: <em>&#8220;Why is this ID-like feature so important?&#8221;</em> can reveal leakage. Or it can guide product thinking: <em>&#8220;Are we actually using the expensive-to-collect feature, or is it redundant?&#8221;</em></p><p style="text-align: justify;">But it&#8217;s easy to over-read these numbers, because a forest is not a causal model. It&#8217;s an opportunistic predictor.</p><p style="text-align: justify;">Two common traps:</p><ul><li><p style="text-align: justify;"><strong>Correlated features share credit in weird ways.</strong> If two features are strongly correlated (say, <em>&#8220;square footage&#8221;</em> and <em>&#8220;number of bedrooms&#8221;</em>), the forest can split on either one. Importance may concentrate on whichever feature happened to win splits more often, even if both encode similar signal. This doesn&#8217;t mean the other feature <em>&#8220;doesn&#8217;t matter&#8221;</em>; it may simply be redundant.</p></li><li><p style="text-align: justify;"><strong>Dataset quirks can look like meaning.</strong> If a feature is a proxy for how the data was collected (timestamp, region code tied to a particular marketing campaign, source system identifier), the forest might use it heavily. Importance then reflects your pipeline, not the world.</p></li></ul><p style="text-align: justify;">The most grounded way to think about it is: a random forest is a strong predictor first, and only a partial explainer in context-dependent ways. Use feature importance to ask better questions, not to conclude a story about why outcomes happen.</p><h1>Tuning without superstition: the knobs that change the forest&#8217;s personality</h1><p style="text-align: justify;">Hyperparameter tuning for random forests can feel like folklore: <em>&#8220;use 500 trees&#8221;</em>, <em>&#8220;depth doesn&#8217;t matter&#8221;</em>, <em>&#8220;always set max features to sqrt&#8221;</em>. There are reasons those heuristics exist, but you&#8217;ll get much further if you map each knob to the <em>problem it&#8217;s trying to solve</em>.</p><p style="text-align: justify;"><strong>Number of trees (`</strong><code>n_estimators</code><strong>`).</strong> This mostly controls variance reduction through averaging. More trees makes the forest&#8217;s output less sensitive to the randomness in bootstrapping and feature subsets. Past a point, returns diminish, so it&#8217;s a stability-versus-cost trade.</p><p style="text-align: justify;"><strong>Tree depth / leaf size (`</strong><code>max_depth</code><strong>`, `</strong><code>min_samples_leaf</code><strong>`).</strong> This controls how much each tree can memorize its bootstrap sample. Deep trees with tiny leaves can fit idiosyncrasies; that&#8217;s not automatically bad in a forest, but if leaves are too small, each tree becomes extremely noisy. Increasing <code>min_samples_leaf</code> forces smoother rules: leaves represent broader regions, which often improves generalization.</p><p style="text-align: justify;"><strong>Features per split (`</strong><code>max_features</code><strong>`).</strong> This is one of the most <em>&#8220;personality-changing&#8221;</em> knobs because it directly affects diversity. If <code>max_features</code> is large, many trees will repeatedly choose the same strong predictors early, increasing correlation. If it&#8217;s small, trees are forced into different early narratives, which can reduce correlation - but if it&#8217;s too small, trees may become weak in the unhelpful sense of missing important signal at many splits.</p><p style="text-align: justify;"><strong>Bootstrap / sampling choices.</strong> Bootstrapping is a built-in way to perturb the training set. If you turn it off (or if your dataset is tiny), trees may become more similar. Conversely, with enough data, bootstrapping can be a gentle way to create diversity without losing too much signal.</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/xcPHI/2/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/057fa3a3-49f2-49e1-9055-8d66a532f5f6_1220x956.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f0ec5c6b-ede6-41b3-a966-1810afc9fe2e_1220x1026.png&quot;,&quot;height&quot;:400,&quot;title&quot;:&quot;The knobs that change a random forest's personality.&quot;,&quot;description&quot;:&quot;Create interactive, responsive &amp; beautiful charts &#8212; no code required.&quot;,&quot;belowTheFold&quot;:true}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/xcPHI/2/" width="730" height="400" frameborder="0" scrolling="no" loading="lazy"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p style="text-align: justify;">A good tuning mindset is: pick a validation metric you trust, tune toward lower error <em>and</em> manageable cost, and interpret each change as <em>&#8220;I&#8217;m trading off memorization, diversity, and stability&#8221;</em>.</p><div><hr></div><p style="text-align: justify;"><strong>&#128172; What did a random forest once teach you the hard way - a feature-importance trap, a leakage surprise, a plateau that &#8220;</strong><em><strong>more trees</strong></em><strong>&#8220; couldn&#8217;t fix? I read every reply.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/random-forests-wisdom-of-the-crowd/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/random-forests-wisdom-of-the-crowd/comments"><span>Leave a comment</span></a></p><div><hr></div><h1>Zooming out: what random forests teach you about machine learning</h1><p style="text-align: justify;">If you step back, random forests are less about trees and more about a philosophy of building reliable systems from unreliable parts.</p><p style="text-align: justify;">Early in ML, it&#8217;s natural to search for the <em>&#8220;right model&#8221;</em> - the one that captures the true structure of the data. That&#8217;s an understandable instinct, and sometimes it works. But many real datasets don&#8217;t reward a single pristine story. They&#8217;re messy: measurement error, hidden confounders, shifting distributions, proxies standing in for unmeasured variables, and feedback loops from the very systems we build.</p><p style="text-align: justify;">In that world, brittleness is the default. A model that depends on one fragile narrative - one feature behaving <em>&#8220;the same way forever&#8221;</em>, one clean separation that only exists in your snapshot of data - will eventually surprise you.</p><p style="text-align: justify;">Random forests embody a different approach: <em>design for resilience</em>. Encourage multiple perspectives. Prevent any one strong but possibly misleading predictor from dominating every decision. And then aggregate in a way that rewards consistent signal and penalizes idiosyncratic noise.</p><p style="text-align: justify;">This is a lesson you can carry beyond random forests. It shows up in cross-validation, in regularization, in ensembling across model families, in monitoring and drift detection, and even in engineering practices like canary deployments. We don&#8217;t just want models that can fit. We want models (and systems) that behave sensibly when the world wiggles.</p><p style="text-align: justify;">A random forest, at its best, is a structured way to turn disagreement into reliability. Not because disagreement is intrinsically good, but because it&#8217;s evidence that you&#8217;re not trapped in one overconfident story. And in machine learning - where the world is always a bit more complicated than our datasets - that humility is often the most practical form of intelligence.</p><div><hr></div><p style="text-align: justify;"><strong>&#128236; This is Machine Learning Fundamentals - one method, built up from intuition to the math that makes it work, no hype. Subscribe and the next one lands in your inbox.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>References</h1><ol><li><p style="text-align: justify;"><a href="https://scikit-learn.org/stable/modules/ensemble.html">Ensemble methods: forests of randomized trees</a> | scikit-learn</p></li><li><p style="text-align: justify;"><a href="https://link.springer.com/article/10.1023/A:1010933404324">Random Forests</a> <em>by Leo Breiman</em> | Machine Learning (2001)</p></li><li><p style="text-align: justify;"><a href="https://arxiv.org/abs/1603.02754">XGBoost: A Scalable Tree Boosting System</a> <em>by Tianqi Chen &amp; Carlos Guestrin</em> | arXiv</p></li><li><p style="text-align: justify;"><a href="https://www.statlearning.com/">An Introduction to Statistical Learning</a> <em>by James, Witten, Hastie &amp; Tibshirani</em> | Springer (free online)</p></li><li><p style="text-align: justify;"><a href="https://hastie.su.domains/ElemStatLearn/">The Elements of Statistical Learning</a> <em>by Hastie, Tibshirani &amp; Friedman</em> | Stanford (free online)</p></li><li><p style="text-align: justify;"><a href="https://www.microsoft.com/en-us/research/publication/pattern-recognition-machine-learning/">Pattern Recognition and Machine Learning</a> <em>by Christopher Bishop</em> | Microsoft Research (free PDF)</p></li><li><p style="text-align: justify;"><a href="https://www.amazon.com/dp/0412048418">Classification and Regression Trees</a> <em>by Breiman, Friedman, Olshen &amp; Stone</em> | Chapman &amp; Hall</p></li></ol>]]></content:encoded></item><item><title><![CDATA[Idempotency: why your Payment API must survive double-clicks and timeouts]]></title><description><![CDATA[Systems Literacy]]></description><link>https://iam.slys.dev/p/idempotency-why-your-payment-api</link><guid isPermaLink="false">https://iam.slys.dev/p/idempotency-why-your-payment-api</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 10 Aug 2026 21:43:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BGUR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e22ca4f-7620-4390-9fbd-052512e9e4a3_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BGUR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e22ca4f-7620-4390-9fbd-052512e9e4a3_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BGUR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e22ca4f-7620-4390-9fbd-052512e9e4a3_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!BGUR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e22ca4f-7620-4390-9fbd-052512e9e4a3_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!BGUR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e22ca4f-7620-4390-9fbd-052512e9e4a3_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!BGUR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e22ca4f-7620-4390-9fbd-052512e9e4a3_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BGUR!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e22ca4f-7620-4390-9fbd-052512e9e4a3_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9e22ca4f-7620-4390-9fbd-052512e9e4a3_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/788983f8-99de-4e8f-87cc-cd3fc52f77ba_1536x1024.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1934233,&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://iam.slys.dev/i/194775535?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F788983f8-99de-4e8f-87cc-cd3fc52f77ba_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BGUR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e22ca4f-7620-4390-9fbd-052512e9e4a3_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!BGUR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e22ca4f-7620-4390-9fbd-052512e9e4a3_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!BGUR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e22ca4f-7620-4390-9fbd-052512e9e4a3_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!BGUR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e22ca4f-7620-4390-9fbd-052512e9e4a3_1536x1024.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 style="text-align: justify;">Picture someone buying concert tickets on a phone while walking out of a subway station. The signal flips between &#8220;<em>fine</em>&#8221; and &#8220;<em>barely there</em>&#8221;. They tap <strong>Pay</strong>, the UI shows a spinner, the app hesitates, and then - nothing. No error. No confirmation. Just that quiet, anxious pause where you can&#8217;t tell whether the app is thinking or dead.</p><p style="text-align: justify;">So they do what any reasonable human does: they tap again. Maybe twice. Maybe they force-close and reopen. Maybe they go back and try the whole flow one more time because the page looks &#8220;<em>stuck</em>&#8221;.</p><p style="text-align: justify;">Later that night they check their bank app and see two charges. Or they get two order confirmation emails with two different order numbers. Or support tells them, &#8220;<em>We see two orders; we can refund one</em>&#8221;, which is not the kind of sentence that builds trust in a checkout flow.</p><p style="text-align: justify;">If you&#8217;ve ever been on-call for a system that handles money, you know what happens next. Support tickets surge. Someone starts pulling logs. Someone else starts running queries to find &#8220;<em>duplicates</em>&#8221;. You get a Slack thread full of screenshots of the payment processor dashboard. And in the middle of it all is the maddening part: everyone behaved &#8220;<em>reasonably</em>&#8221;.</p><p style="text-align: justify;">The user retried because the system gave them no feedback.</p><p style="text-align: justify;">The client code retried because that&#8217;s what SDKs do when they hit a timeout.</p><p style="text-align: justify;">The load balancer rerouted the retry to a different server because that&#8217;s what load balancers do.</p><p style="text-align: justify;">And the original request might have succeeded - maybe it even succeeded quickly - but the response got lost somewhere between the server and the phone. Or it arrived late enough that the client had already given up and tried again.</p><p style="text-align: justify;">The emotional shape of the incident is always the same: <em>&#8220;I did one thing once&#8230; why did the system do it twice?&#8221;</em> That question is the beginning of idempotency, not as a textbook property, but as a survival trait for real production systems.</p><div><hr></div><p style="text-align: justify;"><strong>&#128257; Every engineer who ships payments or sign-ups has lived this exact double-charge story &#8212; send it to the one on your team who's about to.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/idempotency-why-your-payment-api?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/idempotency-why-your-payment-api?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h1>When you can't tell failure from silence</h1><p style="text-align: justify;">Distributed systems have an awkward limitation that doesn&#8217;t feel real until you&#8217;ve been burned by it: they cannot reliably tell the difference between &#8220;<em>didn&#8217;t happen</em>&#8221; and &#8220;<em>happened but I didn&#8217;t hear about it</em>&#8221;.</p><p style="text-align: justify;">From the perspective of a client, these situations can look identical:</p><ul><li><p style="text-align: justify;">The request never arrived.</p></li><li><p style="text-align: justify;">The request arrived but didn&#8217;t finish.</p></li><li><p style="text-align: justify;">The request finished, but the response got lost on the way back.</p></li></ul><p style="text-align: justify;">When you&#8217;re staring at a spinner on a phone, you can&#8217;t see which one you&#8217;re in. When you&#8217;re writing client code, you also can&#8217;t see which one you&#8217;re in. All you have is time passing and the absence of an answer.</p><p style="text-align: justify;">That&#8217;s why naive solutions fall apart.</p><p style="text-align: justify;">If you say, &#8220;<em>Just retry on failure</em>&#8221;, you&#8217;ll eventually duplicate side effects. You&#8217;ll charge twice, create two shipments, send two emails, provision two resources, or reserve two seats. Not because your code is &#8220;<em>wrong</em>&#8221;, but because retries are not rare edge cases. They&#8217;re the system&#8217;s natural response to missing information.</p><p style="text-align: justify;">If instead you say, &#8220;<em>Don&#8217;t retry</em>&#8221;, your user flow becomes fragile. A transient network hiccup becomes a permanent stuck checkout. A single packet loss turns into revenue loss. Reliability drops, not because the system can&#8217;t do the work, but because it can&#8217;t confirm that it did the work.</p><p style="text-align: justify;">And if you say, &#8220;<em>Put it behind a load balancer</em>&#8221;, you often make the ambiguity worse. A retry that lands on a different node loses any in-memory context about the first attempt. Even if the original server &#8220;<em>knows</em>&#8221; it processed the request, the new server doesn&#8217;t. From the system&#8217;s perspective, the retry looks like a brand-new request.</p><p style="text-align: justify;">Underneath these symptoms are a few realities that show up in every large system, no matter how well engineered:</p><p style="text-align: justify;">Latency makes sane people do irrational things. Responses arrive late. Clients assume failure too early. Timeouts are guesses, not truths. And when your timeout is shorter than your long-tail latency, retries become a steady stream of duplicates.</p><p style="text-align: justify;">Partial failure is normal. One service dies while others keep going. A database write succeeds, but the downstream email service is slow. A payment capture succeeds, but the order service restarts before it can commit its own state. Each component has its own failure behavior, and they don&#8217;t coordinate their failure in a neat, unit-test-friendly way.</p><p style="text-align: justify;">Concurrency adds another twist: duplicates can be in-flight at the same time. Two identical requests might race through different servers. If your only defense is &#8220;<em>check then act</em>&#8221;, you&#8217;ll eventually hit the window where both requests check, both see &#8220;<em>nothing yet</em>&#8221;, and both act.</p><p style="text-align: justify;">And in modern architectures, the components are independent. Your payment processor, order database, notification service, and fraud check each have their own semantics. Some are transactional, some are eventually consistent, some retry on their own, and some are black boxes that &#8220;<em>mostly work</em>&#8221; until they don&#8217;t.</p><p style="text-align: justify;">So you end up with a tension you can&#8217;t wish away: you want systems that are retry-friendly without becoming double-effect machines.</p><h1>Retries as safe repetition</h1><p style="text-align: justify;">Idempotency is the idea that doing the same operation multiple times produces the same outcome as doing it once.</p><p style="text-align: justify;">That definition sounds dry until you translate it into the lived reality of production systems: idempotency is how you make retries feel like <em>safe repetition</em> rather than <em>extra actions</em>.</p><p style="text-align: justify;">It&#8217;s not primarily about elegance. It&#8217;s about protecting the things users and operators care about most:</p><p style="text-align: justify;">Correctness of side effects is the obvious one. Charge once. Ship once. Create one user account. Send one password reset email. Reserve one seat. The system can do lots of internal work, but the external effect - the thing that matters - should not multiply just because the network got flaky.</p><p style="text-align: justify;">User trust is the quieter one. People can tolerate a spinner, even an error screen, if the outcome is consistent. What they struggle with is ambiguity: &#8220;<em>Did it go through? Should I try again? Am I about to get charged twice?</em>&#8221; Idempotency lets you build UX that confidently says, &#8220;<em>Yes, you can retry. It won&#8217;t hurt</em>&#8221;.</p><p style="text-align: justify;">Operational sanity is the one you feel at 3 a.m. Without idempotency, you end up with manual reversals, reconciliation jobs, and a long backlog of &#8220;<em>we should clean this up later</em>&#8221; scripts. Idempotency doesn&#8217;t eliminate incidents, but it turns a whole category of chaos into something closer to routine.</p><p style="text-align: justify;">There&#8217;s an important scope note that&#8217;s worth setting early because it prevents disappointment later. Idempotency is easiest when the &#8220;<em>result</em>&#8221; is well-defined. &#8220;<em>Order exists with ID X</em>&#8221; is a crisp outcome. &#8220;<em>Charge captured with transaction ID Y</em>&#8221; is a crisp outcome.</p><p style="text-align: justify;">It gets harder when the action inherently implies repetition, like &#8220;<em>increment counter</em>&#8221; or &#8220;<em>add $10 to balance</em>&#8221;. Those are not impossible to make idempotent, but you can&#8217;t do it by waving the word &#8220;<em>idempotent</em>&#8221; at the problem. You have to change the shape of the operation so there <em>is</em> a stable outcome you can converge on.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iNQm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F683d0242-7c8d-45a9-b0d3-b8db9ccd3225_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iNQm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F683d0242-7c8d-45a9-b0d3-b8db9ccd3225_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!iNQm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F683d0242-7c8d-45a9-b0d3-b8db9ccd3225_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!iNQm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F683d0242-7c8d-45a9-b0d3-b8db9ccd3225_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!iNQm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F683d0242-7c8d-45a9-b0d3-b8db9ccd3225_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iNQm!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F683d0242-7c8d-45a9-b0d3-b8db9ccd3225_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/683d0242-7c8d-45a9-b0d3-b8db9ccd3225_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/819a24a4-620c-4e9b-b4cb-ae38221cea0c_1536x1024.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1342962,&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://iam.slys.dev/i/194775535?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F819a24a4-620c-4e9b-b4cb-ae38221cea0c_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iNQm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F683d0242-7c8d-45a9-b0d3-b8db9ccd3225_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!iNQm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F683d0242-7c8d-45a9-b0d3-b8db9ccd3225_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!iNQm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F683d0242-7c8d-45a9-b0d3-b8db9ccd3225_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!iNQm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F683d0242-7c8d-45a9-b0d3-b8db9ccd3225_1536x1024.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>The idempotency key in the request path</h1><h2>The mental model: give every attempt the same receipt number</h2><p style="text-align: justify;">The simplest way I&#8217;ve found to explain idempotency is to talk about a receipt number.</p><p style="text-align: justify;">When a user performs one logical action - &#8220;<em>place this order</em>&#8221; - you want every attempt at that action to carry the same identity, the way every follow-up email in a thread carries the same context. If you don&#8217;t hear back, you can resend. But you resend <em>the same thread</em>, not a brand-new message with no reference.</p><p style="text-align: justify;">In API terms, that &#8220;<em>receipt number</em>&#8221; is commonly called an <strong>idempotency key</strong>. It&#8217;s a unique token that identifies the <em>intended operation</em>, not the <em>network attempt</em>.</p><p style="text-align: justify;">So &#8220;<em>create_order</em>&#8221; becomes: &#8220;<em>create order for this cart, under idempotency key K</em>&#8221;. If the phone retries, it retries with the same K. If a gateway retries, it retries with the same K. If the user double-clicks, both clicks carry the same K. All of those become different deliveries of the same logical request.</p><h2>The conceptual flow</h2><p style="text-align: justify;">The flow is almost boring, which is part of why it works.</p><p style="text-align: justify;">First, the client picks or receives an idempotency key. The important part is not where the key comes from; it&#8217;s that the key is stable across retries for the <em>same</em> action.</p><p style="text-align: justify;">Then the request arrives at the server with that key.</p><p style="text-align: justify;">The server checks: have we already processed this key?</p><p style="text-align: justify;">If the answer is yes, the server returns the stored outcome (or at least a stable view of where the operation ended up). From the client&#8217;s perspective, this feels like &#8220;<em>the retry succeeded</em>&#8221;, even though the system is really saying, &#8220;<em>You already asked; here&#8217;s what happened</em>&#8221;.</p><p style="text-align: justify;">If the answer is no, the server proceeds to do the work.</p><p style="text-align: justify;">Finally, the server records the outcome tied to the key, durably, so that future duplicates can be answered consistently.</p><p style="text-align: justify;">What makes this work is that it turns the network&#8217;s ambiguity into an application-level certainty. You can&#8217;t stop timeouts from happening, but you can decide what a timeout <em>means</em>: it means &#8220;<em>I don&#8217;t know</em>&#8221;, not &#8220;<em>it failed</em>&#8221;. And when you retry, the system can resolve that uncertainty by looking up the key.</p><h2>Request-level idempotency</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FfMP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5764f52-5c19-4f32-b961-bea242a1e5dd_496x955.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FfMP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5764f52-5c19-4f32-b961-bea242a1e5dd_496x955.png 424w, https://substackcdn.com/image/fetch/$s_!FfMP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5764f52-5c19-4f32-b961-bea242a1e5dd_496x955.png 848w, https://substackcdn.com/image/fetch/$s_!FfMP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5764f52-5c19-4f32-b961-bea242a1e5dd_496x955.png 1272w, https://substackcdn.com/image/fetch/$s_!FfMP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5764f52-5c19-4f32-b961-bea242a1e5dd_496x955.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FfMP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5764f52-5c19-4f32-b961-bea242a1e5dd_496x955.png" width="496" height="955" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f5764f52-5c19-4f32-b961-bea242a1e5dd_496x955.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fb568d50-b513-42aa-a97f-c2f71880865e_496x955.png&quot;,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:955,&quot;width&quot;:496,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:39216,&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://iam.slys.dev/i/194775535?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb568d50-b513-42aa-a97f-c2f71880865e_496x955.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_!FfMP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5764f52-5c19-4f32-b961-bea242a1e5dd_496x955.png 424w, https://substackcdn.com/image/fetch/$s_!FfMP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5764f52-5c19-4f32-b961-bea242a1e5dd_496x955.png 848w, https://substackcdn.com/image/fetch/$s_!FfMP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5764f52-5c19-4f32-b961-bea242a1e5dd_496x955.png 1272w, https://substackcdn.com/image/fetch/$s_!FfMP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5764f52-5c19-4f32-b961-bea242a1e5dd_496x955.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>What to return on duplicates</h2><p style="text-align: justify;">This is where idempotency stops being a purely mechanical trick and becomes a product and design decision.</p><p style="text-align: justify;">For some operations, the cleanest behavior is to return the <em>same response</em> on duplicates. If the first attempt created order <code>12345</code>, every retry should return order <code>12345</code>. The same body, the same identifiers, the same semantics. If you can make it identical, clients become simpler and your system becomes easier to reason about.</p><p style="text-align: justify;">But sometimes the first attempt is still running when the duplicate arrives. Maybe the downstream payment processor is slow. Maybe you&#8217;re doing fraud checks. Maybe you&#8217;re waiting on inventory reservation. In that case, returning the final response isn&#8217;t possible yet. What you can return is a stable current state: &#8220;<em>processing</em>&#8221;, &#8220;<em>completed</em>&#8221;, or &#8220;<em>failed</em>&#8221;.</p><p style="text-align: justify;">The goal, either way, is convergence. Retries should not branch reality into parallel universes. They should collapse onto one logical outcome, even if that outcome takes time to reach.</p><h1>What deduplication costs you</h1><p style="text-align: justify;">Idempotency is one of those features that feels like a pure win until you build it. Then you realize it&#8217;s less like flipping a switch and more like adding a new organ to your system. Useful, sometimes necessary, but not free.</p><p style="text-align: justify;">What idempotency improves is exactly what you want in a world of retries.</p><p style="text-align: justify;">Correctness under retries gets dramatically better because duplicates no longer create duplicate effects. When things go wrong - timeouts, restarts, transient failures - you&#8217;re not compounding the problem by creating extra charges and extra orders.</p><p style="text-align: justify;">Resilience improves because clients can retry aggressively without &#8220;<em>double spending</em>&#8221;. This matters in practice because many layers will retry whether you want them to or not. If your system is not safe under retries, you&#8217;re effectively betting your correctness on the hope that retries don&#8217;t happen. That&#8217;s a bad bet.</p><p style="text-align: justify;">Operator confidence improves too, in a very human way. When incidents happen, you can focus on latency and availability without also worrying that every timeout is secretly creating a financial mess.</p><p style="text-align: justify;">But idempotency makes some things worse, and it introduces new risks you have to be honest about.</p><p style="text-align: justify;">The first cost is <strong>state you didn&#8217;t have before</strong>. To remember that key K was processed, you need a place to store it and a way to look it up. That means extra reads and writes on the critical path of your most important endpoints. It also means schema decisions: what exactly do you store - request hash, response body, status, timestamps, error details?</p><p style="text-align: justify;">The second cost is <strong>latency</strong>. Even if the idempotency store is fast, &#8220;<em>fast</em>&#8221; is still time. And if you need stronger guarantees - like preventing concurrent duplicates from both executing - you may need coordination that adds more overhead. Under load, this can become a real contributor to tail latency.</p><p style="text-align: justify;">The third cost is <strong>complexity</strong>, and not the pleasant kind. You now have to define what &#8220;<em>same operation</em>&#8221; means precisely. Is &#8220;<em>create order</em>&#8221; the same if the cart contents are different? Is it the same if the shipping address changed? Is it the same if the currency changed? Idempotency pushes you to draw boundaries that your product may not have made explicit before.</p><p style="text-align: justify;">The fourth cost is <strong>storage growth</strong>. Keys and outcomes don&#8217;t clean themselves up. You need retention policies - time-to-live, cleanup jobs, archival, maybe even legal considerations depending on what you store. And once you introduce cleanup, you introduce another decision: what happens to late retries after the key expires?</p><p style="text-align: justify;">Then there are the new risks.</p><p style="text-align: justify;"><strong>Key misuse</strong> is the most common. If a client accidentally reuses a key for a different intended action, your system will &#8220;<em>dedupe</em>&#8221; something that shouldn&#8217;t be deduped. From the system&#8217;s perspective, it&#8217;s being consistent. From the user&#8217;s perspective, it&#8217;s doing the wrong thing while insisting it&#8217;s doing the right thing.</p><p style="text-align: justify;"><strong>Poisoned outcomes</strong> are subtler. If you store a bad or partial result - say, you saved &#8220;<em>success</em>&#8221; before all side effects were truly safe, or you saved a response that doesn&#8217;t reflect reality - you can end up consistently repeating the wrong response. Idempotency is a memory. And memories can be incorrect.</p><p style="text-align: justify;"><strong>Hot keys and contention</strong> show up during incidents. When a client times out and retries rapidly, or when a whole fleet retries at once, you can get a thundering herd focused on one key. If your protection mechanism involves locking or serialization, that single operation identity can become a bottleneck. The irony is real: the feature designed to make retries safe can become a focal point for retry traffic.</p><p style="text-align: justify;">These costs force explicit trade-offs, and it&#8217;s worth naming them because they show up in design reviews and in postmortems.</p><p style="text-align: justify;">There&#8217;s an <strong>availability vs correctness</strong> trade-off. If the idempotency store is down, do you fail closed - refuse to proceed because you can&#8217;t guarantee you won&#8217;t duplicate side effects - or do you proceed and accept the risk? &#8220;<em>Fail closed</em>&#8221; protects correctness but harms availability. &#8220;<em>Proceed</em>&#8221; keeps the system moving but can reintroduce duplicate effects right when the system is already degraded.</p><p style="text-align: justify;">There&#8217;s a <strong>simplicity vs scalability</strong> trade-off. A single idempotency table is conceptually clean. At scale, it can become a high-traffic dependency for your hottest endpoints. You may need partitioning, caching, careful indexing, or other strategies to keep it from becoming the bottleneck that defines your throughput.</p><p style="text-align: justify;">And there&#8217;s a <strong>latency vs consistency</strong> trade-off. Stronger guarantees - especially under concurrency - often require more coordination. You can make duplicates harmless in a &#8220;<em>best effort</em>&#8221; way with minimal overhead, or you can make them safe under more adversarial timing with stronger coordination. The more certainty you demand, the more you tend to pay.</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/o0Tx7/3/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b41c60f9-e127-40df-b81d-b2b738e3b4fc_1220x750.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/81ec82a7-1495-4c86-ace1-08b56b2ca249_1220x820.png&quot;,&quot;height&quot;:400,&quot;title&quot;:&quot;Idempotency's three core trade-offs.&quot;,&quot;description&quot;:&quot;&quot;,&quot;belowTheFold&quot;:true}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/o0Tx7/3/" width="730" height="400" frameborder="0" scrolling="no" loading="lazy"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><h1>Where retries already live</h1><p style="text-align: justify;">Idempotency shows up in more places than people expect, because retries show up in more places than people expect.</p><p style="text-align: justify;">APIs are the obvious home. Payment and checkout endpoints are the classic examples: &#8220;<em>charge customer</em>&#8221;, &#8220;<em>create order</em>&#8221;, &#8220;<em>confirm purchase</em>&#8221;. These are exactly the operations where users will retry under uncertainty and where duplicate effects are expensive.</p><p style="text-align: justify;">But &#8220;<em>create user</em>&#8221; is another one that quietly matters. Sign-up flows are full of retries: users double-submit forms, mobile networks drop, verification emails arrive late, and frontend code retries when it thinks the request failed. Without idempotency, you end up with duplicate accounts, conflicting verification states, and confusing &#8220;<em>email already in use</em>&#8221; errors that are technically true but emotionally wrong.</p><p style="text-align: justify;">Databases offer another angle: sometimes the easiest way to make an operation idempotent is to stop treating it as &#8220;<em>create something new</em>&#8221; and start treating it as &#8220;<em>ensure something exists</em>&#8221;. If the client supplies a stable identifier, the database can enforce uniqueness. A unique constraint is, in a sense, a form of idempotency enforcement. It&#8217;s not the whole story - because you still have to decide what to do when a duplicate hits - but it&#8217;s one of the most grounded, battle-tested primitives for deduplication.</p><p style="text-align: justify;">Queues and background jobs are where idempotency becomes less optional and more foundational. Many messaging systems prioritize availability and throughput and therefore lean toward <strong>at-least-once delivery</strong>. That&#8217;s a polite way of saying: sometimes you will see the same message twice. If your job handler is not idempotent, your system&#8217;s behavior becomes probabilistic. Most days it&#8217;s fine, and then one day it sends the same email twice to a million users because a consumer crashed at just the wrong moment.</p><p style="text-align: justify;">Caches are a quieter case. When retries or concurrent requests trigger expensive recomputation, you can get stampedes. Idempotency-like techniques - &#8220;<em>only one of these computations should win; others should reuse the result</em>&#8221; - help keep the system stable under load. Even when you&#8217;re not dealing with money, you&#8217;re still dealing with duplicated work, which can be the first step toward a cascading outage.</p><p style="text-align: justify;">Microservices make the need sharper because they multiply the number of retry boundaries. Service A calls service B and times out. It retries. But service B might have completed the request and simply failed to respond in time. Without idempotency, your internal calls become duplicate side effects, which then fan out. One timeout can lead to two shipments, two inventory decrements, two ledger entries - each &#8220;<em>reasonable</em>&#8221; in isolation, disastrous in combination.</p><p style="text-align: justify;">If you&#8217;ve worked in production, you&#8217;ve probably seen the familiar behaviors that fall out of these designs. You&#8217;ve seen &#8220;<em>Your request is being processed</em>&#8221; responses that exist not because product wanted them, but because reality demanded them. You&#8217;ve seen duplicate email notifications that show up only during latency incidents. You&#8217;ve seen two shipments created for one order because the warehouse integration had a retry policy nobody knew about. You&#8217;ve seen the same job run twice and then watched engineers debate whether it&#8217;s a bug or &#8220;<em>just at-least-once semantics</em>&#8221;.</p><p style="text-align: justify;">Idempotency is the practice of deciding, ahead of time, that duplicates will happen - and teaching your system to treat them as noise rather than as commands.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jnRY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cfca5a-a7bc-47af-8228-2685f1f6b3de_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jnRY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cfca5a-a7bc-47af-8228-2685f1f6b3de_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!jnRY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cfca5a-a7bc-47af-8228-2685f1f6b3de_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!jnRY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cfca5a-a7bc-47af-8228-2685f1f6b3de_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!jnRY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cfca5a-a7bc-47af-8228-2685f1f6b3de_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jnRY!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cfca5a-a7bc-47af-8228-2685f1f6b3de_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c6cfca5a-a7bc-47af-8228-2685f1f6b3de_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/786e524b-326b-462e-8284-2801d5326b5c_1536x1024.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1429718,&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://iam.slys.dev/i/194775535?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F786e524b-326b-462e-8284-2801d5326b5c_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jnRY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cfca5a-a7bc-47af-8228-2685f1f6b3de_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!jnRY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cfca5a-a7bc-47af-8228-2685f1f6b3de_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!jnRY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cfca5a-a7bc-47af-8228-2685f1f6b3de_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!jnRY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cfca5a-a7bc-47af-8228-2685f1f6b3de_1536x1024.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>Half-idempotent systems and ghost orders</h1><p style="text-align: justify;">When idempotency is missing or partially implemented, systems tend to fail in recognizable ways. The tragedy is that these failures often appear only when the system is already under stress - during a latency spike, a deployment, a regional network issue. That&#8217;s when retries increase, concurrency gets weird, and all the assumptions you didn&#8217;t know you had start to break.</p><p style="text-align: justify;">One common mistake is assuming HTTP retries are rare. In real stacks, retries happen at many layers: the browser or mobile client, the app&#8217;s networking library, the API gateway, the load balancer, the service mesh, the SDK that talks to your payment provider, even the database driver. You can build &#8220;<em>no retries</em>&#8221; into your application logic and still get retries. The only reliable strategy is to design endpoints so that retries are safe.</p><p style="text-align: justify;">Another mistake is making only part of the operation idempotent. This is the &#8220;<em>we deduped the order record, so we&#8217;re fine</em>&#8221; trap. The order creation might be safe, but the confirmation email might not be. The warehouse fulfillment request might not be. The analytics event might not be. When the incident hits, you end up with one order, two emails, and two shipments - which is arguably worse than two orders, because now your own system insists it was correct while your users hold the evidence that it wasn&#8217;t.</p><p style="text-align: justify;">Using idempotency only in memory is another classic. It works in a single instance during a happy-path test. Then you redeploy. Or the process restarts. Or a retry lands on a different instance. And suddenly the &#8220;<em>dedupe</em>&#8221; mechanism evaporates right when you need it most. In distributed systems, if you want something to survive, it has to live somewhere more durable than a single process.</p><p style="text-align: justify;">There&#8217;s also a conceptual mistake: confusing idempotency with &#8220;<em>no duplicates ever</em>&#8221;. In practice, duplicates can still happen. You might still process the same message twice internally. You might still write two rows if you have a bug. Idempotency is not a magic spell that prevents duplication. It&#8217;s the discipline of making duplicates harmless - making the outcome stable even when the world gets messy.</p><p style="text-align: justify;">Finally, many teams forget to define the idempotency window. If you expire keys too soon, late retries can re-trigger side effects. And &#8220;<em>late</em>&#8221; is a slippery concept. Mobile clients can retry minutes later. Background jobs can be delayed. Users can hit refresh hours later if the UI is confusing. If your key TTL is shorter than the time it takes for retries to plausibly occur, you&#8217;ve built a correctness feature that disappears on a timer.</p><p style="text-align: justify;">When these mistakes surface, the symptoms are painfully consistent.</p><p style="text-align: justify;">Users see double charges, multiple orders, duplicate notifications. They see confusing &#8220;<em>error</em>&#8221; screens followed by &#8220;<em>success</em>&#8221; later, which is the worst kind of UX because it teaches them to distrust both the error and the success.</p><p style="text-align: justify;">Operators see reconciliation scripts and manual refunds. They see &#8220;<em>ghost orders</em>&#8221;, where payment succeeded but order creation retried into a new ID, leaving finance and fulfillment disagreeing about what is real. They see support ticket spikes after latency incidents, because latency incidents are also retry incidents, and retry incidents are correctness incidents if you haven&#8217;t made retries safe.</p><p style="text-align: justify;">The hardest part is that these failures often don&#8217;t show up in unit tests. They show up in the spaces between components: in timeouts, in restarts, in packets that arrive late, in requests that get duplicated by a proxy you forgot existed. Which is why idempotency is less about cleverness and more about humility.</p><h1>A timeout is missing information</h1><p style="text-align: justify;">Idempotency exists because of a philosophical truth that distributed systems force you to confront: <strong>you don&#8217;t get certainty for free</strong>.</p><p style="text-align: justify;">A timeout is not a fact. It&#8217;s missing information.</p><p style="text-align: justify;">When a client times out, it hasn&#8217;t learned &#8220;<em>the server failed</em>&#8221;. It has learned &#8220;<em>I did not receive a response within my patience window</em>&#8221;. The server might be down, or it might be slow, or it might have succeeded instantly and lost the reply. The timeout tells you something about your observation, not about reality.</p><p style="text-align: justify;">Once you internalize that, a lot of system design starts to look different. You stop building flows that require the network to be honest and instantaneous. You stop treating retries as exceptional. You start treating repetition as a normal conversational pattern between components that don&#8217;t fully trust each other.</p><div class="callout-block" data-callout="true"><p style="text-align: justify;">Idempotency is one of the clearest expressions of that habit: design actions so that repeating yourself is safe.</p></div><p style="text-align: justify;">The human analogy is surprisingly close to how the best systems behave. If you send an important email and you don&#8217;t hear back, you might reply to the same thread: &#8220;<em>Following up on request #123</em>&#8221;. That reference number is doing the same job as an idempotency key. It lets the recipient say, &#8220;<em>Yes, I saw this; here&#8217;s where it is</em>&#8221;, instead of treating every follow-up as a brand-new request that triggers a brand-new action.</p><p style="text-align: justify;">Without a reference, every resend looks like a new request. And people respond the same way systems do: they get confused, they duplicate work, and eventually someone gets annoyed and starts asking why there are three identical tasks in the queue.</p><p style="text-align: justify;">There&#8217;s also an organizational angle that&#8217;s easy to underestimate. Idempotency reduces the need for hero debugging and manual cleanup. It turns failure handling from panic into procedure. When you know retries won&#8217;t corrupt the world, you can be more aggressive about retrying, more confident during incidents, and more disciplined about automation. You&#8217;re not relying on perfect behavior; you&#8217;re relying on safe behavior under imperfection.</p><p style="text-align: justify;">And that&#8217;s a broad system design lesson worth keeping: <mark data-color="#ffff00" style="background-color: rgb(255, 255, 0); color: rgb(0, 0, 0);">the goal is not to eliminate uncertainty</mark>. The goal is to build systems that remain correct even when uncertainty is present.</p><div><hr></div><p><strong>&#128172; Where has a lost response or a silent retry burned you &#8212; and how did you make it safe? I read every reply.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/idempotency-why-your-payment-api/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/idempotency-why-your-payment-api/comments"><span>Leave a comment</span></a></p><div><hr></div><h1>Designing for safe repetition</h1><p style="text-align: justify;">Idempotency exists because timeouts and retries are unavoidable, and they blur the line between &#8220;<em>did it happen?</em>&#8221; and &#8220;<em>did I hear about it?</em>&#8221; In a distributed system, that ambiguity is not a corner case. It&#8217;s a daily condition, especially once you involve mobile networks, multiple services, and any kind of external dependency.</p><p style="text-align: justify;">The goal of idempotency is simple to say but surprisingly deep in practice: <mark data-color="#ffff00" style="background-color: rgb(255, 255, 0); color: rgb(0, 0, 0);">retries should converge to one logical outcome, not multiply side effects.</mark> You want the system to behave as if the user acted once, even if the network delivered that action multiple times.</p><p style="text-align: justify;">Most implementations follow the same conceptual shape. You give each logical operation a stable identity - an idempotency key - and you remember the outcome durably. When duplicates arrive, you don&#8217;t re-run the side effects; you return the previously recorded result (or a stable status if it&#8217;s still in flight). This turns the retry button from a source of bugs into a feature: a safe way to recover from missing information.</p><p style="text-align: justify;">But idempotency isn&#8217;t free. It adds state, latency, and real design complexity. You have to define what &#8220;<em>same operation</em>&#8221; means, you have to decide what to return on duplicates, you have to manage retention windows, and you have to think through failure modes of the idempotency mechanism itself.</p><p style="text-align: justify;">And the hardest part is rarely the mechanism. The hardest part is deciding which side effects must be deduped and how far the guarantee should extend. It&#8217;s one thing to dedupe &#8220;<em>create order</em>&#8221;. It&#8217;s another to ensure that &#8220;<em>charge</em>&#8221;, &#8220;<em>email</em>&#8221;, &#8220;<em>shipment</em>&#8221;, and &#8220;<em>ledger entry</em>&#8221; all converge on the same logical truth under retries, concurrency, and partial failure.</p><p style="text-align: justify;">When teams get this wrong, the failures are predictable: double charges, duplicate messages, conflicting records, and messy reconciliation after incidents - exactly when the system is already stressed. When teams get it right, the system doesn&#8217;t become perfect. It becomes calmer. It becomes the kind of system where uncertainty is expected, repetition is safe, and correctness doesn&#8217;t depend on the network behaving nicely.</p><p style="text-align: justify;">In the end, idempotency is less a feature you tack on and more a mindset you adopt. Once you start seeing timeouts as missing information and retries as normal conversation, you begin designing systems that can tolerate the world as it is: slow, flaky, concurrent, and occasionally unfair. That design habit - building for safe repetition - pays dividends far beyond payments, because it&#8217;s really a way of making distributed systems honest with themselves.</p><div><hr></div><p style="text-align: justify;"><strong>&#128236; This is the opening piece of System Design Fundamentals &#8212; one clear primitive at a time, no hype. Subscribe and the next one lands in your inbox.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>References</h1><ol><li><p style="text-align: justify;"><a href="https://docs.stripe.com/api/idempotent_requests">Idempotent requests</a> | Stripe API Reference</p></li><li><p style="text-align: justify;"><a href="https://stripe.com/blog/idempotency">Designing robust and predictable APIs with idempotency</a> <em>by Brandur Leach</em> | Stripe Blog</p></li><li><p style="text-align: justify;"><a href="https://www.rfc-editor.org/rfc/rfc9110.html">RFC 9110: HTTP Semantics</a> | IETF</p></li><li><p style="text-align: justify;"><a href="https://lamport.azurewebsites.net/pubs/time-clocks.pdf">Time, Clocks, and the Ordering of Events in a Distributed System</a> <em>by Leslie Lamport</em> | Communications of the ACM</p></li><li><p style="text-align: justify;"><a href="https://queue.acm.org/detail.cfm?id=3025012">Life Beyond Distributed Transactions: An Apostate's Opinion</a> <em>by Pat Helland</em> | ACM Queue</p></li><li><p style="text-align: justify;"><a href="https://dataintensive.net/">Designing Data-Intensive Applications</a> <em>by Martin Kleppmann</em> | O'Reilly</p></li><li><p style="text-align: justify;"><a href="https://www.amazon.com/dp/1558601902">Transaction Processing: Concepts and Techniques</a> <em>by Jim Gray &amp; Andreas Reuter</em> | Morgan Kaufmann</p></li><li><p style="text-align: justify;"><a href="https://pragprog.com/titles/mnee2/release-it-second-edition/">Release It!</a> <em>by Michael Nygard</em> | Pragmatic Bookshelf</p></li></ol>]]></content:encoded></item><item><title><![CDATA[Timeouts: the most important number in distributed systems (and the easiest one to get wrong)]]></title><description><![CDATA[Systems Literacy]]></description><link>https://iam.slys.dev/p/timeouts-the-most-important-number</link><guid isPermaLink="false">https://iam.slys.dev/p/timeouts-the-most-important-number</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 03 Aug 2026 20:52:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ZyDd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4db8c29-74d6-42dd-89a1-7d15b0dee961_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZyDd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4db8c29-74d6-42dd-89a1-7d15b0dee961_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZyDd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4db8c29-74d6-42dd-89a1-7d15b0dee961_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ZyDd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4db8c29-74d6-42dd-89a1-7d15b0dee961_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ZyDd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4db8c29-74d6-42dd-89a1-7d15b0dee961_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ZyDd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4db8c29-74d6-42dd-89a1-7d15b0dee961_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZyDd!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4db8c29-74d6-42dd-89a1-7d15b0dee961_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d4db8c29-74d6-42dd-89a1-7d15b0dee961_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1d259b58-322f-4a30-819e-f92791aa8097_1536x1024.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2004983,&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://iam.slys.dev/i/194787089?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d259b58-322f-4a30-819e-f92791aa8097_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZyDd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4db8c29-74d6-42dd-89a1-7d15b0dee961_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ZyDd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4db8c29-74d6-42dd-89a1-7d15b0dee961_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ZyDd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4db8c29-74d6-42dd-89a1-7d15b0dee961_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ZyDd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4db8c29-74d6-42dd-89a1-7d15b0dee961_1536x1024.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 style="text-align: justify;">It&#8217;s 2 a.m. and the pager goes off. Not the clean kind of alert where a service is down and you know what to restart - the kind where the dashboard is a wall of yellow and nobody can quite say what&#8217;s wrong.</p><p style="text-align: justify;">The graphs disagree with each other. Error rate is creeping up. Latency is climbing. But CPU is fine. Memory is fine. Nothing is crash-looping. Nothing is &#8220;<em>down</em>&#8221;.</p><p style="text-align: justify;">And yet the system feels&#8230; stuck.</p><p style="text-align: justify;">One downstream service is &#8220;<em>healthy</em>&#8221; according to the health check, but it&#8217;s slow - just slow enough to turn every upstream caller into a waiting room. Retries begin to pile up like cars entering a tunnel that&#8217;s partially blocked. The tunnel isn&#8217;t closed. Traffic still moves. But the throughput has dropped, and now the entrance ramp is backing up onto the highway.</p><p style="text-align: justify;">The most unsettling part is that nobody deployed code today.</p><p style="text-align: justify;">So why does the system suddenly feel frozen but not dead?</p><p style="text-align: justify;">There&#8217;s a particular kind of failure that doesn&#8217;t look like failure at first. Things don&#8217;t crash. They don&#8217;t spike CPU. They don&#8217;t throw dramatic exceptions. They just take longer. And in distributed systems, &#8220;<em>taking longer</em>&#8221; is not a neutral state. Sometimes waiting longer helps and the system recovers. Sometimes waiting longer turns a manageable slowdown into a cascading outage.</p><p style="text-align: justify;">If you&#8217;ve felt that contradiction - <em>waiting longer sometimes fixes it, and sometimes breaks it</em> - you&#8217;re already standing at the doorstep of timeouts.</p><div><hr></div><p style="text-align: justify;"><strong>&#9201;&#65039; Everyone who&#8217;s been on call for a &#8220;</strong><em><strong>nothing is down but it&#8217;s unusable</strong></em><strong>&#8221; incident knows this feeling &#8212; send it to whoever&#8217;s carrying the pager next.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/timeouts-the-most-important-number?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/timeouts-the-most-important-number?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h1>Waiting is not free</h1><p style="text-align: justify;">In a single-process application, &#8220;<em>waiting</em>&#8221; often feels harmless. A function blocks, eventually returns, and the program continues. Even if it&#8217;s slow, the slowness is contained inside one runtime with one set of resources. You can profile it, optimize it, or scale the machine vertically.</p><p style="text-align: justify;">But a distributed system doesn&#8217;t &#8220;<em>call a function</em>&#8221;. It asks another machine - over a network - to do work. And that work often asks another machine to do work. And so on.</p><p style="text-align: justify;">So a single user request that looks like a simple action - tap <strong>Pay</strong> - is usually a chain:</p><div class="callout-block" data-callout="true"><p style="text-align: justify;"><strong>Client &#8594; API &#8594; Auth &#8594; DB &#8594; Cache &#8594; Payments &#8594; Notifications</strong></p></div><p style="text-align: justify;">You may not have all of those hops in your system, but you almost certainly have <em>some</em> chain. As systems grow, chains get longer for reasons that are usually rational at the local level: separation of concerns, independent scaling, organizational boundaries, regulatory boundaries, data boundaries. The chain is how you keep each part manageable.</p><p style="text-align: justify;">But the chain forces you to answer questions that are easy to avoid in a monolith:</p><ul><li><p style="text-align: justify;">Is the dependency slow, or is it broken?</p></li><li><p style="text-align: justify;">Should we keep waiting, or should we stop and try something else?</p></li><li><p style="text-align: justify;">If we stop waiting, what does &#8220;<em>try something else</em>&#8221; even mean for this operation?</p></li></ul><p style="text-align: justify;">The naive strategies that feel fine early on start to fall apart as soon as load and concurrency enter the picture.</p><p style="text-align: justify;"><strong>Indefinite waiting</strong> is the simplest choice conceptually: &#8220;<em>I&#8217;ll just wait until I get an answer</em>&#8221;. That choice feels polite. Patient. It&#8217;s also one of the fastest ways to quietly exhaust your system.</p><p style="text-align: justify;">When a service waits, it holds onto <em>something</em>: a thread, an event-loop slot, a connection, memory for request context, entries in a queue, file descriptors, locks, or a place in a connection pool. Different architectures hold different resources, but none of them are free at scale.</p><p style="text-align: justify;">And distributed systems fail in ways that aren&#8217;t clean. <strong>Partial failures</strong> are normal. One dependency can be slow while everything else is fine. One availability zone can be impaired. One database replica can be lagging. One specific shard can be hot. Nothing is &#8220;<em>down</em>&#8221;, but something is degraded.</p><p style="text-align: justify;">This is where concurrency amplifies pain. Ten slow requests don&#8217;t stay as ten slow requests. Under traffic spikes, they turn into hundreds or thousands of callers all waiting at once. And as more callers wait, you consume more resources, which reduces your ability to serve <em>other</em> requests, which increases latency, which creates more waiting. The feedback loop is ugly because it&#8217;s subtle: everything is &#8220;<em>working</em>&#8221;, just increasingly poorly.</p><p style="text-align: justify;">There&#8217;s also a deeper reality that makes this problem fundamentally hard - you often cannot tell the difference between:</p><ul><li><p style="text-align: justify;">&#8220;<em>This request will succeed in 2 seconds</em>&#8221;, and</p></li><li><p style="text-align: justify;">&#8220;<em>This request will never succeed</em>&#8221;.</p></li></ul><p style="text-align: justify;">From the caller&#8217;s perspective, the network doesn&#8217;t provide truth. It provides uncertainty.</p><p style="text-align: justify;">Packets get delayed. Queues build up. A server can accept a connection but be too overloaded to respond quickly. A request can be processed but the response can be dropped. A load balancer can route you to a sick instance. Your own process can pause for GC at the wrong time.</p><p style="text-align: justify;">If your system&#8217;s plan is &#8220;<em>wait until you know for sure</em>&#8221;, then your plan is to wait forever - because &#8220;<em>for sure</em>&#8221; is rarely available.</p><p style="text-align: justify;">So the core problem is not just that slow things are annoying. The core problem is that <strong>waiting is an implicit commitment of resources under uncertainty</strong>, and at scale that commitment becomes one of the main determinants of whether your system stays stable.</p><h1>A timeout is a decision, not a knob</h1><p style="text-align: justify;">A timeout is often described like a knob you turn to make things &#8220;<em>faster</em>&#8221;. That framing is tempting, but it&#8217;s misleading in a way that causes real incidents.</p><p style="text-align: justify;">A timeout does not make a slow dependency fast.</p><p style="text-align: justify;">A timeout is a decision that says: </p><div class="callout-block" data-callout="true"><p style="text-align: justify;"><strong>&#8220;I am willing to wait up to *</strong><em><strong>this</strong></em><strong>* long for this work to complete. After that, I will behave as though it failed, and I will take a different path&#8221;.</strong></p></div><p style="text-align: justify;">That &#8220;<em>different path</em>&#8221; is the whole point. Without a next move, a timeout is just an error generator. With a next move, a timeout becomes a tool for keeping the rest of the system alive.</p><p style="text-align: justify;">In plain language, timeouts protect three things that tend to be invisible until an outage:</p><ol><li><p style="text-align: justify;"><strong>Your system&#8217;s ability to keep serving other work.</strong></p></li></ol><p style="text-align: justify;">When you bound waiting, you bound how long you tie up resources. That makes your service less likely to get &#8220;<em>stuck</em>&#8221; as load increases.</p><ol start="2"><li><p style="text-align: justify;"><strong>User experience.</strong></p></li></ol><p style="text-align: justify;">Users handle fast failure better than slow ambiguity. A quick &#8220;<em>we couldn&#8217;t do that</em>&#8221; (with a clear next step) is often less damaging than a spinner that makes them wonder whether the app is broken, whether they should retry, or whether something partially happened.</p><ol start="3"><li><p style="text-align: justify;"><strong>Overall stability.</strong></p></li></ol><p style="text-align: justify;">Unbounded waiting creates piles: piles of threads, piles of requests, piles of retries, piles of open connections. Timeouts cap pile size and make overload behavior more predictable.</p><div class="pullquote"><p>The key mental shift is this: <strong>timeouts turn unbounded uncertainty into bounded failure.</strong></p></div><p style="text-align: justify;">Not &#8220;<em>no failure</em>&#8221;. Bounded failure. Predictable failure. Failure that you can plan around.</p><p style="text-align: justify;">And if there is one theme that repeats across mature distributed systems, it&#8217;s that reliability is less about eliminating failure and more about choosing how failure behaves under stress.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WBHl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27e2473c-d23a-4b5c-83ef-c22e57fbfb49_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WBHl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27e2473c-d23a-4b5c-83ef-c22e57fbfb49_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!WBHl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27e2473c-d23a-4b5c-83ef-c22e57fbfb49_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!WBHl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27e2473c-d23a-4b5c-83ef-c22e57fbfb49_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!WBHl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27e2473c-d23a-4b5c-83ef-c22e57fbfb49_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WBHl!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27e2473c-d23a-4b5c-83ef-c22e57fbfb49_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/27e2473c-d23a-4b5c-83ef-c22e57fbfb49_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/688361cf-a2c9-4b5f-961a-221e5838537e_1536x1024.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1808058,&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://iam.slys.dev/i/194787089?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F688361cf-a2c9-4b5f-961a-221e5838537e_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WBHl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27e2473c-d23a-4b5c-83ef-c22e57fbfb49_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!WBHl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27e2473c-d23a-4b5c-83ef-c22e57fbfb49_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!WBHl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27e2473c-d23a-4b5c-83ef-c22e57fbfb49_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!WBHl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27e2473c-d23a-4b5c-83ef-c22e57fbfb49_1536x1024.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>Spending a time budget</h1><h2>Budgeted waiting</h2><p style="text-align: justify;">A useful way to think about timeouts is not as isolated numbers sprinkled throughout code, but as a <strong>time budget</strong> that gets spent as a request flows through the system.</p><p style="text-align: justify;"><strong>Step 1: A request begins with a time budget (explicit or implicit).</strong> Sometimes this budget is literally configured (&#8220;<em>this endpoint must respond within 2 seconds</em>&#8221;). Sometimes it&#8217;s implicit in user expectations (&#8220;<em>a checkout page that takes 12 seconds is basically broken</em>&#8221;). Either way, the request has a patience limit.</p><p style="text-align: justify;"><strong>Step 2: Each hop spends part of that budget.</strong> The API gateway spends some time parsing, authenticating, routing. A service spends time doing business logic and calling dependencies. The database spends time executing a query. None of these steps knows in advance how long it will take, but each one consumes time from the same overall budget.</p><p style="text-align: justify;"><strong>Step 3: If time is exceeded at any point, the caller stops waiting and chooses a response path.</strong> This is where timeouts stop being &#8220;<em>numbers</em>&#8221; and become &#8220;<em>policies</em>&#8221;. The system decides: do we fail fast, degrade gracefully, serve stale data, enqueue work, or retry?</p><h2>A request chain with deadlines</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KP9m!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d181a3-81ee-4f65-8e1f-b34b7b322ce1_216x606.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KP9m!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d181a3-81ee-4f65-8e1f-b34b7b322ce1_216x606.png 424w, https://substackcdn.com/image/fetch/$s_!KP9m!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d181a3-81ee-4f65-8e1f-b34b7b322ce1_216x606.png 848w, https://substackcdn.com/image/fetch/$s_!KP9m!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d181a3-81ee-4f65-8e1f-b34b7b322ce1_216x606.png 1272w, https://substackcdn.com/image/fetch/$s_!KP9m!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d181a3-81ee-4f65-8e1f-b34b7b322ce1_216x606.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KP9m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d181a3-81ee-4f65-8e1f-b34b7b322ce1_216x606.png" width="216" height="606" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/11d181a3-81ee-4f65-8e1f-b34b7b322ce1_216x606.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:606,&quot;width&quot;:216,&quot;resizeWidth&quot;:216,&quot;bytes&quot;:20579,&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://iam.slys.dev/i/194787089?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d181a3-81ee-4f65-8e1f-b34b7b322ce1_216x606.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_!KP9m!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d181a3-81ee-4f65-8e1f-b34b7b322ce1_216x606.png 424w, https://substackcdn.com/image/fetch/$s_!KP9m!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d181a3-81ee-4f65-8e1f-b34b7b322ce1_216x606.png 848w, https://substackcdn.com/image/fetch/$s_!KP9m!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d181a3-81ee-4f65-8e1f-b34b7b322ce1_216x606.png 1272w, https://substackcdn.com/image/fetch/$s_!KP9m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d181a3-81ee-4f65-8e1f-b34b7b322ce1_216x606.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="pullquote"><p>The diagram is simple, but it captures something important: <strong>timeouts are not only per-service choices. They&#8217;re a choreography.</strong></p></div><p style="text-align: justify;">There are two ideas here that are worth naming because they prevent a lot of accidental chaos.</p><p style="text-align: justify;">First is the <strong>local timeout</strong>: each component decides how long it will wait for its direct dependency. Service A decides how long it will wait for Service B. Service B decides how long it will wait for the DB.</p><p style="text-align: justify;">Local timeouts are unavoidable. Even if you carry an end-to-end deadline, each hop still needs to translate that into its own concrete waiting behavior, because each hop owns its own resources.</p><p style="text-align: justify;">Second is the <strong>end-to-end deadline</strong>: the original request carries a &#8220;<em>must be done by</em>&#8221; time that flows downstream. This is usually the healthier mental model, because it aligns the whole chain around a shared constraint instead of letting each hop &#8220;<em>pick a number that feels good</em>&#8221;.</p><p style="text-align: justify;">I like the meeting analogy for deadlines: imagine a meeting that ends at 10:00 no matter what. If it&#8217;s 9:55, you don&#8217;t start a new complex topic. You summarize, assign follow-ups, and move on. The end time changes what is rational to attempt.</p><p style="text-align: justify;">Deadlines do the same thing to services. If a service sees that there are only 200ms left, it may choose a cheaper query, skip optional dependencies, or return partial data. The goal isn&#8217;t perfection; it&#8217;s finishing something coherent within the remaining budget.</p><h2>What happens on timeout</h2><p style="text-align: justify;">When a timeout happens, the system needs a plan. Common plans include:</p><ul><li><p><strong>Return an error quickly (fail fast).</strong></p></li></ul><p style="text-align: justify;">Sometimes this is the least bad option, especially for operations where partial success is dangerous.</p><ul><li><p><strong>Use cached or stale data.</strong></p></li></ul><p style="text-align: justify;">You trade freshness for availability, which is often the right call for read-heavy user experiences.</p><ul><li><p><strong>Enqueue work for later (async).</strong></p></li></ul><p style="text-align: justify;">Especially useful when the user doesn&#8217;t need the final result immediately, or when you can acknowledge the request and complete it out of band.</p><ul><li><p><strong>Degrade features.</strong></p></li></ul><p style="text-align: justify;">If recommendations are slow, render the page without them. If analytics logging is slow, skip it. If a profile enrichment call is slow, return the core profile.</p><ul><li><p><strong>Trigger retry logic (carefully).</strong></p></li></ul><p style="text-align: justify;">Retries are not inherently bad, but they are one of the easiest ways to turn a slowdown into a storm. The &#8220;<em>carefully</em>&#8221; is doing a lot of work there, and we&#8217;ll come back to it.</p><p style="text-align: justify;">The important point is that a timeout is only half of the design. The other half is the fallback behavior you choose - and what that fallback implies for correctness, user trust, and downstream load.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YOkw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45532e01-f6d9-4d28-9868-fac9d093d8c8_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YOkw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45532e01-f6d9-4d28-9868-fac9d093d8c8_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!YOkw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45532e01-f6d9-4d28-9868-fac9d093d8c8_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!YOkw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45532e01-f6d9-4d28-9868-fac9d093d8c8_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!YOkw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45532e01-f6d9-4d28-9868-fac9d093d8c8_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YOkw!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45532e01-f6d9-4d28-9868-fac9d093d8c8_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/45532e01-f6d9-4d28-9868-fac9d093d8c8_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f5104729-45db-4797-aa5b-1acffac20e28_1536x1024.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1543417,&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://iam.slys.dev/i/194787089?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5104729-45db-4797-aa5b-1acffac20e28_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YOkw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45532e01-f6d9-4d28-9868-fac9d093d8c8_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!YOkw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45532e01-f6d9-4d28-9868-fac9d093d8c8_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!YOkw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45532e01-f6d9-4d28-9868-fac9d093d8c8_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!YOkw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45532e01-f6d9-4d28-9868-fac9d093d8c8_1536x1024.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>What bounding the wait costs you</h1><p style="text-align: justify;">Timeouts have a reputation for being &#8220;<em>just configuration</em>&#8221;. In practice they are one of the most consequential policy choices you make, because they reshape how your system behaves under stress.</p><h2>What timeouts improve</h2><p style="text-align: justify;">Timeouts are one of the few tools that directly control <strong>tail latency</strong>. Average latency might look fine while your p99 quietly becomes intolerable. The long tail is where user sessions die and where incident tickets get created.</p><p style="text-align: justify;">By bounding waiting, you stop the &#8220;<em>slowest 1%</em>&#8221; from expanding indefinitely and dominating your system&#8217;s capacity. This matters because queues form from the tail, not from the median. A small fraction of very slow requests can hold onto a disproportionate amount of resources.</p><p style="text-align: justify;">Timeouts also provide <strong>resource protection</strong>. When threads don&#8217;t wait forever, your thread pool is less likely to be exhausted. When connections aren&#8217;t held indefinitely, your connection pools recover faster. When request contexts don&#8217;t accumulate, your memory pressure drops.</p><p style="text-align: justify;">Another underrated benefit is <strong>faster detection of bad downstream behavior</strong>. In a distributed system, &#8220;<em>healthy but slow</em>&#8221; is often equivalent to unhealthy. If a dependency is responding slowly enough to cause upstream pileups, then from the caller&#8217;s perspective it is not fulfilling its contract. Timeouts make that reality visible.</p><p style="text-align: justify;">And finally, timeouts help with <strong>blast-radius containment</strong>. Without them, one slow dependency can freeze many upstream services. With them, upstream services can cut their losses, degrade, and continue serving some fraction of traffic.</p><h2>What timeouts make worse</h2><p style="text-align: justify;">Timeouts also make some problems more visible, and visibility can feel like regression.</p><p style="text-align: justify;">A timeout converts &#8220;<em>eventual success</em>&#8221; into &#8220;<em>declared failure</em>&#8221;. That means you will often see <strong>more errors</strong> in dashboards, even as user experience improves overall. The system becomes more honest about what it can&#8217;t do within the required time.</p><p style="text-align: justify;">Timeouts also increase <strong>inconsistency risk</strong>. A caller can time out and give up, while the callee continues processing and eventually succeeds. That&#8217;s not a theoretical edge case; it happens constantly in real systems. If the operation has side effects (charging a card, reserving inventory, sending an email), then &#8220;<em>caller gave up</em>&#8221; does not mean &#8220;<em>nothing happened</em>&#8221;.</p><p style="text-align: justify;">This is the classic double-charge: the first attempt may have succeeded late, but the client timed out and retried, causing the side effect to happen twice. The timeout didn&#8217;t cause the bug alone; it revealed the system&#8217;s lack of a safe strategy for duplicate requests.</p><p style="text-align: justify;">Timeouts can also create <strong>higher retry pressure</strong> when retries are naive. If every timeout triggers an immediate retry, you&#8217;re effectively multiplying load precisely when the system is already struggling. This can turn a mild slowdown into a self-sustaining overload loop.</p><h2>New risks introduced by timeouts</h2><p style="text-align: justify;">Once you start bounding waiting, you introduce new failure modes that are less obvious than &#8220;<em>it&#8217;s slow</em>&#8221;.</p><p style="text-align: justify;">One is the <strong>retry storm</strong> (or thundering herd): many clients time out around the same time and retry together, spiking traffic in synchronized waves. Systems often fail not because they can&#8217;t handle steady load, but because they can&#8217;t handle synchronized bursts created by uniform retry behavior.</p><p style="text-align: justify;">Another is <strong>duplicate work</strong>. Even if duplicate side effects are prevented, duplicate computation can still be expensive. If a slow request is still being processed and you retry, you may now have two expensive operations running for one user action.</p><p style="text-align: justify;">And there&#8217;s the &#8220;<em>split-brain</em>&#8221; user experience: the user sees failure, but the side effect happened. That is often the most trust-damaging outcome because it breaks the user&#8217;s mental model. If the UI says &#8220;<em>payment failed</em>&#8221;, users assume no money moved. If the system later contradicts that, it feels like betrayal - even if the backend is technically consistent.</p><h2>The trade-offs you can't escape</h2><p style="text-align: justify;">Timeout decisions force trade-offs you can&#8217;t escape; you can only choose where you sit.</p><p style="text-align: justify;"><strong>Latency vs correctness:</strong> Short timeouts make the system feel responsive, but they increase the chance that you cut off valid work. If your downstream normally completes in 400ms but occasionally takes 900ms, a 500ms timeout will convert that occasional variance into user-visible failures. Sometimes that&#8217;s acceptable; sometimes it&#8217;s not.</p><p style="text-align: justify;"><strong>Availability vs correctness:</strong> Serving cached or stale data keeps the system available, but it may be wrong. Whether that&#8217;s acceptable depends on the feature. Showing a slightly stale feed is fine; showing a stale account balance might not be.</p><p style="text-align: justify;"><strong>Simplicity vs scalability:</strong> &#8220;<em>Just wait longer</em>&#8221; is conceptually simple and can even feel safer. The problem is that its cost grows with concurrency. At small scale you can afford to be patient. At large scale, patience can be what kills you.</p><p style="text-align: justify;"><strong>Local optimization vs end-to-end behavior:</strong> If each service chooses timeouts independently, the chain becomes chaotic. One service might wait 2 seconds, while its dependency gives up after 200ms. That mismatch produces wasted work, confusing logs, and unpredictable user experience. End-to-end time budgeting tends to reduce this chaos by aligning the system&#8217;s patience.</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/xEMc1/3/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d5c250ca-8f2d-41e5-9f08-9a308301d25f_1220x760.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2fc85364-cb75-46e2-bf3d-0e1e76977884_1220x880.png&quot;,&quot;height&quot;:400,&quot;title&quot;:&quot;Timeout trade-offs: you don't escape them, you only choose where to sit.&quot;,&quot;description&quot;:&quot;&quot;,&quot;belowTheFold&quot;:true}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/xEMc1/3/" width="730" height="400" frameborder="0" scrolling="no" loading="lazy"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><h1>Timeouts, component by component</h1><p>Timeouts show up in different forms across a system. The underlying idea is the same - bounded patience - but the symptoms and failure modes vary by component.</p><h2>Databases</h2><p style="text-align: justify;">Databases are often where &#8220;<em>the system is frozen but not dead</em>&#8221; first becomes visible.</p><p style="text-align: justify;">A database can be up, accepting connections, and returning <em>some</em> queries quickly - while a subset of queries run long, hold locks, or monopolize resources. Without query timeouts, slow queries can sit on connections for a long time. Under load, that behavior starves the connection pool, and suddenly even fast queries can&#8217;t get a connection.</p><p style="text-align: justify;">Transaction timeouts play a related role. Long-running transactions can hold locks longer than intended, blocking other operations that would otherwise be quick. The result is a system that looks healthy at the machine level - CPU is fine - while user requests pile up waiting for locks.</p><p style="text-align: justify;">This is one reason database incidents can feel so confusing: nothing is &#8220;<em>burning</em>&#8221;, but everything is waiting.</p><p style="text-align: justify;">Timeouts don&#8217;t fix bad queries or poor indexing, but they prevent those problems from turning into system-wide paralysis by limiting how long any single request is allowed to monopolize shared database resources.</p><h2>APIs / service-to-service calls</h2><p style="text-align: justify;">In service-to-service calls, timeouts define how long one service is willing to be blocked waiting on another. This sounds straightforward, but it interacts with load in a way that surprises people.</p><p style="text-align: justify;">If Service A has 200 worker threads and it calls Service B, then long timeouts mean threads in A can be tied up doing nothing but waiting. Under burst traffic, you can exhaust A&#8217;s threads even if A&#8217;s own CPU usage is low, simply because A is acting like a waiting room for B.</p><p style="text-align: justify;">And when A times out, it often returns an error upstream - but B may still be processing the request. That means the system can do real work that the user never sees, which is a hidden cost. It also means side effects can occur after the user believes the operation failed.</p><p style="text-align: justify;">This is one of the reasons timeouts and idempotency are so tightly linked in practice: timeouts make it normal for callers to not know what happened downstream.</p><h2>Queues and async systems</h2><p style="text-align: justify;">Async systems have their own timeout-shaped problems.</p><p style="text-align: justify;">Workers can crash mid-job. They can hang. They can be paused. They can get stuck on a slow dependency. If a job is &#8220;<em>checked out</em>&#8221; by a worker and there&#8217;s no mechanism to reclaim it, you can end up with jobs that are effectively lost - neither completed nor retried.</p><p style="text-align: justify;">This is where concepts like processing deadlines and visibility timeouts come in. The system says: &#8220;<em>A worker has this long to finish. If it doesn&#8217;t, we&#8217;ll assume the job is stuck and make it available again</em>&#8221;.</p><p style="text-align: justify;">That assumption can cause duplicate processing, so it pushes you toward idempotent job handlers. But the alternative - jobs that vanish into limbo - is often worse.</p><p style="text-align: justify;">Timeouts in async systems are a way of turning &#8220;<em>maybe the worker will come back</em>&#8221; into a concrete policy decision: after some time, we stop waiting and we try again.</p><h2>Caches</h2><p style="text-align: justify;">Caches introduce a different flavor of timeout decision: <strong>do you wait for the origin?</strong></p><p style="text-align: justify;">If a cache miss triggers a call to an origin service or database, then slow origin responses can cause cache clients to pile up waiting - especially if many requests miss at once (for example, after a cache eviction or a hot key expiration).</p><p>Timeouts shape whether you:</p><ul><li><p>wait for the origin,</p></li><li><p>serve stale data,</p></li><li><p>or fail the request.</p></li></ul><p style="text-align: justify;">In many real systems, preventing cache stampedes is a combination of bounded waiting plus techniques like jitter and serve-stale behavior. The details vary, but the intuition is stable: you don&#8217;t want a brief origin slowdown to turn into thousands of synchronized cache-miss callers all waiting (and then retrying).</p><h2>Microservices and UI aggregation endpoints</h2><p style="text-align: justify;">Aggregation endpoints - services that compose responses from multiple downstream dependencies - are where time budgeting becomes very tangible.</p><p style="text-align: justify;">If you&#8217;re building a page that needs profile info, notifications, recommendations, and experiments, you quickly discover that the user doesn&#8217;t value all of those equally. Recommendations arriving 400ms late are often worse than no recommendations at all, because they block the whole page render.</p><p style="text-align: justify;">So aggregators often use explicit per-dependency budgets: &#8220;<em>If recommendations don&#8217;t arrive in 100ms, render without them</em>&#8221;. That is a timeout decision, but it&#8217;s also a product decision: completeness is less important than responsiveness for that component.</p><p style="text-align: justify;">Aggregation is also where mismatched timeouts become painful. If the aggregator times out at 100ms but downstream keeps working for 2 seconds, you&#8217;ve created hidden load that users don&#8217;t benefit from. Good end-to-end deadline propagation helps downstream services avoid doing expensive work that cannot possibly make it back to the user in time.</p><h1>Too patient, too aggressive, or absent</h1><p style="text-align: justify;">Timeouts fail in two directions: too much patience and too little. And the absence of a timeout is not neutral - it&#8217;s just &#8220;<em>infinite patience</em>&#8221;, which is usually the worst of both worlds.</p><h2>Typical mistakes</h2><p style="text-align: justify;"><strong>No timeouts at all.</strong> This is the easiest mistake to make early on because things &#8220;<em>work</em>&#8221; until they don&#8217;t. Without timeouts, partial failures turn into silent resource exhaustion. Requests accumulate. Thread pools saturate. Connection pools drain. Eventually everything backs up behind the slowest dependency, and the incident looks like a system-wide stall rather than a crisp failure.</p><p style="text-align: justify;"><strong>Timeouts set too high (&#8220;</strong><em><strong>be safe</strong></em><strong>&#8221;).</strong> This is a very human instinct: if timeouts cause errors, then longer timeouts should reduce errors. Locally, that can be true. System-wide, it often increases blast radius. Long timeouts turn every upstream service into a buffer for downstream slowness. You might reduce error rates while increasing the chance of a total stall.</p><p style="text-align: justify;"><strong>Timeouts set too low (&#8220;</strong><em><strong>be fast</strong></em><strong>&#8221;).</strong> At the other extreme, aggressive timeouts can create self-inflicted outages. Normal latency variance starts to look like failure. Downstream services might be healthy, but they&#8217;re denied the time they need during ordinary load fluctuations. This can create a feedback loop where timeouts trigger retries, retries increase load, load increases latency, and latency triggers more timeouts.</p><p style="text-align: justify;"><strong>Retries without coordination.</strong> Timeouts plus immediate retries are a classic outage recipe. If the system is slow because it&#8217;s overloaded, retrying increases overload. If it&#8217;s slow because a dependency is degraded, retrying creates more work for the degraded component. Retries can be valuable, but only when they are rate-limited, jittered, bounded, and paired with a clear understanding of what failures they are meant to address.</p><p style="text-align: justify;"><strong>Mismatched timeouts in a chain.</strong> If an upstream waits 2 seconds but a downstream times out at 200ms, you can end up with wasted work and confusing traces: the upstream says &#8220;<em>I waited forever</em>&#8221;, the downstream says &#8220;<em>I gave up quickly</em>&#8221;, and neither is wrong from its own perspective. What&#8217;s missing is an end-to-end budget that aligns expectations across the chain.</p><p style="text-align: justify;"><strong>No idempotency strategy for operations that may partially succeed.</strong> Timeouts make &#8220;<em>unknown outcome</em>&#8221; normal. If you don&#8217;t have a way to safely handle duplicate requests, you end up with the user-facing split-brain: &#8220;<em>it failed</em>&#8221; plus &#8220;<em>side effect happened</em>&#8221;. Payment flows are the obvious example, but the pattern appears everywhere: sending messages, creating orders, updating profiles, issuing refunds, provisioning resources.</p><h2>User/operator symptoms</h2><p style="text-align: justify;">From the user&#8217;s perspective, timeout problems often look like:</p><ul><li><p style="text-align: justify;">spinners that end in &#8220;<em>try again</em>&#8221;,</p></li><li><p style="text-align: justify;">repeated attempts that sometimes work and sometimes don&#8217;t,</p></li><li><p style="text-align: justify;">duplicate actions (double charges, duplicate orders),</p></li><li><p style="text-align: justify;">inconsistent state across screens (&#8220;<em>order not found</em>&#8221; then &#8220;<em>order shipped</em>&#8221;).</p></li></ul><p style="text-align: justify;">From the operator&#8217;s perspective, timeout problems often show up as:</p><ul><li><p style="text-align: justify;">high p99 latency and timeouts while averages look okay,</p></li><li><p style="text-align: justify;">saturated connection pools,</p></li><li><p style="text-align: justify;">thread exhaustion or request queue growth,</p></li><li><p style="text-align: justify;">retry spikes,</p></li><li><p style="text-align: justify;">&#8220;<em>nothing is down, but it&#8217;s unusable</em>&#8221; incidents.</p></li></ul><p style="text-align: justify;">That last one is worth lingering on. The most expensive incidents aren&#8217;t always the dramatic crashes. They&#8217;re the gray failures: the system technically responds, but slowly enough that users abandon it, and slowly enough that internal retries and backlogs quietly amplify the damage.</p><p style="text-align: justify;">Timeouts are one of the main ways you prevent gray failures from spreading.</p><h1>Systems of bounded patience</h1><p style="text-align: justify;">If you only remember one thing about timeouts, I&#8217;d want it to be this: in distributed systems, you rarely get certainty. You get time-based decisions.</p><p style="text-align: justify;">A timeout is less a number and more a statement of values:</p><ul><li><p style="text-align: justify;">When things get slow, what do we value most - speed, accuracy, completeness, safety?</p></li><li><p style="text-align: justify;">Which user experiences should degrade gracefully, and which should fail hard?</p></li><li><p style="text-align: justify;">Which dependencies are essential, and which are &#8220;<em>nice to have</em>&#8221;?</p></li><li><p style="text-align: justify;">How much hidden work are we willing to do that might not benefit the user?</p></li></ul><div><hr></div><p style="text-align: justify;"><strong>&#128172; What timeout value have you most regretted setting &#8212; too patient or too aggressive? Tell me what it broke.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/timeouts-the-most-important-number/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/timeouts-the-most-important-number/comments"><span>Leave a comment</span></a></p><div><hr></div><p style="text-align: justify;">These are policy questions, not just engineering questions. They shape reliability because reliability is not the absence of failure; it&#8217;s the system&#8217;s ability to keep delivering something meaningful under stress.</p><p style="text-align: justify;">Coordination is expensive in distributed systems. The more components you need to agree, the more likely you are to hit time limits. That&#8217;s not because engineers are bad at their jobs; it&#8217;s because each component adds variability: network hops, queues, load balancers, retries, GC pauses, lock contention, noisy neighbors.</p><p style="text-align: justify;">Timeouts are one of the few tools we have to keep that variability from consuming the entire system.</p><p style="text-align: justify;">There&#8217;s also an organizational analogy that I think is more than cute - it&#8217;s instructive.</p><p style="text-align: justify;">A team that never sets deadlines can appear calm. Work can expand to fill the available time. Long tasks can block other tasks indefinitely. Nobody is forced to make trade-offs. It feels polite.</p><p style="text-align: justify;">Then a crisis hits: a customer needs an answer, a security issue needs a patch, a launch date arrives. Suddenly the lack of deadlines reveals itself as fragility. Everything is blocked by one long-running effort.</p><p style="text-align: justify;">Deadlines don&#8217;t guarantee quality, but they prevent one task from consuming all attention forever.</p><p style="text-align: justify;">Timeouts are deadlines for machines. They don&#8217;t guarantee correctness, but they prevent one slow dependency from consuming all of your system&#8217;s ability to respond.</p><div class="callout-block" data-callout="true"><p>And that&#8217;s the reflective takeaway: reliability isn&#8217;t only about preventing failure. It&#8217;s about <strong>choosing how to fail</strong> so the rest of the system can keep living.</p></div><h1>Choosing how to fail</h1><p style="text-align: justify;">Timeouts are how distributed systems turn uncertainty into a decision. The network will not tell you whether a request is &#8220;<em>slow but fine</em>&#8221; or &#8220;<em>never coming back</em>&#8221;. It will simply make you wait. If your system has no explicit policy for when to stop waiting, it will eventually pay for that patience with resources it can&#8217;t replenish fast enough under load.</p><p style="text-align: justify;">Waiting is not a neutral choice. It has a cost in threads, connections, memory, queue space, and user attention. Without timeouts, partial failures spread outward until they become system-wide stalls. With timeouts, you trade &#8220;<em>maybe success later</em>&#8221; for &#8220;<em>bounded failure now</em>&#8221;, and that trade is often what keeps the rest of the system responsive.</p><p style="text-align: justify;">But timeouts are not a free win. Bad timeout values and uncoordinated retries can create their own outages: retry storms, duplicate work, and user experiences where an operation &#8220;<em>failed</em>&#8221; and &#8220;<em>happened</em>&#8221; at the same time. The most stable systems treat timeouts as part of an end-to-end time budget, not as isolated numbers chosen independently by each service.</p><p style="text-align: justify;">And once you accept time budgeting as the core model, the real design work becomes clearer: the timeout itself is only the stopping rule. The hard part is choosing what you do next - fail fast, degrade, serve stale, or shift work to async - while being honest about the trade-offs in correctness, availability, and user trust.</p><p style="text-align: justify;">If you want to follow this post with the natural next chapter, the companion topic is retries. Timeouts define when we stop waiting; retries define how we reattempt without turning &#8220;<em>slow</em>&#8221; into &#8220;<em>down</em>&#8221;. That pairing is where a lot of real-world reliability stories are written - for better or worse.</p><div><hr></div><p style="text-align: justify;"><strong>&#128236; This is System Design Fundamentals &#8212; one primitive at a time, no hype. Subscribe and the next chapter lands in your inbox.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>References</h1><ol><li><p style="text-align: justify;"><a href="https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing">The Fallacies of Distributed Computing</a> <em>by L. Peter Deutsch et al.</em> | Wikipedia</p></li><li><p style="text-align: justify;"><a href="https://colin-scott.github.io/personal_website/research/interactive_latency.html">Latency Numbers Every Programmer Should Know</a> <em>by Colin Scott</em> | interactive, after Jeff Dean's numbers</p></li><li><p style="text-align: justify;"><a href="https://www.usenix.org/legacy/event/lisa07/tech/full_papers/hamilton/hamilton.pdf">On Designing and Deploying Internet-Scale Services</a> <em>by James Hamilton</em> | USENIX LISA '07</p></li><li><p style="text-align: justify;"><a href="https://dataintensive.net/">Designing Data-Intensive Applications</a> <em>by Martin Kleppmann</em> | O'Reilly</p></li><li><p style="text-align: justify;"><a href="https://pragprog.com/titles/mnee2/release-it-second-edition/">Release It!</a> <em>by Michael Nygard</em> | Pragmatic Bookshelf</p></li><li><p style="text-align: justify;"><a href="https://www.oreilly.com/library/view/designing-distributed-systems/9781491983638/">Designing Distributed Systems</a> <em>by Brendan Burns</em> | O'Reilly</p></li></ol>]]></content:encoded></item><item><title><![CDATA[Backpressure — when saying "slow down" saves the system]]></title><description><![CDATA[Systems Literacy]]></description><link>https://iam.slys.dev/p/backpressure-when-saying-slow-down</link><guid isPermaLink="false">https://iam.slys.dev/p/backpressure-when-saying-slow-down</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 20 Jul 2026 20:28:07 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!mtIH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabefa6e2-09fb-4a1d-a118-58274576ef48_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mtIH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabefa6e2-09fb-4a1d-a118-58274576ef48_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mtIH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabefa6e2-09fb-4a1d-a118-58274576ef48_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!mtIH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabefa6e2-09fb-4a1d-a118-58274576ef48_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!mtIH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabefa6e2-09fb-4a1d-a118-58274576ef48_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!mtIH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabefa6e2-09fb-4a1d-a118-58274576ef48_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mtIH!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabefa6e2-09fb-4a1d-a118-58274576ef48_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/abefa6e2-09fb-4a1d-a118-58274576ef48_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b465f691-3ff2-4c53-b96b-7cc229dd3740_1536x1024.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2238190,&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://iam.slys.dev/i/194774545?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb465f691-3ff2-4c53-b96b-7cc229dd3740_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mtIH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabefa6e2-09fb-4a1d-a118-58274576ef48_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!mtIH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabefa6e2-09fb-4a1d-a118-58274576ef48_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!mtIH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabefa6e2-09fb-4a1d-a118-58274576ef48_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!mtIH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabefa6e2-09fb-4a1d-a118-58274576ef48_1536x1024.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 style="text-align: justify;">Picture a flash sale.</p><p style="text-align: justify;">Not a steady &#8220;<em>more traffic than usual</em>&#8221;. A step-function.</p><p style="text-align: justify;">One minute the site is busy-but-fine. The next minute, traffic is unreasonable. Social posts land, push notifications fire, and suddenly your checkout flow is getting hit like it&#8217;s the only doorway out of a stadium.</p><p style="text-align: justify;">And what&#8217;s unsettling is that it doesn&#8217;t fully go down.</p><p>It gets <em>weird</em>.</p><p style="text-align: justify;">Pages still load. Browsing still works. Product detail pages are mostly okay. But checkout turns into a haunted house:</p><ul><li><p style="text-align: justify;">the &#8220;<em>Place order</em>&#8221; button spins and spins</p></li><li><p style="text-align: justify;">some users time out quickly, others succeed after <strong>30 - 60 seconds</strong></p></li><li><p style="text-align: justify;">support starts reporting duplicates: &#8220;<em>I clicked twice because it froze</em>&#8221;</p></li><li><p style="text-align: justify;">your payment provider dashboard looks normal-ish, but your database graphs look&#8230; sticky</p></li></ul><p style="text-align: justify;">Then the on-call engineer notices something that feels wrong.</p><p style="text-align: justify;">CPU isn&#8217;t pegged everywhere. Some app pods are cruising at 30 - 40%. Autoscaling has helped a bit. Nothing is obviously &#8220;<em>on fire</em>&#8221; at the compute layer.</p><p style="text-align: justify;">But one downstream dependency - maybe the database, maybe inventory, maybe payments - has gotten slower than usual. Not dead. Just slower.</p><p style="text-align: justify;">And in the metrics you can see the shape of the disaster forming:</p><ul><li><p style="text-align: justify;">queue depth climbing steadily</p></li><li><p style="text-align: justify;">in-flight requests rising and never coming down</p></li><li><p style="text-align: justify;">timeouts starting to pepper the logs</p></li><li><p style="text-align: justify;">thread pools slowly filling like a bathtub with a clogged drain</p></li></ul><p style="text-align: justify;">That&#8217;s the scary part: <strong>a small slowdown starts behaving like an outage.</strong></p><p style="text-align: justify;">Not because the system is &#8220;<em>down</em>&#8221;. Because it&#8217;s panicking.</p><p style="text-align: justify;">You can feel it in the symptoms: the system is spending more and more energy <em>waiting</em>. And waiting is not free.</p><p style="text-align: justify;">We&#8217;ll name what&#8217;s happening soon. For now, just sit with the lived experience:</p><p style="text-align: justify;">A checkout system that&#8217;s &#8220;<em>mostly up</em>&#8221; can still become unusable. And it can happen fast.</p><div><hr></div><p style="text-align: justify;"><strong>&#128678; If you've watched a small slowdown snowball into a self-inflicted outage, this is the mechanism &#8212; send it to whoever owns the checkout path.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/backpressure-when-saying-slow-down?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/backpressure-when-saying-slow-down?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h1>When work outpaces capacity</h1><h2>What breaks at scale</h2><p style="text-align: justify;"><strong>Work arrives faster than it can be completed.</strong> This is the most basic mismatch in systems. If a downstream can handle <strong>500 req/s</strong> but receives <strong>1,000 req/s</strong>, the extra 500 doesn&#8217;t disappear.</p><p style="text-align: justify;">It waits somewhere.</p><p style="text-align: justify;">And it&#8217;s tempting to think waiting is harmless. After all, queues exist. Buffers exist. &#8220;<em>We&#8217;ll just let it pile up for a bit.</em>&#8221;</p><p style="text-align: justify;">But that brings us to the second point.</p><p style="text-align: justify;"><strong>Waiting consumes real resources.</strong> Each &#8220;<em>waiting</em>&#8221; request is not just a line in a log. It&#8217;s usually holding onto <em>something</em>:</p><ul><li><p style="text-align: justify;">memory for request/response objects</p></li><li><p style="text-align: justify;">sockets and file descriptors</p></li><li><p style="text-align: justify;">threads or async tasks</p></li><li><p style="text-align: justify;">entries in a queue</p></li><li><p style="text-align: justify;">locks, transactions, or connection pool slots</p></li><li><p style="text-align: justify;">CPU cycles doing retries, timeouts, and bookkeeping</p></li></ul><p style="text-align: justify;">So the cost of &#8220;<em>we&#8217;ll just wait</em>&#8221; is that you&#8217;re slowly converting a capacity mismatch into <em>resource exhaustion</em>.</p><p style="text-align: justify;">And then there&#8217;s the reality that makes this worse than most people expect:</p><p style="text-align: justify;"><strong>Partial failure is common.</strong> Dependencies often don&#8217;t crash cleanly. They degrade.</p><ul><li><p style="text-align: justify;">a DB replica is overloaded and starts responding in 800ms instead of 30ms</p></li><li><p style="text-align: justify;">a third-party API starts tailing at 5 - 10 seconds</p></li><li><p style="text-align: justify;">a hot shard gets noisy neighbors</p></li><li><p style="text-align: justify;">a cache cluster is fine, but a small percentage of keys now miss and trigger expensive recomputation</p></li></ul><p style="text-align: justify;">This kind of slowdown is the most dangerous failure mode because it&#8217;s ambiguous. It still &#8220;<em>works</em>&#8221;, but it works slowly enough to poison everything upstream.</p><h2>Why naive solutions stop working</h2><p style="text-align: justify;"><strong>Unbounded buffering turns overload into latency (and then into outages).</strong> If you accept requests into an unbounded queue (or a queue that is &#8220;<em>effectively unbounded</em>&#8221;), you&#8217;re choosing a particular failure shape:</p><ul><li><p style="text-align: justify;">you <em>hide</em> overload for a while</p></li><li><p style="text-align: justify;">latency grows gradually</p></li><li><p style="text-align: justify;">timeouts start to appear far from the root cause</p></li><li><p style="text-align: justify;">recovery becomes painful because you now have a backlog to drain</p></li></ul><p style="text-align: justify;">Large buffers are polite. They let everyone in the door.</p><p style="text-align: justify;">But politeness during overload is often just delayed failure. And delayed failure tends to be bigger.</p><p style="text-align: justify;"><strong>Retries multiply traffic during the worst moment.</strong> Timeouts cause clients to retry. Services retry each other. SDKs retry silently. Load balancers retry idempotent calls. Humans retry by clicking the button again.</p><p style="text-align: justify;">Now your bottleneck - the one thing that is already slow - gets hit with <em>more</em> traffic.</p><p style="text-align: justify;">This is how a system DDoSes itself without any malicious actor. It&#8217;s just a collection of &#8220;<em>helpful</em>&#8221; components doing what they were told: &#8220;<em>try again if it fails</em>&#8221;.</p><p style="text-align: justify;"><strong>More concurrency can reduce throughput.</strong> This is deeply unintuitive when you&#8217;re early in your systems journey. It feels like:</p><div class="pullquote"><p style="text-align: center;">&#8220;If we have a backlog, we should process more in parallel.&#8221;</p></div><p style="text-align: justify;">But shared bottlenecks don&#8217;t behave that way.</p><ul><li><p style="text-align: justify;">DB locks become contended</p></li><li><p>connection pools run out</p></li><li><p style="text-align: justify;">caches thrash</p></li><li><p style="text-align: justify;">CPU spends more time context switching</p></li><li><p style="text-align: justify;">tail latencies inflate, which increases in-flight work upstream</p></li><li><p style="text-align: justify;">eventually effective throughput <em>drops</em> even though you&#8217;re &#8220;<em>doing more</em>&#8221;</p></li></ul><p style="text-align: justify;">A good mental model is a busy intersection. Adding more cars doesn&#8217;t increase throughput once you&#8217;ve reached saturation. It creates gridlock.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4a79!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faada2308-8171-4c88-ba61-f8770eed4096_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4a79!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faada2308-8171-4c88-ba61-f8770eed4096_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!4a79!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faada2308-8171-4c88-ba61-f8770eed4096_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!4a79!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faada2308-8171-4c88-ba61-f8770eed4096_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!4a79!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faada2308-8171-4c88-ba61-f8770eed4096_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4a79!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faada2308-8171-4c88-ba61-f8770eed4096_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aada2308-8171-4c88-ba61-f8770eed4096_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8bf7263-4101-46f4-b964-5f310e6cac87_1536x1024.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1618344,&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://iam.slys.dev/i/194774545?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8bf7263-4101-46f4-b964-5f310e6cac87_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4a79!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faada2308-8171-4c88-ba61-f8770eed4096_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!4a79!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faada2308-8171-4c88-ba61-f8770eed4096_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!4a79!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faada2308-8171-4c88-ba61-f8770eed4096_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!4a79!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faada2308-8171-4c88-ba61-f8770eed4096_1536x1024.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>Distributed systems realities</h2><p style="text-align: justify;">This whole situation is the consequence of a few boring truths that become dramatic at scale:</p><ul><li><p style="text-align: justify;"><strong>Latency:</strong> when a downstream gets slower, upstream holds onto more in-flight work.</p></li><li><p style="text-align: justify;"><strong>Concurrency:</strong> many callers amplify contention and queue growth.</p></li><li><p style="text-align: justify;"><strong>Independent components:</strong> each service &#8220;<em>does its job</em>&#8221; unless forced to coordinate.</p></li><li><p style="text-align: justify;"><strong>Uncertainty:</strong> you can&#8217;t reliably tell &#8220;<em>slow</em>&#8221; from &#8220;<em>dead</em>&#8221; in the moment.</p></li></ul><p style="text-align: justify;">And because you can&#8217;t tell &#8220;<em>slow</em>&#8221; from &#8220;<em>dead</em>&#8221;, systems often behave optimistically. They keep sending work, hoping the slowdown is temporary.</p><p style="text-align: justify;">Hope is not a strategy. In distributed systems, hope is often how outages start.</p><h1>Pushing "slow down" upstream</h1><div class="callout-block" data-callout="true"><p style="text-align: justify;"><strong>Backpressure is a feedback mechanism that pushes &#8220;</strong><em><strong>slow down</strong></em><strong>&#8221; upstream so the system stays within safe operating limits.</strong></p></div><p style="text-align: justify;">Notice what that implies: we&#8217;re not talking about a single feature. Backpressure is a <em>relationship</em> between parts of a system. A way for a constrained component to say:</p><div class="pullquote"><p>&#8220;I&#8217;m falling behind. If you keep pushing at this rate, we&#8217;ll both go down.&#8221;</p></div><p style="text-align: justify;">Backpressure tends to optimize for a few pragmatic goals:</p><ul><li><p style="text-align: justify;"><strong>stability over politeness</strong></p></li></ul><p style="text-align: justify;">It&#8217;s better to refuse some work than to stall everything.</p><ul><li><p style="text-align: justify;"><strong>boundedness</strong></p></li></ul><p style="text-align: justify;">Finite queues. Finite in-flight requests. Finite memory growth. The system should have hard edges.</p><ul><li><p style="text-align: justify;"><strong>recoverability</strong></p></li></ul><p style="text-align: justify;">Avoid digging a hole you can&#8217;t climb out of. If you build a two-hour backlog, you might survive the spike but still lose the incident because you can&#8217;t drain fast enough.</p><h2>A stadium with one exit</h2><p style="text-align: justify;">Think about a stadium with a single narrow exit.</p><p style="text-align: justify;">If you let everyone rush that door at once, you get crushing gridlock. People push, movement slows, panic rises, and eventually nobody moves.</p><p style="text-align: justify;">So what do cities (and event staff) do?</p><p style="text-align: justify;">They meter the flow. Barriers. Lines. Temporary gates. A controlled release of people.</p><p style="text-align: justify;">It&#8217;s annoying in the moment. But it prevents a worse outcome.</p><p style="text-align: justify;">Backpressure is that metering. It&#8217;s the system admitting: &#8220;<em>We have one door here. We can&#8217;t pretend we have ten.</em>&#8221;</p><h1>From pressure to a stable rate</h1><h2>The basic loop: pressure &#8594; signal &#8594; adaptation</h2><p style="text-align: justify;">Let&#8217;s walk through the loop as it happens in real incidents. Not the clean textbook version - the slightly messy one you see on dashboards.</p><ol><li><p style="text-align: justify;"><strong>Downstream capacity drops</strong> </p><p style="text-align: justify;">Maybe the DB is slow because of a long-running query. Maybe a payment provider is tailing. Maybe a hot partition is melting. The exact reason doesn&#8217;t matter. What matters is: completions per second go down.</p></li><li><p style="text-align: justify;"><strong>Pressure accumulates</strong> </p><p style="text-align: justify;">Upstream continues sending at the old rate. Now you have mismatch. Pressure shows up as:</p></li></ol><ul><li><p style="text-align: justify;">rising queue length</p></li><li><p style="text-align: justify;">rising in-flight request count</p></li><li><p style="text-align: justify;">higher connection pool usage</p></li><li><p style="text-align: justify;">rising latency</p></li><li><p style="text-align: justify;">more time spent waiting on locks or IO</p></li></ul><ol start="3"><li><p style="text-align: justify;"><strong>A boundary detects unsafe conditions</strong> </p><p style="text-align: justify;">This is important: backpressure is usually triggered before &#8220;<em>100% broken</em>&#8221;.</p><p>It&#8217;s &#8220;<em>we&#8217;re approaching unsafe</em>&#8221;.</p></li></ol><ul><li><p style="text-align: justify;">queue at 80% of capacity</p></li><li><p style="text-align: justify;">worker pool saturated</p></li><li><p style="text-align: justify;">error rate rising</p></li><li><p style="text-align: justify;">p95/p99 crossing a threshold</p></li><li><p style="text-align: justify;">connection pool nearly exhausted</p></li></ul><ol start="4"><li><p style="text-align: justify;"><strong>Backpressure signal travels upstream</strong> </p><p style="text-align: justify;">The system communicates &#8220;<em>slow down</em>&#8221; by:</p></li></ol><ul><li><p style="text-align: justify;">rejecting requests (fast failure)</p></li><li><p style="text-align: justify;">throttling (rate limiting)</p></li><li><p style="text-align: justify;">blocking (making callers wait)</p></li><li><p style="text-align: justify;">degrading (serving a simpler response)</p><p></p><p style="text-align: justify;">Different systems choose different signals, but they all communicate the same thing: <em>do less work right now.</em></p></li></ul><ol start="5"><li><p style="text-align: justify;"><strong>Upstream adapts</strong> </p><p style="text-align: justify;">Good upstream behavior looks like:</p></li></ol><ul><li><p style="text-align: justify;">reduce send rate</p></li><li><p style="text-align: justify;">back off (increasing delays between attempts)</p></li><li><p style="text-align: justify;">prioritize higher-value work</p></li><li><p style="text-align: justify;">shed load (drop optional work)</p></li></ul><ol start="6"><li><p style="text-align: justify;"><strong>System reaches a stable rate</strong> </p><p style="text-align: justify;">Arrivals &#8776; completions.</p><p style="text-align: justify;">Not because the world got nicer, but because the system stopped pretending it had infinite capacity.</p></li></ol><p style="text-align: justify;">This is the quiet win of backpressure: you trade a chaotic meltdown for controlled degradation.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7hiB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d892976-e9c1-47fe-a9d5-562a604860aa_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7hiB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d892976-e9c1-47fe-a9d5-562a604860aa_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!7hiB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d892976-e9c1-47fe-a9d5-562a604860aa_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!7hiB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d892976-e9c1-47fe-a9d5-562a604860aa_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!7hiB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d892976-e9c1-47fe-a9d5-562a604860aa_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7hiB!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d892976-e9c1-47fe-a9d5-562a604860aa_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2d892976-e9c1-47fe-a9d5-562a604860aa_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/72946f93-df22-495a-bf26-de2fb993b5d8_1536x1024.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1638789,&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://iam.slys.dev/i/194774545?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72946f93-df22-495a-bf26-de2fb993b5d8_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7hiB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d892976-e9c1-47fe-a9d5-562a604860aa_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!7hiB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d892976-e9c1-47fe-a9d5-562a604860aa_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!7hiB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d892976-e9c1-47fe-a9d5-562a604860aa_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!7hiB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d892976-e9c1-47fe-a9d5-562a604860aa_1536x1024.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>Where backpressure is applied</h2><p style="text-align: justify;">Backpressure isn&#8217;t one location. It&#8217;s a design choice about <em>where</em> you want to enforce limits.</p><p style="text-align: justify;"><strong>Admission control at entry points</strong> </p><blockquote><p><em>&#8220;We&#8217;re at capacity; we won&#8217;t accept more right now.&#8221;</em></p></blockquote><p style="text-align: justify;">This is the most user-visible form: 429s, &#8220;<em>Try again</em>&#8221;, &#8220;<em>We&#8217;re experiencing high demand</em>&#8221;.</p><p style="text-align: justify;"><strong>Bounded queues between stages</strong> </p><blockquote><p><em>&#8220;The waiting room is full; stop sending people into the hallway.&#8221;</em></p></blockquote><p style="text-align: justify;">Queues are useful when they are <em>bounded</em>. They absorb short bursts and smooth jitter. But they need to have a maximum, otherwise they become a slow-motion memory leak.</p><p style="text-align: justify;"><strong>Concurrency limits at dependency boundaries</strong> </p><blockquote><p><em>&#8220;Only N requests may call this dependency at once.&#8221;</em></p></blockquote><p style="text-align: justify;">This is a strong pattern because it protects the fragile parts: DB pools, third-party APIs, expensive computations. It prevents one slow dependency from consuming the entire service&#8217;s attention.</p><h2>The forms a &#8220;slow down&#8221; signal takes</h2><p style="text-align: justify;">Once you&#8217;ve decided <em>where</em> to enforce a limit, there&#8217;s still the question of <em>how</em> the &#8220;<em>slow down</em>&#8221; is expressed. A few forms recur across real designs.</p><p style="text-align: justify;">A <strong>bounded queue with blocking</strong> makes producers wait when the queue is full. It&#8217;s straightforward and effective inside a single process, where blocking is cheap and well understood; it turns overload into waiting rather than memory growth.</p><p style="text-align: justify;">A <strong>bounded queue with rejection</strong> hands producers an immediate &#8220;<em>try later</em>&#8221;. It can feel harsh, but it has a virtue: it&#8217;s honest. It stops the system from accepting promises it can&#8217;t keep.</p><p style="text-align: justify;"><strong>Pull-based consumption</strong> flips the default: consumers request work only when they&#8217;re ready, instead of having work pushed at them. In push systems you have to add backpressure; in pull systems it&#8217;s more natural, because the consumer controls the rate.</p><p style="text-align: justify;"><strong>Credits or tokens</strong> let a producer send a fixed number of items and send more only when credits are returned. It&#8217;s a clear accounting system for in-flight work - like giving customers numbered tickets, where only ticket holders may enter.</p><p style="text-align: right;">And <strong>windowing</strong> keeps only a limited number of in-flight items at once. It shows up in networking and streaming, anywhere &#8220;<em>too much concurrency</em>&#8221; is the real problem: the window caps parallelism even when the producer could send more.</p><div class="callout-block" data-callout="true"><p>All of these share one idea: <strong>make the rate of production reflect the reality of consumption.</strong></p></div><h2>The flow in one picture</h2><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6yfF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801cb2a9-5a4c-4ff7-9bbf-a4749a8adee4_1179x89.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6yfF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801cb2a9-5a4c-4ff7-9bbf-a4749a8adee4_1179x89.png 424w, https://substackcdn.com/image/fetch/$s_!6yfF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801cb2a9-5a4c-4ff7-9bbf-a4749a8adee4_1179x89.png 848w, https://substackcdn.com/image/fetch/$s_!6yfF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801cb2a9-5a4c-4ff7-9bbf-a4749a8adee4_1179x89.png 1272w, https://substackcdn.com/image/fetch/$s_!6yfF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801cb2a9-5a4c-4ff7-9bbf-a4749a8adee4_1179x89.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6yfF!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801cb2a9-5a4c-4ff7-9bbf-a4749a8adee4_1179x89.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/801cb2a9-5a4c-4ff7-9bbf-a4749a8adee4_1179x89.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:89,&quot;width&quot;:1179,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:17758,&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://iam.slys.dev/i/194774545?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801cb2a9-5a4c-4ff7-9bbf-a4749a8adee4_1179x89.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6yfF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801cb2a9-5a4c-4ff7-9bbf-a4749a8adee4_1179x89.png 424w, https://substackcdn.com/image/fetch/$s_!6yfF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801cb2a9-5a4c-4ff7-9bbf-a4749a8adee4_1179x89.png 848w, https://substackcdn.com/image/fetch/$s_!6yfF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801cb2a9-5a4c-4ff7-9bbf-a4749a8adee4_1179x89.png 1272w, https://substackcdn.com/image/fetch/$s_!6yfF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801cb2a9-5a4c-4ff7-9bbf-a4749a8adee4_1179x89.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>This diagram is simple, but the behavior it creates is profound:</p><div class="pullquote"><p>Instead of letting pressure silently pile up, you force it to become visible <em>as early as possible</em>, where it can be managed.</p></div><h1>What saying no costs you</h1><h2>What backpressure improves</h2><p style="text-align: justify;"><strong>Prevents cascading failure.</strong> Cascades happen when a slowdown in one place creates overload elsewhere.</p><p style="text-align: justify;">Without backpressure, an upstream service continues to accept work it can&#8217;t finish. It accumulates in-flight requests, saturates pools, increases latency, triggers retries, and now <em>multiple</em> services become unhealthy.</p><p style="text-align: justify;">Backpressure breaks that chain. It says: &#8220;<em>This damage stops here</em>&#8221;.</p><p style="text-align: justify;"><strong>Protects critical resources.</strong> Some resources are &#8220;<em>hard to recover</em>&#8221; once exhausted:</p><ul><li><p style="text-align: justify;">thread pools that get stuck waiting</p></li><li><p style="text-align: justify;">memory growth that triggers GC storms or OOM kills</p></li><li><p style="text-align: justify;">connection pools that deadlock under contention</p></li><li><p style="text-align: justify;">file descriptor exhaustion</p></li><li><p style="text-align: justify;">queues so large they can&#8217;t drain in reasonable time</p></li></ul><p style="text-align: justify;">Backpressure keeps these resources from being consumed by work that has no chance of completing promptly.</p><p style="text-align: justify;"><strong>Improves tail behavior.</strong> Users don&#8217;t experience your averages. They experience your tails.</p><p style="text-align: justify;">Backpressure tends to reduce &#8220;<em>infinite wait</em>&#8221; behavior. It draws sharper lines:</p><ul><li><p style="text-align: justify;">either you get a result quickly</p></li><li><p style="text-align: justify;">or you fail quickly and try later</p></li></ul><p style="text-align: justify;">This is emotionally painful (nobody likes errors), but operationally healthy. A system that fails fast under overload is often <em>more</em> trustworthy than a system that randomly hangs.</p><h2>What it makes worse</h2><p style="text-align: justify;"><strong>Some requests fail sooner</strong>. This is the whole point, and it&#8217;s worth stating plainly.</p><p style="text-align: justify;">You are replacing &#8220;<em>hang forever</em>&#8221; with &#8220;<em>try again</em>&#8221;.</p><p style="text-align: justify;">That means you&#8217;ll see more errors during spikes. But those errors are controlled and often temporary. The alternative is a slow death spiral where <em>everyone</em> gets a bad experience.</p><p style="text-align: justify;"><strong>Perceived availability may drop during spikes.</strong> If you measure availability as &#8220;<em>percentage of successful requests</em>&#8221;, backpressure can look like a regression during overload.</p><p style="text-align: justify;">But if you measure availability as &#8220;<em>can real users complete critical actions</em>&#8221;, backpressure usually improves outcomes.</p><p style="text-align: justify;">A checkout system that rejects 20% quickly but lets 80% succeed reliably is often better than one that accepts 100% and lets 60% time out after a minute.</p><h2>New risks it introduces</h2><p style="text-align: justify;"><strong>Over-throttling.</strong> If your thresholds are too conservative, you leave capacity unused and create errors you didn&#8217;t need.</p><p style="text-align: justify;">This can happen when:</p><ul><li><p style="text-align: justify;">limits are static but traffic patterns change</p></li><li><p style="text-align: justify;">you don&#8217;t differentiate endpoints (cheap vs expensive)</p></li><li><p style="text-align: justify;">your signals lag reality (you throttle based on old data)</p></li></ul><p style="text-align: justify;"><strong>Unfairness.</strong> One tenant can crowd out others unless you isolate budgets.</p><p style="text-align: justify;">If backpressure is global, the loudest customer wins. That&#8217;s the &#8220;<em>noisy neighbor</em>&#8221; problem. And it becomes a business problem fast.</p><p style="text-align: justify;"><strong>Oscillation / jitter.</strong> Feedback loops can overcorrect:</p><ul><li><p style="text-align: justify;">throttle too hard &#8594; pressure drops &#8594; remove throttle &#8594; pressure spikes &#8594; throttle again</p></li></ul><p style="text-align: justify;">This produces a sawtooth pattern: alternating between &#8220;<em>too strict</em>&#8221; and &#8220;<em>too loose</em>&#8221;. Users experience it as randomness.</p><h2>The trade-offs you can't escape</h2><p style="text-align: justify;">These trade-offs show up in postmortems again and again. Naming them early helps you make peace with them.</p><ul><li><p style="text-align: justify;"><strong>Availability vs correctness:</strong> rejecting work preserves system health but drops/defers operations.</p></li><li><p style="text-align: justify;"><strong>Latency vs consistency:</strong> quick failures can surface temporary inconsistency to users (e.g., &#8220;<em>order status unknown, try again</em>&#8221;).</p></li><li><p style="text-align: justify;"><strong>Simplicity vs scalability:</strong> &#8220;<em>accept everything</em>&#8221; is simple; &#8220;<em>accept safely</em>&#8221; scales.</p></li><li><p style="text-align: justify;"><strong>Throughput vs tail latency:</strong> chasing throughput with unbounded queues destroys p99 and recovery time.</p></li></ul><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/SYfbI/2/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/de56bbc7-b6ce-4dc5-a004-821220234afc_1220x728.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/300cf917-180c-4a2b-8879-4fabb358adec_1220x798.png&quot;,&quot;height&quot;:400,&quot;title&quot;:&quot;The trade-offs backpressure forces you to make on purpose.&quot;,&quot;description&quot;:&quot;&quot;,&quot;belowTheFold&quot;:true}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/SYfbI/2/" width="730" height="400" frameborder="0" scrolling="no" loading="lazy"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p style="text-align: justify;">Backpressure is a decision to be disciplined about limits.</p><div class="callout-block" data-callout="true"><p style="text-align: justify;">It&#8217;s less &#8220;<em>can we handle it?</em>&#8221; and more &#8220;<em>what do we do when we can&#8217;t?</em>&#8221;</p></div><h1>Backpressure you already have</h1><p style="text-align: justify;">Backpressure is one of those concepts that sounds abstract until you recognize it hiding in plain sight. Many production systems already have backpressure - sometimes accidentally.</p><h2>APIs</h2><ul><li><p style="text-align: justify;"><strong>rate limits, concurrency caps, &#8220;</strong><em><strong>busy/try later</strong></em><strong>&#8221; responses</strong></p></li></ul><p style="text-align: justify;">These are explicit backpressure. The system is telling clients: &#8220;<em>I can&#8217;t take this right now</em>&#8221;.</p><ul><li><p style="text-align: justify;"><strong>prioritization (checkout &gt; recommendations)</strong></p></li></ul><p style="text-align: justify;">Under overload, it&#8217;s common to keep the core path alive by shedding optional work. If recommendations fail, users are mildly annoyed. If checkout fails, you lose revenue and trust.</p><p style="text-align: justify;">This is backpressure as <em>product policy</em>.</p><h2>Microservices</h2><ul><li><p style="text-align: justify;"><strong>per-dependency in-flight limits</strong></p></li></ul><p style="text-align: justify;">A service might be healthy overall, but one downstream is slow. If you don&#8217;t cap calls to that downstream, it can consume all threads/tasks and make the whole service look dead.</p><p style="text-align: justify;">Per-dependency limits say: &#8220;<em>This dependency gets at most N concurrent requests. The rest of the service can still function</em>&#8221;.</p><ul><li><p style="text-align: justify;"><strong>budgets per tenant/customer (noisy neighbor control)</strong></p></li></ul><p style="text-align: justify;">A single customer running a batch job shouldn&#8217;t melt the experience for everyone else. You enforce fairness by giving each tenant a slice of capacity.</p><p style="text-align: justify;">This is backpressure as <em>multi-tenant governance</em>.</p><h2>Queues / pipelines</h2><ul><li><p style="text-align: justify;"><strong>bounded buffers between stages</strong></p></li></ul><p style="text-align: justify;">Pipelines are classic backpressure terrain: producers and consumers run at different speeds. Bounded buffers allow short bursts, but force coordination when the mismatch becomes sustained.</p><ul><li><p style="text-align: justify;"><strong>producers slow down when consumer lag grows</strong></p></li></ul><p style="text-align: justify;">This is the ideal: lag is a signal, not something to hide. If consumer lag grows, producers should slow or drop lower-priority messages.</p><p style="text-align: justify;">This is backpressure as <em>pipeline stability</em>.</p><h2>Databases</h2><ul><li><p style="text-align: justify;"><strong>connection pools as &#8220;</strong><em><strong>natural</strong></em><strong>&#8221; backpressure</strong></p></li></ul><p style="text-align: justify;">A connection pool is a limit: once exhausted, callers must wait or fail.</p><p style="text-align: justify;">That&#8217;s backpressure - whether you intended it or not.</p><p style="text-align: justify;">The danger is when you rely on it as your <em>only</em> backpressure. If every request blocks waiting for a DB connection, you can exhaust threads and memory upstream.</p><ul><li><p style="text-align: justify;"><strong>query concurrency limits to avoid lock thrash</strong></p></li></ul><p style="text-align: justify;">Databases often degrade under too much concurrency. Limiting concurrency can increase throughput by reducing contention.</p><p style="text-align: justify;">This is backpressure as <em>contention management</em>.</p><h2>Caches</h2><ul><li><p style="text-align: justify;"><strong>limiting concurrent cache-miss refills to protect the origin</strong></p></li></ul><p style="text-align: justify;">If a hot key expires and thousands of requests miss at once, they can stampede the origin (DB or service). A cache that limits concurrent refills is applying backpressure to the refill path.</p><ul><li><p style="text-align: justify;"><strong>coalescing duplicate requests so 1 refill serves many waiters</strong></p></li></ul><p style="text-align: justify;">Instead of letting 1,000 identical misses trigger 1,000 origin calls, you let one request do the work and others wait for the result.</p><p style="text-align: justify;">This is backpressure as <em>anti-stampede discipline</em>.</p><h1>Unbounded queues and retry storms</h1><p style="text-align: justify;">Backpressure done well makes incidents smaller. Backpressure done poorly can make systems feel unpredictable - or worse, can hide problems until they&#8217;re catastrophic.</p><h2>Typical mistakes</h2><p style="text-align: justify;"><strong>Unbounded queues.</strong> &#8220;<em>We never drop</em>&#8221; sounds noble.</p><p style="text-align: justify;">In practice it becomes:</p><ul><li><p style="text-align: justify;">memory growth</p></li><li><p style="text-align: justify;">long GC pauses</p></li><li><p style="text-align: justify;">timeouts everywhere</p></li><li><p style="text-align: justify;">crash loops</p></li><li><p style="text-align: justify;">a backlog so large that even after traffic drops, you&#8217;re still in incident mode</p></li></ul><p style="text-align: justify;">Unbounded queues are like letting cars pile onto a bridge during a traffic jam. <strong>You&#8217;re not solving congestion. You&#8217;re moving it somewhere harder to manage.</strong></p><p style="text-align: justify;"><strong>Retry storms.</strong> Retries without budgets/backoff amplify load.</p><p style="text-align: justify;">A common failure pattern:</p><ol><li><p>downstream slows</p></li><li><p>upstream times out</p></li><li><p>upstream retries immediately</p></li><li><p>downstream gets more work, slows more</p></li><li><p>timeouts increase</p></li><li><p>retry volume explodes</p></li></ol><p style="text-align: justify;">The tragedy is that every component thinks it&#8217;s being resilient. Collectively, they&#8217;re making the bottleneck drown.</p><p style="text-align: justify;"><strong>Backpressure only at the edge.</strong> You add a rate limit at the API gateway and call it a day.</p><p style="text-align: justify;">But internal services still overload each other. You&#8217;ve just moved the chaos inward. Some internal service becomes the new &#8220;<em>queue</em>&#8221;, and it may be far less equipped to handle it.</p><p style="text-align: justify;"><strong>Good backpressure is usually layered: edge + service boundaries + dependency boundaries.</strong></p><p style="text-align: justify;"><strong>No prioritization.</strong> If low-value work competes equally with critical work, overload becomes morally wrong.</p><p>Examples:</p><ul><li><p style="text-align: justify;">sending emails competes with checkout</p></li><li><p style="text-align: justify;">analytics events compete with inventory reservations</p></li><li><p style="text-align: justify;">recommendations compete with payment authorization</p></li></ul><p style="text-align: justify;">Under pressure, systems should have opinions about what matters.</p><p style="text-align: justify;"><strong>Late signals.</strong> Throttling triggers after exhaustion, when recovery is hardest.</p><p style="text-align: justify;">If you only start rejecting once thread pools are full and the DB pool is deadlocked, you&#8217;re already in the hole.</p><p style="text-align: justify;"><strong>Backpressure works best when it triggers while you still have enough headroom to serve </strong><em><strong>some</strong></em><strong> traffic reliably.</strong></p><h2>User/operator symptoms</h2><p style="text-align: justify;">These are the patterns that show up in alerts and support tickets:</p><ul><li><p style="text-align: justify;">rising p99 latency + &#8220;<em>random</em>&#8221; timeouts</p></li><li><p style="text-align: justify;">&#8220;<em>it got worse when we scaled up</em>&#8221;</p></li><li><p style="text-align: justify;">queue depth rises and never returns</p></li><li><p style="text-align: justify;">long recovery: incident lasts 10 minutes, draining takes 2 hours</p></li></ul><p style="text-align: justify;">That last one is worth lingering on.</p><p style="text-align: justify;">The spike might be brief. But your backlog is a time debt.</p><p style="text-align: justify;">If you let the system accumulate too much debt, you&#8217;ll pay it back slowly, painfully, and while users are still watching.</p><h1>A reliable system knows its limits</h1><p style="text-align: justify;">A reliable system is not one that <strong>tries to do everything</strong>; it&#8217;s one that <strong>knows its limits</strong>.</p><p style="text-align: justify;">That&#8217;s not defeatism. It&#8217;s engineering maturity.</p><p style="text-align: justify;">Backpressure is &#8220;<em>coordination without central control</em>&#8221;:</p><ul><li><p style="text-align: justify;">you can&#8217;t perfectly synchronize components</p></li><li><p style="text-align: justify;">you can&#8217;t make networks reliable</p></li><li><p style="text-align: justify;">you can&#8217;t make slow dependencies instantly reveal themselves</p></li><li><p style="text-align: justify;">you can&#8217;t avoid bursts and surprises</p></li></ul><p style="text-align: justify;">So instead, you build feedback.</p><p style="text-align: justify;">You let pressure become a signal. You let constraints propagate upstream. You force the system to behave like a set of cooperating parts, not a set of independent optimists.</p><p style="text-align: justify;">There&#8217;s also an organizational analogy that feels almost too real:</p><p style="text-align: justify;">Teams that say &#8220;<em>yes</em>&#8221; to everything become unpredictable. Deadlines slip, quality drops, and priorities blur. The team looks &#8220;<em>available</em>&#8221;, but delivery becomes random.</p><p style="text-align: justify;">Teams with clear intake limits - explicit WIP limits, explicit prioritization- feel less accommodating in the short term.</p><p style="text-align: justify;">But they become trustworthy.</p><p style="text-align: justify;">Backpressure is that kind of trustworthiness, expressed in infrastructure.</p><div><hr></div><p style="text-align: justify;"><strong>&#128236; This is System Design Fundamentals &#8212; one primitive at a time, no hype. Subscribe and the next one lands in your inbox.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>Boundedness plus feedback</h1><p style="text-align: justify;">Backpressure aligns incoming work with the capacity that actually exists to process it. Without that alignment, overload doesn&#8217;t announce itself - it accumulates as growing queues, exhausted pools, and cascades that spread from one slow dependency to everything upstream. Buffers feel like the answer, but they aren&#8217;t free: a large queue trades immediate, honest failure for delayed and amplified failure, and the retries and extra concurrency that pile on during a slowdown usually make the bottleneck worse, not better.</p><p style="text-align: justify;">What good backpressure buys you is controlled degradation instead of collapse. By throttling, rejecting, shedding, and prioritizing, the system trades a chaotic meltdown for a predictable one - and that trade has a real cost, because it means fewer successes now to prevent total collapse later. The lesson underneath all of it is that stability comes from <strong>boundedness plus feedback</strong>, not optimism.</p><p style="text-align: justify;">Backpressure isn&#8217;t about being harsh. It&#8217;s about being honest.</p><p style="text-align: justify;">Systems that survive load aren&#8217;t the ones that accept everything. They&#8217;re the ones that can say, clearly and early:</p><blockquote><p>&#8220;<em>Not right now. Try again soon.</em>&#8221;</p></blockquote><div><hr></div><p style="text-align: justify;"><strong>&#128172; What&#8217;s the worst self-inflicted overload you&#8217;ve seen &#8212; retry storm, unbounded queue, missing limit? Tell me what finally stabilized it.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/backpressure-when-saying-slow-down/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/backpressure-when-saying-slow-down/comments"><span>Leave a comment</span></a></p><p></p><div><hr></div><h1>References</h1><ol><li><p style="text-align: justify;"><a href="https://www.reactive-streams.org/">Reactive Streams Specification</a> | Reactive Streams</p></li><li><p style="text-align: justify;"><a href="https://research.google/pubs/pub40801/">The Tail at Scale</a> <em>by Jeffrey Dean &amp; Luiz Andr&#233; Barroso</em> | Communications of the ACM</p></li><li><p style="text-align: justify;"><a href="https://aws.amazon.com/blogs/architecture/">AWS Architecture Blog</a> | AWS</p></li><li><p style="text-align: justify;"><a href="https://cloud.google.com/architecture/framework/reliability">Google Cloud Architecture Framework: Reliability</a> | Google Cloud</p></li><li><p style="text-align: justify;"><a href="https://sre.google/sre-book/table-of-contents/">Site Reliability Engineering</a> <em>by Betsy Beyer et al.</em> | Google (free online)</p></li><li><p style="text-align: justify;"><a href="https://dataintensive.net/">Designing Data-Intensive Applications</a> <em>by Martin Kleppmann</em> | O'Reilly</p></li><li><p style="text-align: justify;"><a href="https://pragprog.com/titles/mnee2/release-it-second-edition/">Release It!</a> <em>by Michael Nygard</em> | Pragmatic Bookshelf</p></li><li><p style="text-align: justify;"><a href="https://the-cloud-book.com/">The Practice of Cloud System Administration</a> <em>by Thomas Limoncelli, Strata Chalup &amp; Christina Hogan</em> | Addison-Wesley</p></li></ol>]]></content:encoded></item><item><title><![CDATA[Rate Limiting: how Redis, Cloudflare, Stripe, and Envoy do it]]></title><description><![CDATA[Token buckets and sliding windows are the easy part. The hard part is coordination.]]></description><link>https://iam.slys.dev/p/rate-limiting-how-redis-cloudflare</link><guid isPermaLink="false">https://iam.slys.dev/p/rate-limiting-how-redis-cloudflare</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 29 Jun 2026 12:57:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!o9g9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3470588-860a-4239-b2da-c7ef231d8e3a_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!o9g9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3470588-860a-4239-b2da-c7ef231d8e3a_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!o9g9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3470588-860a-4239-b2da-c7ef231d8e3a_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!o9g9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3470588-860a-4239-b2da-c7ef231d8e3a_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!o9g9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3470588-860a-4239-b2da-c7ef231d8e3a_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!o9g9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3470588-860a-4239-b2da-c7ef231d8e3a_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!o9g9!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3470588-860a-4239-b2da-c7ef231d8e3a_1672x941.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a3470588-860a-4239-b2da-c7ef231d8e3a_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4e8f282-a7e8-4e2d-9155-0aaff0101a76_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1846226,&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://iam.slys.dev/i/198868357?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4e8f282-a7e8-4e2d-9155-0aaff0101a76_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!o9g9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3470588-860a-4239-b2da-c7ef231d8e3a_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!o9g9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3470588-860a-4239-b2da-c7ef231d8e3a_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!o9g9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3470588-860a-4239-b2da-c7ef231d8e3a_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!o9g9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3470588-860a-4239-b2da-c7ef231d8e3a_1672x941.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><div class="callout-block" data-callout="true"><p style="text-align: justify;">Think about the entrance to a busy parking garage on a Friday evening. There is a gate that lifts, one car at a time. It does not ask whether you deserve a spot. It does not care that you drove farther than the car behind you. It counts. If the garage is full, the gate stays down, and you wait or you leave. The intelligence in that system is not the gate itself - it is the counting mechanism that knows how many cars are inside at any given moment.</p></div><p style="text-align: justify;">Distributed systems face the same problem at a different scale. The gate is a rate limiter, and the mechanism that counts is harder to build than it looks. Stripe's rate limiter enforces multiple simultaneous limit dimensions at once - per-endpoint request rates, per-account totals, and concurrent request caps - all checked on every inbound request. Cloudflare counts traffic at the edge, inside each point of presence, without synchronizing those counts globally across its network of data centers. Both are doing the same conceptual thing as the parking garage gate, and both arrived at surprisingly different architectures to do it correctly.</p><p style="text-align: justify;">The reason the architectures diverge is not the algorithm. Token buckets, sliding windows, leaky buckets - these are undergraduate material. The reason they diverge is the coordination problem. When you have thirty gateway nodes handling traffic, and each one maintains its own counter, and a user sends ten requests that happen to land on ten different nodes, what does "the user has sent ten requests" even mean? It means ten nodes each believe the user has sent one. Without shared state, your limit of ten requests per second becomes effectively three hundred.</p><p style="text-align: justify;">Lyft's open-source <code>envoyproxy/ratelimit</code> service handles more than 2 million requests per second backed by a Redis coordination layer. Redis is the canonical answer to the coordination problem - a single-threaded in-memory store that can execute atomic Lua scripts, maintain sorted sets for timestamp tracking, and expire keys on a schedule. That combination gives you a place to put the count that every gateway node trusts equally. The tricky part is using it correctly, and the gap between "<em>using Redis for rate limiting</em>" and "<em>using Redis correctly for rate limiting</em>" is where most implementations introduce subtle bugs.</p><p style="text-align: justify;">This post works through that gap. The algorithm choice matters, but it is the second question. The first question is how you coordinate counts across nodes without adding meaningful latency to every request. The answer to that question determines everything else: which data structure you use in Redis, how you handle the moment Redis becomes unavailable, and which production systems made which compromises.</p><p style="text-align: justify;">Distributed rate limiting is a canonical system-design interview problem, but the interview answer and the production answer are not the same document. This is the production answer.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!95Ny!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ca63b6d-5127-4f4e-8654-3121f35e003e_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!95Ny!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ca63b6d-5127-4f4e-8654-3121f35e003e_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!95Ny!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ca63b6d-5127-4f4e-8654-3121f35e003e_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!95Ny!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ca63b6d-5127-4f4e-8654-3121f35e003e_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!95Ny!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ca63b6d-5127-4f4e-8654-3121f35e003e_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!95Ny!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ca63b6d-5127-4f4e-8654-3121f35e003e_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1ca63b6d-5127-4f4e-8654-3121f35e003e_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7de7b15b-0405-40fa-a8ce-c786879638ae_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1071655,&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://iam.slys.dev/i/198868357?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7de7b15b-0405-40fa-a8ce-c786879638ae_1672x941.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_!95Ny!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ca63b6d-5127-4f4e-8654-3121f35e003e_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!95Ny!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ca63b6d-5127-4f4e-8654-3121f35e003e_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!95Ny!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ca63b6d-5127-4f4e-8654-3121f35e003e_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!95Ny!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ca63b6d-5127-4f4e-8654-3121f35e003e_1672x941.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><h1>The scope</h1><p style="text-align: justify;">This post covers five algorithms - fixed window counter, sliding window log, sliding window counter, token bucket, and leaky bucket - and then works through the Redis-backed coordination model that makes any of them useful in a distributed setting. The algorithm section focuses on the failure mode of each, not the mechanics alone, because the failure mode is what determines which one you reach for.</p><p style="text-align: justify;">On coordination, the post covers Lua script atomicity, key naming strategy, TTL discipline, clock skew, and the circuit breaker pattern for graceful degradation when Redis becomes unavailable. Four production implementations are examined in enough depth to extract transferable decisions: Stripe&#8217;s multi-tier token bucket, Cloudflare&#8217;s edge-local counting model, Lyft&#8217;s gRPC-backed Redis service, and Grab&#8217;s per-node frequency-capping shards.</p><p style="text-align: justify;">The post scopes Redis in three operational modes. Standalone Redis is the simplest deployment - a single primary, no replication. Redis Sentinel adds automatic failover: a set of Sentinel processes monitor the primary and promote a replica when the primary dies, typically within thirty seconds. Redis Cluster distributes data across multiple primaries using hash slots, providing horizontal scale at the cost of operational complexity. Each represents a different trade-off between simplicity, availability, and throughput, and the right choice depends on your RPS ceiling.</p><p style="text-align: justify;">What this post does not cover: API gateway product comparisons beyond the named anchors, load balancing, DDoS mitigation at the network layer (L3/L4), or authentication and authorization. Rate limiting sits at the application layer and operates on authenticated identity - it assumes you already know who is making the request.</p><p style="text-align: justify;">The boundary between rate limiting and circuit breaking is worth naming precisely, because both appear in the same system-design discussions and share enough vocabulary to cause real confusion. A circuit breaker reacts to downstream failure rates: it opens when the service you are calling starts failing, protecting your system from cascading errors. A rate limiter reacts to upstream request rates: it opens when callers are sending more traffic than you can absorb, protecting your service from overload. Shared vocabulary - "<em>open</em>", "<em>closed</em>", "<em>threshold</em>", "<em>failure count</em>" - does not mean shared mechanism. They are complementary tools with different subjects; a circuit breaker's subject is the service it wraps, a rate limiter's subject is the caller it governs.</p><p style="text-align: justify;">L3/L4 DDoS mitigation is excluded for a more fundamental reason than scope. At the network layer, traffic analysis operates on raw packet counts before the application layer has parsed an HTTP request, extracted a user ID, or verified a session token. There is no concept of "<em>per-user</em>" at that layer - only per-IP or per-connection. A rate limiter enforcing per-account limits requires authenticated identity, which only exists after the application stack has processed the request.</p><h2>Non-Functional Requirements</h2><p style="text-align: justify;">A rate limiter without concrete targets is decorative. The NFRs below are drawn from production baselines, not hypotheticals, and each one connects to a specific failure if it is missed.</p><p style="text-align: justify;"><strong>Latency.</strong> The rate-limit check must add no more than 1 millisecond at p99 to the request path. Redis command latency over a local network is typically in the 200&#8211;500 microsecond range under normal load. The budget allows for one Redis round trip per request and the Lua execution overhead on top. If this target is missed, the rate limiter becomes visible as a latency source in production traces, and teams begin talking about disabling it.</p><p style="text-align: justify;"><strong>Throughput.</strong> The target ceiling is 100,000 requests per second sustained. Lyft's <code>envoyproxy/ratelimit</code> service establishes this as a real-world reference point for single-cluster Redis sizing. Above this threshold, sharding becomes necessary.</p><p style="text-align: justify;"><strong>Accuracy.</strong> The sliding window counter algorithm introduces a bounded approximation error. In practice, measured error is approximately 0.003% - meaning a limit of 1,000 requests per minute may allow at most 1,000.03 requests under adversarial timing. This is acceptable for API rate limiting on most surfaces. The sorted-set sliding log is exact, but its memory scaling makes it impractical at the scale this post targets.</p><p style="text-align: justify;"><strong>Memory.</strong> At 1 million users across three limit tiers (per-second, per-minute, per-hour), the sliding window counter requires approximately 600 MB of Redis memory. That is 1 million users &#215; 3 tiers &#215; 200 bytes per counter. This is the memory budget that eliminates the sorted-set sliding log from consideration at scale: at 1,000 requests per minute per user across 1 million users, the log approach requires approximately 100 GB.</p><p style="text-align: justify;"><strong>Availability.</strong> The rate-limit path must achieve 99.99% availability. Critically, a Redis failure must not propagate to 100% request failure. The rate limiter is infrastructure for a gate - if the gate breaks, you do not want the building to catch fire. The circuit breaker pattern is the mechanism that prevents this coupling, and it requires an explicit policy decision: fail open (allow requests when Redis is unavailable) or fail closed (reject requests).</p><p style="text-align: justify;"><strong>Headers.</strong> Every 429 response must carry the standard rate-limit headers: <code>X-RateLimit-Limit</code>, <code>X-RateLimit-Remaining</code>, <code>X-RateLimit-Reset</code>, and <code>Retry-After</code>. These are not optional courtesies - they are the contract that allows clients to backoff correctly. A client that receives a 429 without a <code>Retry-After</code> header has no choice but to guess at a retry interval, which produces thundering-herd behavior at scale.</p><p style="text-align: justify;"><strong>Scalability.</strong> Distributed sliding-window implementations show throughput plateauing without horizontal sharding strategies. The architecture section covers Redis Cluster as the answer, but the NFR is what motivates the requirement: a single Redis node can handle roughly 100,000 Lua operations per second, and that ceiling is reachable at this scale.</p><p style="text-align: justify;">The trade-off embedded in this NFR set is accuracy versus memory. The sorted-set sliding log is exact, but it scales with requests per user, not users. The sliding window counter scales with users. For most API surfaces, the 0.003% error is irrelevant, and the memory savings are decisive.</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/tDCT1/2/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/797a639b-4f1b-4354-b3c2-ca8bcc853b34_1220x1594.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d1b49621-e080-4201-9c08-46f001e888bf_1220x1664.png&quot;,&quot;height&quot;:516,&quot;title&quot;:&quot;NFR summary for the distributed rate-limiter design&quot;,&quot;description&quot;:&quot;&quot;,&quot;belowTheFold&quot;:true}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/tDCT1/2/" width="730" height="516" frameborder="0" scrolling="no" loading="lazy"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!p42v!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F846be4ce-79f9-4655-a82b-8b0dea6d545d_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!p42v!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F846be4ce-79f9-4655-a82b-8b0dea6d545d_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!p42v!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F846be4ce-79f9-4655-a82b-8b0dea6d545d_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!p42v!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F846be4ce-79f9-4655-a82b-8b0dea6d545d_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!p42v!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F846be4ce-79f9-4655-a82b-8b0dea6d545d_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!p42v!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F846be4ce-79f9-4655-a82b-8b0dea6d545d_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/846be4ce-79f9-4655-a82b-8b0dea6d545d_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8f4c7370-f20c-44ef-af21-50e72d3c0fcb_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1147113,&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://iam.slys.dev/i/198868357?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f4c7370-f20c-44ef-af21-50e72d3c0fcb_1672x941.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_!p42v!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F846be4ce-79f9-4655-a82b-8b0dea6d545d_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!p42v!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F846be4ce-79f9-4655-a82b-8b0dea6d545d_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!p42v!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F846be4ce-79f9-4655-a82b-8b0dea6d545d_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!p42v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F846be4ce-79f9-4655-a82b-8b0dea6d545d_1672x941.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>Back-of-the-envelope numbers</h1><p style="text-align: justify;">Before choosing an algorithm or designing a Redis topology, it helps to know the shape of the problem in numbers. The estimates below assume 1 million registered users, 100,000 requests per second at peak, and three limit tiers per user: per-second, per-minute, and per-hour.</p><h2>Key-space size</h2><p style="text-align: justify;">The key naming pattern for a rate limiter is <code>rl:{user_id}:{endpoint}:{window_start}</code>. With 1 million users, 5 monitored endpoints, and 3 time windows, the key space is 15 million keys.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">1,000,000 users &#215; 5 endpoints &#215; 3 windows = 15,000,000 keys</code></pre></div><p style="text-align: justify;">Not all 15 million keys exist at once. Keys carry a TTL equal to the window duration, so per-second keys expire every second, per-minute keys every minute, and per-hour keys every hour. The live key count at any instant is bounded by how many users are active within the most recent window.</p><h3>Memory footprint per algorithm</h3><p style="text-align: justify;"><strong>Fixed window counter.</strong> One string key per user per window, holding a single integer. Redis string overhead is approximately 56 bytes; the integer itself is 4 bytes. At 1 million users across 3 windows: roughly 180 MB.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:null,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-null">1,000,000 &#215; 3 &#215; 60 bytes = 180 MB</code></pre></div><p style="text-align: justify;"><strong>Sliding window log.</strong> A Redis sorted set where each member is a request timestamp. Memory scales with requests, not users. At 1,000 requests per minute per user across 1 million users, each timestamp entry costs approximately 100 bytes:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:null,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-null">1,000 req/min &#215; 1,000,000 users &#215; 100 bytes = 100 GB</code></pre></div><p style="text-align: justify;">That is not a rounding error. The sorted-set log is exact, and that exactness comes at a memory cost that makes it impractical for public-facing APIs at this user scale. It is viable for internal services with low request rates or very small user populations.</p><p style="text-align: justify;"><strong>Sliding window counter.</strong> Two string keys per user per tier - one for the current window, one for the previous. At approximately 200 bytes per user per tier across 3 tiers:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:null,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-null">1,000,000 &#215; 3 &#215; 200 bytes = 600 MB</code></pre></div><p style="text-align: justify;">This is the recommended default. The memory footprint is manageable, the error rate is negligible in practice, and the implementation is a small Lua script.</p><p style="text-align: justify;"><strong>Token bucket.</strong> A hash with two fields: <code>tokens</code> (remaining capacity) and <code>last_ts</code> (last refill timestamp). Redis hash overhead for two fields is approximately 88 bytes per user:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:null,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-null">1,000,000 &#215; 88 bytes &#8776; 88 MB</code></pre></div><p style="text-align: justify;">Token bucket has the smallest per-user footprint of all five algorithms. It collapses the time dimension entirely - instead of storing when each request arrived, it stores only how many tokens remain and when they were last replenished.</p><p style="text-align: justify;"><strong>Side-by-side summary</strong> at 1 million users, 100,000 RPS peak:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:null,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-null">Algorithm              Memory       Dominant factor
Fixed window counter   ~180 MB      users &#215; tiers
Sliding window log     ~100 GB      users &#215; requests/user
Sliding window counter ~600 MB      users &#215; tiers &#215; 2 keys
Token bucket           ~88 MB       users only
Leaky bucket (list)    ~500 MB      users &#215; queue depth</code></pre></div><p style="text-align: justify;">The three-order-of-magnitude gap between the sliding window log (100 GB) and the token bucket (88 MB) reflects a fundamental trade-off: </p><div class="callout-block" data-callout="true"><p style="text-align: justify;">exact counting requires storing every event; approximate counting requires storing only a summary. </p></div><p style="text-align: justify;">For a limit of 1,000 requests per minute, the 0.003% approximation error of the sliding window counter - at most 0.03 extra requests per user - is the price of a 165&#215; reduction in memory.</p><div><hr></div><p><strong>&#128257; If your team is sizing a rate limiter, the memory math above settles the algorithm debate before it even starts - worth passing on.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/rate-limiting-how-redis-cloudflare?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/rate-limiting-how-redis-cloudflare?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h3>Redis operations per second</h3><p style="text-align: justify;">At 100,000 RPS, each request requires one Lua script execution - one Redis operation per rate-limit check.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:null,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-null">100,000 RPS &#215; 1 Lua EVAL = 100,000 Redis ops/sec</code></pre></div><p style="text-align: justify;">A single Redis node handles approximately 100,000 to 200,000 simple command operations per second, but Lua script execution has higher overhead than a raw GET or INCR. The realistic ceiling for Lua-heavy workloads is closer to 100,000 ops/sec. This means 100,000 RPS is right at the boundary of what a single Redis node can serve without degradation.</p><p style="text-align: justify;">For multi-tier rate limiting - checking per-second, per-minute, and per-hour limits on every request - a naive implementation would require three sequential Lua calls per request, multiplying Redis load by three. The mitigation is pipelining: batch multiple Lua EVAL calls into a single TCP round trip.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3H4f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfebea7d-d1fc-438e-b50b-cb84ab60862e_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3H4f!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfebea7d-d1fc-438e-b50b-cb84ab60862e_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!3H4f!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfebea7d-d1fc-438e-b50b-cb84ab60862e_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!3H4f!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfebea7d-d1fc-438e-b50b-cb84ab60862e_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!3H4f!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfebea7d-d1fc-438e-b50b-cb84ab60862e_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3H4f!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfebea7d-d1fc-438e-b50b-cb84ab60862e_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dfebea7d-d1fc-438e-b50b-cb84ab60862e_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2506aa40-fef7-4749-b4da-9e05eb73ea75_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:873683,&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://iam.slys.dev/i/198868357?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2506aa40-fef7-4749-b4da-9e05eb73ea75_1672x941.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_!3H4f!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfebea7d-d1fc-438e-b50b-cb84ab60862e_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!3H4f!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfebea7d-d1fc-438e-b50b-cb84ab60862e_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!3H4f!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfebea7d-d1fc-438e-b50b-cb84ab60862e_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!3H4f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfebea7d-d1fc-438e-b50b-cb84ab60862e_1672x941.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><hr></div><p><strong>&#9878;&#65039; What if &#8220;</strong><em><strong>just pick an algorithm</strong></em><strong>&#8221; is the wrong first question?</strong></p><p style="text-align: justify;">Everything above is the setup - the problem, the targets, the memory math. The rest of this post is the <strong>production answer</strong>: the coordination architecture, copy-paste Lua scripts, the Redis-down circuit-breaker path, and exactly how Stripe, Cloudflare, Lyft, and Grab each compromised.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div>
      <p>
          <a href="https://iam.slys.dev/p/rate-limiting-how-redis-cloudflare">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[I taught Claude to read my Substack — here's the gateway behind it]]></title><description><![CDATA[Five repos, one contract, and what vibe coding done well actually means]]></description><link>https://iam.slys.dev/p/i-taught-claude-to-read-my-substack</link><guid isPermaLink="false">https://iam.slys.dev/p/i-taught-claude-to-read-my-substack</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 08 Jun 2026 20:57:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!McxL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76a1e2ef-3e52-4ca2-903c-8bb9fc5bde12_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!McxL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76a1e2ef-3e52-4ca2-903c-8bb9fc5bde12_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!McxL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76a1e2ef-3e52-4ca2-903c-8bb9fc5bde12_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!McxL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76a1e2ef-3e52-4ca2-903c-8bb9fc5bde12_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!McxL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76a1e2ef-3e52-4ca2-903c-8bb9fc5bde12_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!McxL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76a1e2ef-3e52-4ca2-903c-8bb9fc5bde12_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!McxL!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76a1e2ef-3e52-4ca2-903c-8bb9fc5bde12_1672x941.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/76a1e2ef-3e52-4ca2-903c-8bb9fc5bde12_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c33f7acd-34bb-4fb8-add1-8e0bb190bc8f_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2134735,&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://iam.slys.dev/i/201023891?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc33f7acd-34bb-4fb8-add1-8e0bb190bc8f_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!McxL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76a1e2ef-3e52-4ca2-903c-8bb9fc5bde12_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!McxL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76a1e2ef-3e52-4ca2-903c-8bb9fc5bde12_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!McxL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76a1e2ef-3e52-4ca2-903c-8bb9fc5bde12_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!McxL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76a1e2ef-3e52-4ca2-903c-8bb9fc5bde12_1672x941.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 style="text-align: justify;">That little exchange is the whole demo. I ask Claude about one of my own Substack notes, and it answers - not by scraping, not by guessing, but by calling a tool I wrote and getting back a typed response. The model is doing what models do; the interesting part is what sits underneath, where my publication is reachable as a real API instead of a screen to read.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!u5bO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe07aff09-7e14-4eed-99d0-df774ad16368_960x603.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!u5bO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe07aff09-7e14-4eed-99d0-df774ad16368_960x603.gif 424w, https://substackcdn.com/image/fetch/$s_!u5bO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe07aff09-7e14-4eed-99d0-df774ad16368_960x603.gif 848w, https://substackcdn.com/image/fetch/$s_!u5bO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe07aff09-7e14-4eed-99d0-df774ad16368_960x603.gif 1272w, https://substackcdn.com/image/fetch/$s_!u5bO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe07aff09-7e14-4eed-99d0-df774ad16368_960x603.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!u5bO!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe07aff09-7e14-4eed-99d0-df774ad16368_960x603.gif" width="1200" height="753.75" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e07aff09-7e14-4eed-99d0-df774ad16368_960x603.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:603,&quot;width&quot;:960,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:635646,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/201023891?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe07aff09-7e14-4eed-99d0-df774ad16368_960x603.gif&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_!u5bO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe07aff09-7e14-4eed-99d0-df774ad16368_960x603.gif 424w, https://substackcdn.com/image/fetch/$s_!u5bO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe07aff09-7e14-4eed-99d0-df774ad16368_960x603.gif 848w, https://substackcdn.com/image/fetch/$s_!u5bO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe07aff09-7e14-4eed-99d0-df774ad16368_960x603.gif 1272w, https://substackcdn.com/image/fetch/$s_!u5bO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe07aff09-7e14-4eed-99d0-df774ad16368_960x603.gif 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 style="text-align: justify;">There are three moving parts in that GIF, and each one has a job. Claude is the agent - the thing that decides which tool to call and what to do with the result. The Model Context Protocol is the transport between Claude and my service: a small, opinionated way for an LLM to talk to tools defined somewhere else. And the service itself is a Python REST gateway that speaks Substack on one side and a clean contract on the other, with an MCP surface bolted on as a sibling transport rather than a translator. The agent surface and the workflow surface - MCP for Claude, REST for everything else, including my n8n nodes - share one service layer in <code>substack-gateway-oss</code>.</p><p style="text-align: justify;">Lately, I pushed the deprecation notices on two repositories I had been quietly maintaining for nearly a year: <code>substack-api</code>, a reverse-engineered TypeScript client, and the original <code>n8n-nodes-substack</code>, the community node that imported it. They were the first two pieces of this story, and they are both done. What replaced them is the gateway you watched Claude talk to.</p><p style="text-align: justify;">This post is not really about Claude. It is about the boring middle layer that made the moment in the GIF cheap. I want to walk through how I got from a TypeScript wrapper that I patched by hand more than a hundred times to a contract-first gateway where REST and MCP fall out of the same service code, and why the order in which I did the work mattered more than any single decision inside it. The agent moment looks easy. It only looks easy because the parts underneath were done in the right order.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QMez!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ed0fd66-6433-488b-ae0d-eeda0e420482_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QMez!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ed0fd66-6433-488b-ae0d-eeda0e420482_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!QMez!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ed0fd66-6433-488b-ae0d-eeda0e420482_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!QMez!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ed0fd66-6433-488b-ae0d-eeda0e420482_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!QMez!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ed0fd66-6433-488b-ae0d-eeda0e420482_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QMez!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ed0fd66-6433-488b-ae0d-eeda0e420482_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5ed0fd66-6433-488b-ae0d-eeda0e420482_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e6a2c7cf-d787-4abd-8b6d-13cb9c78439b_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:864323,&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://iam.slys.dev/i/201023891?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6a2c7cf-d787-4abd-8b6d-13cb9c78439b_1672x941.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_!QMez!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ed0fd66-6433-488b-ae0d-eeda0e420482_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!QMez!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ed0fd66-6433-488b-ae0d-eeda0e420482_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!QMez!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ed0fd66-6433-488b-ae0d-eeda0e420482_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!QMez!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ed0fd66-6433-488b-ae0d-eeda0e420482_1672x941.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><h1>Why I built any of this</h1><p style="text-align: justify;">The honest origin story is small and unromantic. I write on Substack, and I wanted my own workflow around it - the kind of small automations I had been running for years on other platforms. Notes I could draft from a script. A way to restack things from an inbox. The ability to grep my own backlog. None of it was novel. It only mattered because it was mine, and because the friction of doing it by hand was annoying enough to fix.</p><p style="text-align: justify;">The first place I tried to wire this up was n8n. n8n is a workflow tool I already use for other personal automations - webhooks, schedulers, the usual glue. If there had been an official Substack integration, I would have used it, written a few flows, and moved on with my life. There was not. There still is not, in any documented form.</p><p style="text-align: justify;">That is the structural constraint behind everything that follows: Substack does not publish a public API. There is no spec, no terms-of-service-covered endpoint surface, no client library. There is only the network traffic the website and the mobile app make, which a determined reader can study and reproduce - the reader feed, the comment/feed endpoint that publishes notes, the profile endpoints that back the writer dashboards. Anything I wanted to automate had to ride those private endpoints, and any wrapper I wrote would, by definition, be reverse-engineered. There is no clean way around that. There is only choosing how much of the mess to absorb in one place.</p><p style="text-align: justify;">I was not trying to build a SaaS. I was not trying to publish a client for the world. I wanted a way to drive my own publication from workflows I already trusted, and eventually from an agent, without writing the same HTTP-level glue twice. The first version of that was the most obvious move: build a small, typed client in the language n8n nodes are written in, and then wrap it. That worked, in the way first moves usually work - it solved the problem in front of me and quietly created the next one.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Fosz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d428d0e-07ce-460a-bfec-d450059d1c8d_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Fosz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d428d0e-07ce-460a-bfec-d450059d1c8d_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!Fosz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d428d0e-07ce-460a-bfec-d450059d1c8d_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!Fosz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d428d0e-07ce-460a-bfec-d450059d1c8d_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!Fosz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d428d0e-07ce-460a-bfec-d450059d1c8d_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Fosz!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d428d0e-07ce-460a-bfec-d450059d1c8d_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4d428d0e-07ce-460a-bfec-d450059d1c8d_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3a0f1c3b-2324-4470-a8fb-00c8bd8af214_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1494296,&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://iam.slys.dev/i/201023891?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a0f1c3b-2324-4470-a8fb-00c8bd8af214_1672x941.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_!Fosz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d428d0e-07ce-460a-bfec-d450059d1c8d_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!Fosz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d428d0e-07ce-460a-bfec-d450059d1c8d_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!Fosz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d428d0e-07ce-460a-bfec-d450059d1c8d_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!Fosz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d428d0e-07ce-460a-bfec-d450059d1c8d_1672x941.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><code>substack-api</code>, the reverse-engineered TypeScript client</h1><p style="text-align: justify;">The first repository in this story is <code>substack-api</code>, started in June 2025. I picked TypeScript because the eventual consumer was an n8n community node, and n8n nodes are TypeScript end to end. Sharing a language with the consumer felt like a small win at the time. The code was tidy: a <code>SubstackClient</code> at the root, domain types for <code>OwnProfile</code>, <code>Profile</code>, <code>Note</code>, <code>Post</code>, and <code>Comment</code>, and a fluent <code>NoteBuilder</code> for composing notes. It compiled clean, it had a rollup build, it had jest tests. Inside the package, the discipline was decent.</p><p style="text-align: justify;">The moment you crossed the package boundary in either direction, though, it was a different story. On the way down, every method ended at an undocumented Substack endpoint. The reader feed, the comment/feed endpoint, the profile endpoints - they all have stable enough shapes to model, but none of them have a contract anyone signed. On the way up, callers got typed domain objects, but those objects had been shaped by what the wire format happened to return. There was no layer in the middle insulating the domain from the transport.</p><p style="text-align: justify;">The clearest example was publishing a note. <code>NoteBuilder.publish()</code> looks like a polite method on a fluent builder, but underneath it is doing exactly what you would do at a terminal with <code>curl</code>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">async publish(): Promise&lt;PublishNoteResponse&gt; {
  const rawResponse = await this.substackClient.post&lt;unknown&gt;(
    '/comment/feed/',
    this.toNoteRequest()
  )
  return decodeOrThrow(PublishNoteResponseCodec, rawResponse, 'Publish note response')
}</code></pre></div><p style="text-align: justify;">That <code>POST /comment/feed/</code> is the load-bearing line. The request shape was hand-rolled. The response shape was decoded against a hand-written codec. Both were inferred by watching the website do the same thing in a browser. When Substack changed any of it - a renamed field, a new required header, a tightened validator - the only place to feel that change was inside this method, and the only way to find out was to publish a note and watch it fail.</p><p style="text-align: justify;">Over the lifetime of the client, the repository ended up with 758 total commits, 126 of which I tagged as fixes - about seventeen percent of all commits were patches to keep the wire format honest. That number matches my own recollection of the era: more than a hundred manual fixes, most of them small, almost all of them reactive. It worked. I shipped notes, I read posts, I powered the early version of the n8n node. But the trade had already been made and I had not really seen it yet. By choosing TypeScript I had bought language continuity with the consumer; later I would have to spend that back when I rewrote the gateway in Python. And by reverse-engineering directly into a domain model, I had given the upstream wire format a permanent seat at the table inside my own code.</p><h1><code>n8n-nodes-substack</code>, the old node that imported the client</h1><p style="text-align: justify;">The second repository was the n8n community node, and it shipped on the same day as the client depending on <code>substack-api</code> from commit one. From the outside that pairing looked sensible. Two repositories, one boundary, no duplication. From the inside it was the second half of the same building.</p><p style="text-align: justify;">The node's structure was operation-per-file: <code>Note.operations.ts</code>, <code>Post.operations.ts</code>, and so on. Every one of those files began the same way.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { SubstackClient } from 'substack-api';

async function get(
    executeFunctions: IExecuteFunctions,
    client: SubstackClient,
    publicationAddress: string,
    itemIndex: number,
): Promise&lt;IStandardResponse&gt; {
    try {
        const limitParam = executeFunctions.getNodeParameter('limit', itemIndex, '');
        const limit = OperationUtils.parseLimit(limitParam);
        const ownProfile = await client.ownProfile();
        const notesIterable = await ownProfile.notes();
        const results = await OperationUtils.executeAsyncIterable(
            notesIterable, limit, DataFormatters.formatNote, publicationAddress,
        );
        return { success: true, data: results, metadata: { status: 'success' } };
    } catch (error) {
        return SubstackUtils.formatErrorResponse({ message: error.message, node: executeFunctions.getNode(), itemIndex });
    }
}</code></pre></div><p style="text-align: justify;"><code>import { SubstackClient } from 'substack-api'</code>. There was no adapter, no DTO layer, no internal interface. The node code held a <code>SubstackClient</code> reference, called its methods, and handed the resulting domain objects straight to n8n's data formatters.</p><p style="text-align: justify;">The repository accumulated 456 commits over its life. Much of that churn was not new features. It was the node catching up to the client catching up to Substack. The shared release rhythm I had thought of as a strength turned out to be the thing that made the cost compound: every Substack-side change rippled through <code>substack-api</code>, and every <code>substack-api</code> patch rippled through the node. I had traded the friction of a stable internal seam for speed of delivery, and the bill on that trade came in the form of a steady drip of evenings spent fixing the same shape of problem in two repositories at once.</p><h1>The double-maintenance trap</h1><p style="text-align: justify;">The pain section is the easiest to write because the pain was concrete. By late 2025 I had a pattern: a Substack-side change would slip out, something in my workflows or my own publishing flow would fail quietly, I would track it back to a field rename or a new required header, and then I would spend an hour patching <code>substack-api</code> and another half hour patching the old node so the n8n side picked up the fix. Sometimes the patch broke an unrelated path - a field added for notes shifted a decoder used by posts, or a tightened type narrowed a method the comments code still depended on. The seventeen percent fix-tagged commit rate in <code>substack-api</code> is the receipt for that era. More than a hundred manual fixes, each one small, each one tied to something I could not see from my side.</p><p style="text-align: justify;">The result was that every fix had a blast radius bigger than its cause. A change to one publishing endpoint could move output rows in three n8n nodes that did not touch publishing. A response-shape adjustment in the reader feed could shift comment-side decoders because they happened to share a codec. None of this was anyone's fault - it is what happens when the seam between "<em>what the upstream does</em>" and "<em>what my system does</em>" is not drawn anywhere. The two repositories were not really two layers; they were two halves of one layer pretending to be modular.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t7z6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50b5d31-6c1a-45c3-b7a7-47623b6a4aab_2387x203.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t7z6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50b5d31-6c1a-45c3-b7a7-47623b6a4aab_2387x203.png 424w, https://substackcdn.com/image/fetch/$s_!t7z6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50b5d31-6c1a-45c3-b7a7-47623b6a4aab_2387x203.png 848w, https://substackcdn.com/image/fetch/$s_!t7z6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50b5d31-6c1a-45c3-b7a7-47623b6a4aab_2387x203.png 1272w, https://substackcdn.com/image/fetch/$s_!t7z6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50b5d31-6c1a-45c3-b7a7-47623b6a4aab_2387x203.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t7z6!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50b5d31-6c1a-45c3-b7a7-47623b6a4aab_2387x203.png" width="1200" height="102.1978021978022" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e50b5d31-6c1a-45c3-b7a7-47623b6a4aab_2387x203.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:124,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:72180,&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://iam.slys.dev/i/201023891?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50b5d31-6c1a-45c3-b7a7-47623b6a4aab_2387x203.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_!t7z6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50b5d31-6c1a-45c3-b7a7-47623b6a4aab_2387x203.png 424w, https://substackcdn.com/image/fetch/$s_!t7z6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50b5d31-6c1a-45c3-b7a7-47623b6a4aab_2387x203.png 848w, https://substackcdn.com/image/fetch/$s_!t7z6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50b5d31-6c1a-45c3-b7a7-47623b6a4aab_2387x203.png 1272w, https://substackcdn.com/image/fetch/$s_!t7z6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50b5d31-6c1a-45c3-b7a7-47623b6a4aab_2387x203.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p style="text-align: justify;">If I wanted to stop paying the double-maintenance tax, I had to insert a stable surface between the consumers and Substack - one place that absorbed the upstream churn and presented a shape my workflows and any future agent could rely on. The node could not be the contract, because the node was a consumer. The client could not be the contract, because the client was the part that broke. The contract had to be a new thing in the middle.</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/bxasM/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/09f46f57-51d8-4330-b80b-43eb2b69e807_1220x770.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/61d510a2-77cb-4621-9655-acdca2b7aedb_1220x890.png&quot;,&quot;height&quot;:264,&quot;title&quot;:&quot;Direct-client coupling vs gateway-contract coupling across the dimensions that drove the rewrite&quot;,&quot;description&quot;:&quot;&quot;,&quot;belowTheFold&quot;:true}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/bxasM/1/" width="730" height="264" frameborder="0" scrolling="no" loading="lazy"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p style="text-align: justify;">That insertion is the pivot. Everything below is what happens when you take it seriously instead of treating it as a wrapper exercise.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Cnq9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8f3baf8-f14a-4723-8e84-81ee9d418833_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Cnq9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8f3baf8-f14a-4723-8e84-81ee9d418833_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!Cnq9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8f3baf8-f14a-4723-8e84-81ee9d418833_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!Cnq9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8f3baf8-f14a-4723-8e84-81ee9d418833_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!Cnq9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8f3baf8-f14a-4723-8e84-81ee9d418833_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Cnq9!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8f3baf8-f14a-4723-8e84-81ee9d418833_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8f3baf8-f14a-4723-8e84-81ee9d418833_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e016e4df-64c3-415a-a21d-5369d6efa98a_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1073060,&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://iam.slys.dev/i/201023891?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe016e4df-64c3-415a-a21d-5369d6efa98a_1672x941.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_!Cnq9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8f3baf8-f14a-4723-8e84-81ee9d418833_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!Cnq9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8f3baf8-f14a-4723-8e84-81ee9d418833_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!Cnq9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8f3baf8-f14a-4723-8e84-81ee9d418833_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!Cnq9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8f3baf8-f14a-4723-8e84-81ee9d418833_1672x941.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>The pivot &#8212; vibe coding done well</h1><p style="text-align: justify;">I want to be careful here, because the phrase "<em>vibe coding</em>" has become a marketing word for several different things at once, and most of them are not what I mean. What I mean is narrower and more boring. When the implementation is going to be written by an agent and refined by a human, the order of the work has to invert. Architecture comes first. Contract comes second. Tests come third. Code comes last. The discipline is not in the tool the code gets typed into; the discipline is in deciding what gets typed before any of it is typed at all.</p><p style="text-align: justify;">Architecture first means choosing the boundaries before choosing the libraries. Before I wrote a line of the new gateway, I had decided that there would be one Python service with two transports, that those transports would share a service layer. Once the boundaries are drawn, the implementation has somewhere to land. Once they are not, every line of code makes another small decision about where the seams go, and the seams end up scattered.</p><p style="text-align: justify;">Contract first means defining the shapes the outside world will see before the internals know how to produce them. In the gateway, that meant Pydantic schemas in <code>models/schemas.py</code> doing double duty: they are the request and response types for the REST routes, and they are also the source of truth for the OpenAPI document that FastAPI generates at runtime. There is no standalone <code>openapi.yaml</code> in the repository. I considered writing one, the way the OpenAPI tradition suggests, and I decided against it. A hand-written spec file is a second source of truth that drifts; a generated one is a faithful projection of the code with no extra ceremony. The cost of that trade is that I cannot wave a spec file at a tool that wants one before the service runs. The benefit is that the Pydantic models are the spec, full stop, and there is exactly one place to look when a shape is in doubt.</p><p style="text-align: justify;">Test first, in this project, meant Gherkin. The repository carries more than eighteen <code>.feature</code> files under <code>features/api/</code> and <code>features/mcp/</code>, executed by behave, written in plain <code>Given / When / Then</code> against scenarios I wanted the gateway to honor. They are not unit tests dressed up. They are the executable form of the contract: the gateway is allowed to do these things, in these ways, with these failure modes, and anything else is out of scope until a new scenario says otherwise. BDD has more ceremony per scenario than a plain pytest file, and I traded that ceremony for readable specs that describe behavior in the same words I would use to talk about it.</p><p style="text-align: justify;">The first-hour commits on the gateway tell the same story. Before there was a working endpoint, there was uv, ruff, ty, and CI. The linters and the type checker and the formatter were not retrofits; they were part of the architecture in the same way the directory layout was. The point was not that the tooling is special. The point is that quality gates installed late are negotiable and quality gates installed first are not. By the time any implementation arrived, the build was already failing on anything sloppy, and there was nowhere to hide a quick fix.</p><p style="text-align: justify;">The order matters because of who is writing the code. With architecture, contract, and tests in place, the implementation step is constrained on all sides: the shapes are fixed, the scenarios are written, the directory layout is decided. An agent given that context produces code that fits a slot rather than code that decides what the slot should be. With those things missing, the same agent produces something that compiles, passes whatever tests exist, and quietly invents structure that nobody owns. </p><div class="callout-block" data-callout="true"><p style="text-align: justify;">The thesis is not "<em>let the model write the code</em>". The thesis is <strong>"</strong><em><strong>decide the boundaries, the contract, and the gates yourself, and then it is fine to let the model write the code</strong></em><strong>"</strong>. The agent is allowed to do the typing. The human keeps the decisions about shape.</p></div><p style="text-align: justify;">That order is what made the rewrite below clean. If I had started with implementation and tried to retrofit a contract on top, this post would be a very different story, and probably one that ended with another round of patches on the old client.</p><h1><code>substack-gateway-oss</code>, born REST+MCP on Day 0</h1><p style="text-align: justify;">The stack inside OSS is intentionally narrow: Python 3.10 and up, uv as the package manager, FastAPI for the HTTP surface, FastMCP for the agent surface, Pydantic for the schemas, behave for the scenarios, pytest underneath, ruff and ty as gates, Vercel for hosting. The directory layout falls out of the boundaries the architecture step decided on. There is an <code>api/v1/</code> directory holding the REST routes. There is an <code>mcp/app.py</code> holding the MCP tool registrations. They both depend on a single <code>services/</code> package - the place where Substack-specific logic actually lives - and on <code>models/schemas.py</code>, which defines every shape that crosses any boundary. The transports are siblings, not parent and child. Neither one is allowed to own the domain.</p><div class="callout-block" data-callout="true"><p style="text-align: justify;"><strong>The OSS gateway is live.</strong> It is deployed to Vercel at <a href="https://substack-gateway.vercel.app">substack-gateway.vercel.app</a>, with the REST surface under <a href="https://substack-gateway.vercel.app/api/v1">/api/v1</a>, the auto-generated OpenAPI documentation at <a href="https://substack-gateway.vercel.app/api/docs">/api/docs</a>, and the MCP server at <a href="https://substack-gateway.vercel.app/mcp">/mcp</a>. The OpenAPI page is the spec the Pydantic schemas project; the MCP endpoint is the one Claude talks to in the demo at the top of this post. Everything in the walkthrough below maps to a real route you can curl.</p></div><p style="text-align: justify;">The Notes endpoint is the cleanest place to see the contract walk. The Pydantic-shaped REST route is small enough to read in one pass.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Path
from gateway_oss.api.deps import get_notes_service
from gateway_oss.models.schemas import (CreateNoteRequest, CreateNoteResponse, NoteResponse)
from gateway_oss.services.notes import NotesService

router = APIRouter(tags=["notes"])

@router.get("/notes/{note_id}", response_model=NoteResponse)
async def get_note(note_id: Annotated[int, Path(gt=0)], service: Annotated[NotesService, Depends(get_notes_service)]) -&gt; NoteResponse:
    note = await service.get_note_by_id(note_id)
    return NoteResponse.from_substack(note)

@router.delete("/notes/{note_id}", status_code=204)
async def delete_note(note_id: Annotated[int, Path(gt=0)], service: Annotated[NotesService, Depends(get_notes_service)]) -&gt; None:
    await service.delete_note(note_id)

@router.post("/notes", response_model=CreateNoteResponse, status_code=201)
async def create_note(body: CreateNoteRequest, service: Annotated[NotesService, Depends(get_notes_service)]) -&gt; CreateNoteResponse:
    try:
        note = await service.create_note(body.content, attachment=body.attachment)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    return CreateNoteResponse.from_substack(note)</code></pre></div><p style="text-align: justify;">There are three things worth noticing in that snippet. The handlers ask FastAPI to inject <code>NotesService</code> rather than constructing it themselves, so the route is a thin binding between an HTTP verb and a method call. The request and response types are imported from <code>models/schemas.py</code>; the route does not define shapes, it consumes them. And the error path is a single <code>ValueError &#8594; HTTPException</code> translation - the service raises in its own vocabulary, the route translates once at the boundary, the contract stays clean.</p><p style="text-align: justify;">The Gherkin sitting next to it describes exactly what those routes are allowed to do.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;gherkin&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-gherkin">Feature: Create note endpoint

  Scenario: Successfully create a note from markdown
    Given a valid gateway token "test-token"
    And the Substack create-note endpoint returns the sample response
    When I send POST /api/v1/notes with JSON body {"content": "Hello **world**."}
    Then the response status code is 201
    And the response field "id" is not null

  Scenario: Empty content returns 400
    Given a valid gateway token "test-token"
    When I send POST /api/v1/notes with JSON body {"content": ""}
    Then the response status code is 400

  Scenario: Authentication failure returns 401
    Given a valid gateway token "test-token"
    And the Substack create-note endpoint returns status 401
    When I send POST /api/v1/notes with JSON body {"content": "Hello world."}
    Then the response status code is 401

  Scenario: Missing x-gateway-token header returns 422
    When I send POST /api/v1/notes with JSON body {"content": "Hello world."}
    Then the response status code is 422</code></pre></div><p style="text-align: justify;">Each scenario is one decision about acceptable behavior. The happy path returns 201 with a non-null id. Empty content is a client error, not a 500. An upstream 401 surfaces as a 401, not a generic failure. A missing <code>x-gateway-token</code> header is a 422 from FastAPI's own validation, before the service ever runs. The <code>.feature</code> file is not a description of the implementation; it is the spec the implementation is allowed to honor.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Lx2B!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff12d5601-f078-4ff3-807a-1e0070796456_970x1179.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Lx2B!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff12d5601-f078-4ff3-807a-1e0070796456_970x1179.png 424w, https://substackcdn.com/image/fetch/$s_!Lx2B!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff12d5601-f078-4ff3-807a-1e0070796456_970x1179.png 848w, https://substackcdn.com/image/fetch/$s_!Lx2B!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff12d5601-f078-4ff3-807a-1e0070796456_970x1179.png 1272w, https://substackcdn.com/image/fetch/$s_!Lx2B!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff12d5601-f078-4ff3-807a-1e0070796456_970x1179.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Lx2B!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff12d5601-f078-4ff3-807a-1e0070796456_970x1179.png" width="970" height="1179" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f12d5601-f078-4ff3-807a-1e0070796456_970x1179.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cb87612f-da46-432b-a4fa-712094891906_970x1179.png&quot;,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1179,&quot;width&quot;:970,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:105960,&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://iam.slys.dev/i/201023891?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb87612f-da46-432b-a4fa-712094891906_970x1179.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_!Lx2B!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff12d5601-f078-4ff3-807a-1e0070796456_970x1179.png 424w, https://substackcdn.com/image/fetch/$s_!Lx2B!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff12d5601-f078-4ff3-807a-1e0070796456_970x1179.png 848w, https://substackcdn.com/image/fetch/$s_!Lx2B!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff12d5601-f078-4ff3-807a-1e0070796456_970x1179.png 1272w, https://substackcdn.com/image/fetch/$s_!Lx2B!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff12d5601-f078-4ff3-807a-1e0070796456_970x1179.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 style="text-align: justify;">There are a few decisions visible in this section that are worth naming as trades rather than as facts. Choosing REST and MCP as siblings instead of treating MCP as an adapter over the REST gateway cost me a small amount of duplicated wiring at the transport level - two registration points instead of one. It bought a service layer that does not know which transport is calling it, which means neither transport can quietly corrupt the domain to fit its own ergonomics. Choosing Python over TypeScript broke language continuity with the n8n consumer and gave up the option of sharing a package; it bought FastAPI plus FastMCP plus Pydantic plus Behave, which together land the contract-first story almost for free.</p><div><hr></div><p style="text-align: justify;"><strong>&#9878;&#65039; What if there&#8217;s no such thing as a &#8220;</strong><em><strong>perfect architecture</strong></em><strong>&#8221;?</strong></p><p>Most system design decisions are trade-offs - not best practices. Every decision optimizes for something&#8230; and sacrifices something else.</p><p><strong>&#128172; What&#8217;s a trade-off you struggled with?</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/i-taught-claude-to-read-my-substack/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://iam.slys.dev/p/i-taught-claude-to-read-my-substack/comments"><span>Leave a comment</span></a></p><div><hr></div><p style="text-align: justify;">The next section is the payoff for all of it.</p><h1>MCP fell out of REST nearly for free</h1><p style="text-align: justify;">The reason MCP was cheap to add is that the service layer never knew which transport was calling it. <code>NotesService</code> does not have an HTTP <code>Request</code> object anywhere in its signature. It does not know about headers, status codes, or content types. It knows how to fetch a note, delete a note, and create a note, and it speaks in Pydantic models. That is exactly what an MCP tool needs.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">_mcp = FastMCP("substack-gateway", auth=runtime.mcp_auth_provider)

@_mcp.tool(
    description="Retrieve a single Substack note by its numeric ID.",
    tags={"notes", "read"},
    annotations=ToolAnnotations(
        title="Get Note",
        readOnlyHint=True,
        idempotentHint=True,
    ),
)
async def get_note(note_id: int) -&gt; dict[str, Any]:
    async with (
        _public_publication_client() as publication,
        _public_substack_client() as substack,
    ):
        note = await NotesService(publication, substack).get_note_by_id(note_id)
    return NoteResponse.from_substack(note).model_dump(exclude_none=True)

mcp = _mcp.http_app(transport="streamable-http", path="/", stateless_http=True)</code></pre></div><p style="text-align: justify;">The <code>get_note</code> tool constructs a <code>NotesService</code> with the same dependencies the REST route gets through FastAPI's injection, calls the same <code>get_note_by_id</code> method, and returns the same <code>NoteResponse</code> shape, serialized through <code>model_dump</code> instead of FastAPI's response model machinery. There was no second domain to design. There was no MCP-flavored variant of the Notes service.</p><p style="text-align: justify;">That second decision is worth lingering on. The gateway never holds a Substack session. Credentials are passed per call: base64-encoded JSON containing the publication URL and the relevant cookies, sent as <code>x-gateway-token</code> over REST or as a <code>token</code> argument on the MCP tool call. The trade is ergonomic - the caller has to handle credentials on every request instead of authenticating once and forgetting about it. The benefit is that the service is stateless in the strong sense: there is no server-side session to leak, expire, or rotate; there is no shared in-memory state to invalidate; horizontal scaling is a <code>vercel deploy</code> away. For an agent surface, that property matters more than the convenience of stored credentials, because agents make a lot of small calls and the security story has to hold under each one.</p><p style="text-align: justify;">Looking back at the section title - MCP fell out of REST nearly for free - the word doing real work in that sentence is "<em>nearly</em>". There was work to do. There were tool annotations to write, scenarios to add under <code>features/mcp/</code>, an auth provider to wire in. What there was not was a new domain, a new service layer, or a new shape of <code>Note</code>. That is what the contract-first work bought.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rDgd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f81535f-ebf0-489a-b6fa-0f6e36378404_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rDgd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f81535f-ebf0-489a-b6fa-0f6e36378404_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!rDgd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f81535f-ebf0-489a-b6fa-0f6e36378404_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!rDgd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f81535f-ebf0-489a-b6fa-0f6e36378404_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!rDgd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f81535f-ebf0-489a-b6fa-0f6e36378404_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rDgd!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f81535f-ebf0-489a-b6fa-0f6e36378404_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f81535f-ebf0-489a-b6fa-0f6e36378404_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/137f81cc-e8e8-4ce4-baae-2ec9cdfbc6c9_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:896078,&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://iam.slys.dev/i/201023891?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F137f81cc-e8e8-4ce4-baae-2ec9cdfbc6c9_1672x941.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_!rDgd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f81535f-ebf0-489a-b6fa-0f6e36378404_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!rDgd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f81535f-ebf0-489a-b6fa-0f6e36378404_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!rDgd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f81535f-ebf0-489a-b6fa-0f6e36378404_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!rDgd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f81535f-ebf0-489a-b6fa-0f6e36378404_1672x941.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><code>n8n-nodes-substack-new</code>, talking only the contract</h1><p style="text-align: justify;">The rewritten n8n node started with a commit message I am still fond of: "<em>feat: Substack API v2</em>". It has accumulated 239 commits since then, which sounds like a lot until you remember it is the node and only the node - none of those commits are Substack patches, because the node no longer talks to Substack. It talks to the REST contract. Substack is somebody else's problem now, from this repository's point of view.</p><p style="text-align: justify;">The internal structure is built around Effect and <code>@effect/platform</code>. There are five nodes - Substack Gateway, Following Feed, Profile Feed, Batch Feed, Randomizer - sitting on top of a <code>runtime/</code> pipeline that walks every operation through six typed stages: decode the requested operation, read the n8n input, decode it into a command, build the HTTP request, execute it, decode the response. Each stage's input and output are types the next stage consumes. The pipeline is the schema; the schema is the pipeline.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import * as HttpClient from '@effect/platform/HttpClient';
import * as ClientRequest from '@effect/platform/HttpClientRequest';
import * as ClientResponse from '@effect/platform/HttpClientResponse';
import { Effect, Match, pipe } from 'effect';

const makeRequest = (request: GatewayHttpRequest) =&gt;
    Match.value(request.method).pipe(
        Match.when('GET', () =&gt; ClientRequest.get(request.url)),
        Match.when('POST', () =&gt; ClientRequest.post(request.url)),
        Match.when('PUT', () =&gt; ClientRequest.put(request.url)),
        Match.when('DELETE', () =&gt; ClientRequest.del(request.url)),
        Match.exhaustive,
    );

export const executeGatewayRequest = (request: GatewayHttpRequest) =&gt;
    pipe(
        makeRequest(request),
        Effect.flatMap(HttpClient.execute),
        Effect.mapError(toApiError('Gateway request failed')),
        Effect.flatMap(ClientResponse.filterStatusOk),
        Effect.flatMap((response) =&gt;
            request.responseMode === 'empty'
                ? Effect.succeed(request.emptyResponseBody ?? {})
                : response.json,
        ),
    );</code></pre></div><p style="text-align: justify;">The execute step is small but worth reading. The verb selection is a <code>Match</code> over the method, exhaustive at the type level - if I add a new HTTP verb to the gateway, this match stops compiling until I handle it. The execution itself is a single <code>HttpClient.execute</code> call, with error translation to a domain-specific <code>ApiError</code> and status filtering before any decoding. The decoder branches on whether the request expects an empty response or a JSON body. Crucially, nothing in this file knows anything about Substack. It knows about URLs, methods, status codes, and the gateway's response envelopes. The contract has reached all the way into the node's transport layer, and the node has stopped knowing about anything below it.</p><p style="text-align: justify;">The trade in choosing Effect was the steepest learning curve in this whole arc. It is not a library you pick up over a weekend. The compiler errors are precise and patient, and the runtime gives you typed pipelines, structured errors, and resource safety in exchange for taking its model seriously. I traded onboarding cost for a transport layer that mirrors the gateway's contract instead of leaking Substack's wire format into n8n nodes - and the cost of that trade is paid once, by me, while the benefit shows up every time the gateway adds an endpoint and the node picks it up without learning anything new.</p><h1>What the discipline bought</h1><p style="text-align: justify;">Recently I pushed deprecation notices on <code>substack-api</code> and the old <code>n8n-nodes-substack</code>. The TypeScript client and the node that imported it - the two halves of the original double-maintenance trap - are both retired. The migration was clean. No caller was stranded. Everything I had been driving from the old node now runs through the rewritten one against the gateway, and the gateway's MCP surface gave me a second consumer for free.</p><p style="text-align: justify;">Architecture-first, contract-first, tests-first work is supposed to make migrations cheap, and the only way to know whether it actually did is to do a migration. This one was cheap.</p><p style="text-align: justify;">The agent demo at the top of this post is the other receipt. Yes, Claude is reading my Substack through the gateway, and yes, that is a small wonder. It is the kind of moment that gets a GIF and an opening line. But the wonder is not the model. The wonder is that I did not have to build anything Claude-shaped to make it happen. The MCP surface is the same <code>NotesService</code> the REST routes call, exposed through FastMCP as a sibling transport, gated to read-only, credentialed per call. The model decides which tool to invoke. The boring layer underneath decides what the tools mean. The boring layer is the post.</p><p style="text-align: justify;">There is a PRO sibling repository I have been deliberately quiet about throughout this write-up. <code>substack-gateway-pro</code> extends the OSS gateway with the parts that need real authentication - write paths and personal-data reads exposed as MCP tools, sitting on top of the same service architecture, never storing Substack credentials on the server. The same Notes domain that anchored the walkthrough is the one PRO broadens. That is the entire teaser. I am not committing to a roadmap in this post and I am not publishing a feature matrix; the point of mentioning it is that the OSS/PRO split was a Day-0 architectural decision, and the discipline that paid off on the OSS side is what makes the PRO side tractable at all.</p><p style="text-align: justify;">If there is one sentence I want to leave the reader with, it is this. Vibe coding done well does not mean the model writes the code while the human watches. It means the human owns the architecture, the contract, and the quality gates, and the model fills in the implementation that those three constraints have already shaped. When that order holds, the agent's code stays inside the boundaries the human drew, and a rewrite that touches five repositories can finish on a Tuesday with both originals deprecated by lunch. When the order does not hold, you get the first version of this story - a fluent TypeScript client, a node that imports it directly, a hundred small fixes, and a slow understanding that the seam you wanted was never drawn. The gateway is not the clever part. Having a contract is.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/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">slys.dev is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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><h1>References</h1><ol><li><p><a href="https://modelcontextprotocol.io">Model Context Protocol specification</a> <em>by Anthropic </em>| modelcontextprotocol.io</p></li><li><p><a href="https://github.com/jakub-k-slys/substack-gateway-oss">Substack Gateway OSS</a> <em>by Jakub Slys </em>| GitHub</p></li><li><p><a href="https://github.com/jakub-k-slys/substack-gateway-oss">Substack</a><a href="https://github.com/jakub-k-slys/substack-api"> API</a> <em>by Jakub Slys </em>| GitHub</p></li><li><p><a href="https://github.com/jakub-k-slys/n8n-nodes-substack">Substack Node</a> <em>by Jakub Slys </em>| GitHub</p></li><li><p><a href="https://n8n.io">N8n</a> <em>by Jan Oberhauser </em>| n8n.io</p></li><li><p><a href="https://fastapi.tiangolo.com/">FastAPI</a> <em>by Sebasti&#225;n Ram&#237;rez</em> | fastapi.tiangolo.com</p></li><li><p><a href="https://www.openapis.org">OpenAPI Initiative</a>  <em>by Linux Foundation </em>| openapis.org</p></li><li><p><a href="https://behave.readthedocs.io">Behave</a> <em>by Benno Rice</em> | behave.readthedocs.io</p></li><li><p><a href="https://github.com/jlowin/fastmcp">FastMCP</a> <em>by Jeremiah Lowin </em>| GitHub</p></li><li><p><a href="https://github.com/jakub-k-slys/n8n-nodes-substack-new">New Substack Node</a> <em>by Jakub Slys </em>| GitHub</p></li></ol>]]></content:encoded></item><item><title><![CDATA[Decision Trees — how machines learn by asking questions]]></title><description><![CDATA[AI Literacy]]></description><link>https://iam.slys.dev/p/decision-trees-how-machines-learn</link><guid isPermaLink="false">https://iam.slys.dev/p/decision-trees-how-machines-learn</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Wed, 27 May 2026 20:57:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!wJxm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe636abd0-c8da-418c-b16e-6869df1ac59e_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wJxm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe636abd0-c8da-418c-b16e-6869df1ac59e_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wJxm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe636abd0-c8da-418c-b16e-6869df1ac59e_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!wJxm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe636abd0-c8da-418c-b16e-6869df1ac59e_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!wJxm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe636abd0-c8da-418c-b16e-6869df1ac59e_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!wJxm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe636abd0-c8da-418c-b16e-6869df1ac59e_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wJxm!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe636abd0-c8da-418c-b16e-6869df1ac59e_1672x941.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e636abd0-c8da-418c-b16e-6869df1ac59e_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9b964b0d-0735-4e6b-9d96-e66425821fb2_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1514033,&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://iam.slys.dev/i/187836635?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b964b0d-0735-4e6b-9d96-e66425821fb2_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wJxm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe636abd0-c8da-418c-b16e-6869df1ac59e_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!wJxm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe636abd0-c8da-418c-b16e-6869df1ac59e_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!wJxm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe636abd0-c8da-418c-b16e-6869df1ac59e_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!wJxm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe636abd0-c8da-418c-b16e-6869df1ac59e_1672x941.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 style="text-align: justify;">Decision trees are one of the rare machine-learning models that you can explain without asking anyone to suspend disbelief. You can <em>read</em> a tree like you&#8217;d read a flowchart. You can often defend its behavior in plain language. And you can usually tell, by inspection, why it fails.</p><p style="text-align: justify;">That readability is not an accident. A decision tree is built around a very human idea: if you&#8217;re uncertain, ask a question that reduces uncertainty. Then repeat. A tree is a learned sequence of questions - questions the algorithm invents by looking at data - until the remaining ambiguity is small enough that it&#8217;s willing to commit to an answer.</p><p style="text-align: justify;">In this post I&#8217;m going to treat decision trees less like a &#8220;<em>model family</em>&#8221; and more like a <em>habit of mind</em>: learning as the craft of choosing useful questions. We&#8217;ll start with the intuition (why questions help), then get concrete about how a tree proposes and evaluates candidate questions, what &#8220;<em>splits</em>&#8221; really mean geometrically, why recursion is the natural way to grow a tree, and how stopping and pruning are basically the same human skill: knowing when additional detail stops being insight and starts being noise.</p><div><hr></div><p><strong>&#9889; What if models don&#8217;t &#8220;</strong><em><strong>think</strong></em><strong>&#8221; at all?</strong></p><p style="text-align: justify;">Most machine learning systems are just optimizing - adjusting step by step to reduce error. Not intelligence - adjustment.</p><p><strong>&#128073; Subscribe for more intuitive ML explanations.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p style="text-align: justify;">Along the way, I&#8217;ll introduce the simplest mathematical notion behind &#8220;<em>good questions</em>&#8221;: <strong>impurity</strong> (how mixed a group is), and <strong>information gain</strong> (how much a question reduces impurity). I&#8217;ll do the arithmetic slowly, with a small worked example, because this is one of those places where the formula only becomes memorable after you&#8217;ve computed it by hand once.</p><p style="text-align: justify;">If you keep one mental model throughout, let it be this: a decision tree is a conversation with the data. Each internal node asks a question; each branch is the data&#8217;s answer; each leaf is the model saying, &#8220;<em>Given everything I&#8217;ve asked so far, I&#8217;m going to predict what most similar past examples looked like.</em>&#8221;</p><h2>A game of &#8220;<em>Twenty Questions</em>&#8221;, but with a rulebook</h2><p style="text-align: justify;">Think about the feeling of playing &#8220;<em>Twenty Questions</em>&#8221; with someone who&#8217;s good at it. You start with almost no structure - just a big fog of possibilities. Then come the questions:</p><ul><li><p><em>&#8220;Is it an animal?&#8221;</em></p></li><li><p><em>&#8220;Is it bigger than a breadbox?&#8221;</em></p></li><li><p><em>&#8220;Does it have wheels?&#8221;</em></p></li></ul><p style="text-align: justify;">What makes these questions satisfying is not that they&#8217;re clever in a literary sense, but that they <em>slice the space of possibilities</em> into chunks that are easier to manage. A great question takes a messy set of candidates and splits it into two groups where each group is, in some way, more coherent than what you started with.</p><p style="text-align: justify;">A decision tree is the machine-learning version of that game, except the machine writes the questions for itself. You give it labeled examples - rows of data where you already know the correct answer - and it tries to invent questions that separate the labels as cleanly as possible. Each question is usually yes/no: <em>&#8220;Is feature </em><code>(x)</code><em> greater than </em><code>3.7</code><em>?&#8221; or &#8220;Is color equal to red?&#8221;</em> So the tree feels like a structured interrogation: one crisp question at a time.</p><p style="text-align: justify;">The core promise is simple and surprisingly powerful: if we can turn a messy prediction problem into a sequence of small, clear choices, we can often classify things well. Not because the world is truly made of crisp thresholds, but because <em>thresholds are an efficient way to summarize what you&#8217;ve seen</em>. With enough data, and with the right constraints to keep the tree honest, a tree can approximate complicated decision boundaries using only these simple questions.</p><p style="text-align: justify;">There&#8217;s also a second, quieter promise: interpretability. A tree doesn&#8217;t just output &#8220;<em>approve</em>&#8221; or &#8220;<em>deny</em>.&#8221; It outputs a <em>path</em>: &#8220;<em>income was high, debt was low, history was long&#8230; therefore approve</em>&#8221;. That path can be wrong, but it is at least legible - which is more than you can say for many other models.</p><div><hr></div><p><strong>&#9889; What if models don&#8217;t &#8220;</strong><em><strong>think</strong></em><strong>&#8221; at all?</strong></p><p style="text-align: justify;">Most machine learning systems are just optimizing - adjusting step by step to reduce error. Not intelligence - adjustment. Each step is small, but together they lead to meaningful learning.</p><p><strong>&#128257; Share it if this made something click.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/decision-trees-how-machines-learn?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/decision-trees-how-machines-learn?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h2>Why &#8220;<em>asking questions</em>&#8221; is a useful way to think about learning</h2><p style="text-align: justify;">At a deep level, supervised learning is about reducing uncertainty. You start with a dataset where labels are mixed together. If you pick a random example from the dataset, you&#8217;re not sure what its label will be. Learning is the process of using features to become <em>more sure</em>.</p><p style="text-align: justify;">A &#8220;<em>question</em>&#8221; is useful when it meaningfully reduces that uncertainty. If I ask, &#8220;<em>Is the applicant&#8217;s favorite color blue?</em>&#8221; and the answer has basically no relationship to loan repayment, then the set of possibilities doesn&#8217;t shrink in a helpful way. But if I ask, &#8220;<em>Is their existing debt above a certain level?</em>&#8221; and that tends to separate reliable payers from risky ones, then the two resulting groups become more consistent - more <em>pure</em>.</p><p style="text-align: justify;">This idea of purity is the key intuition behind most tree algorithms. A pure group is one where almost everyone has the same label. If a group is pure, you don&#8217;t need more questions; the decision is nearly obvious. If a group is mixed, you either need a better question or you accept that your model will have error.</p><p style="text-align: justify;">To make that intuition measurable, we use an impurity score. Two common ones are <strong>entropy</strong> and <strong>Gini impurity</strong>. I&#8217;ll focus on entropy first because it matches the &#8220;<em>uncertainty</em>&#8221; story very directly.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cSO1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aad2c3c-365a-4ff7-af71-9b80ee89e74a_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cSO1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aad2c3c-365a-4ff7-af71-9b80ee89e74a_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!cSO1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aad2c3c-365a-4ff7-af71-9b80ee89e74a_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!cSO1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aad2c3c-365a-4ff7-af71-9b80ee89e74a_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!cSO1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aad2c3c-365a-4ff7-af71-9b80ee89e74a_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cSO1!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aad2c3c-365a-4ff7-af71-9b80ee89e74a_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0aad2c3c-365a-4ff7-af71-9b80ee89e74a_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/48441206-6d12-4da2-9c10-3b1729d9476d_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1252723,&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://iam.slys.dev/i/187836635?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48441206-6d12-4da2-9c10-3b1729d9476d_1672x941.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_!cSO1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aad2c3c-365a-4ff7-af71-9b80ee89e74a_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!cSO1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aad2c3c-365a-4ff7-af71-9b80ee89e74a_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!cSO1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aad2c3c-365a-4ff7-af71-9b80ee89e74a_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!cSO1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aad2c3c-365a-4ff7-af71-9b80ee89e74a_1672x941.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>Suppose a node contains a mix of classes, and let <code>(p&#7522;) </code>be the fraction of examples in class <code>(i)</code>. Entropy is defined as:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H = -\\sum_i p_i \\log_2 p_i&quot;,&quot;id&quot;:&quot;XTYVPHIPPZ&quot;}" data-component-name="LatexBlockToDOM"></div><p>Let&#8217;s unpack why this formula matches our intuition, step by step.</p><ol><li><p>We want a score that is <strong>0 when the node is pure</strong>. If one class has probability <code>1</code>, then <code>(p = 1)</code> and <code>(log&#8322;(1) = 0)</code>, so the whole sum becomes 0.</p></li><li><p>We want the score to be <strong>largest when classes are evenly mixed</strong>. For two classes, that happens at <code>(p = 0.5)</code> and <code>(1 - p = 0.5)</code>, which produces a larger value than, say, <code>(p = 0.9)</code> and <code>(0.1)</code>.</p></li><li><p>The <code>(log&#8322;)</code> appears because entropy is measuring uncertainty in &#8220;<em>bits</em>&#8221;: yes/no questions are naturally base-2. You don&#8217;t need to love information theory to use this; you just need to accept that &#8220;<em>how surprised should I be?</em>&#8221; grows like a logarithm.</p></li></ol><p>Gini impurity is a close cousin:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;G = 1 - \\sum_i p_i^2&quot;,&quot;id&quot;:&quot;BPCLRXXWSX&quot;}" data-component-name="LatexBlockToDOM"></div><p>It has the same qualitative behavior (<code>0 </code>when pure, larger when mixed), and in practice entropy vs. Gini rarely changes your life. </p><div class="callout-block" data-callout="true"><p>What matters is the mental model: <em>good questions reduce impurity</em>.</p></div><h2>From a pile of examples to the first question</h2><p style="text-align: justify;">A decision tree starts with a pile of labeled examples. Each row is one example; each column is a feature you can measure; and there&#8217;s a label you want to predict. For a loan toy example, a row might contain (income, debt, credit-history-length) plus a label like &#8220;<em>approved</em>&#8221; or &#8220;<em>denied</em>&#8221; (or perhaps &#8220;<em>repaid</em>&#8221; vs. &#8220;<em>defaulted</em>,&#8221; depending on what you&#8217;re actually trying to predict).</p><p style="text-align: justify;">At the root of the tree, you have every training example mixed together. The algorithm&#8217;s first job is to propose candidate questions and pick the one that best sorts the pile into cleaner buckets.</p><p>Most tree-building algorithms do something like this:</p><ol><li><p>Choose a feature (say, <code>income</code>).</p></li><li><p>Choose a threshold (say, <code>income &#10878; 60k</code>).</p></li><li><p>Split the data into left/right groups based on that question.</p></li><li><p>Score how much &#8220;<em>cleaner</em>&#8221; the groups are compared to the parent.</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_!VW8T!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e44f54c-7cfe-409c-883f-7b6edc7e5fba_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VW8T!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e44f54c-7cfe-409c-883f-7b6edc7e5fba_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!VW8T!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e44f54c-7cfe-409c-883f-7b6edc7e5fba_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!VW8T!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e44f54c-7cfe-409c-883f-7b6edc7e5fba_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!VW8T!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e44f54c-7cfe-409c-883f-7b6edc7e5fba_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VW8T!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e44f54c-7cfe-409c-883f-7b6edc7e5fba_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9e44f54c-7cfe-409c-883f-7b6edc7e5fba_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0c51a862-b40d-4f20-ae33-c0ee8bbff006_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1491333,&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://iam.slys.dev/i/187836635?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c51a862-b40d-4f20-ae33-c0ee8bbff006_1672x941.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_!VW8T!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e44f54c-7cfe-409c-883f-7b6edc7e5fba_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!VW8T!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e44f54c-7cfe-409c-883f-7b6edc7e5fba_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!VW8T!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e44f54c-7cfe-409c-883f-7b6edc7e5fba_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!VW8T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e44f54c-7cfe-409c-883f-7b6edc7e5fba_1672x941.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 style="text-align: justify;">The scoring part is where &#8220;<em>information gain</em>&#8221; comes in. If you use entropy as your impurity measure, then information gain is literally the reduction in entropy:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;IG = H_{par} - H_{kids}&quot;,&quot;id&quot;:&quot;ERFCVMOQYS&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">where (Hkids&#7522;) is the <em>weighted average</em> entropy of the two child nodes:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H_{kids} = w_L H_L + w_R H_R&quot;,&quot;id&quot;:&quot;ODHAINNEKE&quot;}" data-component-name="LatexBlockToDOM"></div><p>and the weights are just proportions of examples:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;w_L = \\frac{n_L}{n}&quot;,&quot;id&quot;:&quot;OPAWNXRZQP&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;w_R = \\frac{n_R}{n}&quot;,&quot;id&quot;:&quot;YXNZWFVDOP&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">That weighted average is important: <em>a split that makes a tiny pure node and leaves a huge messy node is not as impressive as it looks</em>.</p><h3>Worked example: computing information gain by hand</h3><p>Suppose at the root we have 10 examples:</p><ul><li><p>6 are &#8220;<em>approve</em>&#8221;</p></li><li><p>4 are &#8220;<em>deny</em>&#8221;</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_!-Zq7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f267c2a-15bd-493f-b639-ff71619045a8_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-Zq7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f267c2a-15bd-493f-b639-ff71619045a8_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!-Zq7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f267c2a-15bd-493f-b639-ff71619045a8_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!-Zq7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f267c2a-15bd-493f-b639-ff71619045a8_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!-Zq7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f267c2a-15bd-493f-b639-ff71619045a8_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-Zq7!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f267c2a-15bd-493f-b639-ff71619045a8_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f267c2a-15bd-493f-b639-ff71619045a8_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e9ad70c2-fca9-4ec5-a8ac-6e89ec0948f6_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1359981,&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://iam.slys.dev/i/187836635?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9ad70c2-fca9-4ec5-a8ac-6e89ec0948f6_1672x941.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_!-Zq7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f267c2a-15bd-493f-b639-ff71619045a8_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!-Zq7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f267c2a-15bd-493f-b639-ff71619045a8_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!-Zq7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f267c2a-15bd-493f-b639-ff71619045a8_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!-Zq7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f267c2a-15bd-493f-b639-ff71619045a8_1672x941.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>So:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p_{app} = 6/10 = 0.6&quot;,&quot;id&quot;:&quot;QFBXUJZZNQ&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p_{den} = 4/10 = 0.4&quot;,&quot;id&quot;:&quot;HYSNILVRAY&quot;}" data-component-name="LatexBlockToDOM"></div><p>Root entropy:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H_{par} = -0.6\\log_2(0.6) -0.4\\log_2(0.4)&quot;,&quot;id&quot;:&quot;KOJWRLYGCZ&quot;}" data-component-name="LatexBlockToDOM"></div><p>Now compute the pieces:</p><ul><li><p><code>(log&#8322;(0.6) &#8776; -0.737)</code></p></li><li><p><code>(log&#8322;(0.4) &#8776; -1.322)</code></p></li></ul><p>Substitute:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H_{par} &#8776; -0.6(-0.737) -0.4(-1.322)&quot;,&quot;id&quot;:&quot;OZEBQNNFIF&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H_{par} &#8776; 0.442 + 0.529 = 0.971&quot;,&quot;id&quot;:&quot;YZZCMIPXVQ&quot;}" data-component-name="LatexBlockToDOM"></div><p>Now test a candidate split, like &#8220;<code>debt &#10877; 30k</code>&#8221;. Suppose it produces:</p><ul><li><p>Left node: 5 examples (4 approve, 1 deny)</p></li><li><p>Right node: 5 examples (2 approve, 3 deny)</p></li></ul><p>Left entropy:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H_L = -0.8\\log_2(0.8) -0.2\\log_2(0.2)&quot;,&quot;id&quot;:&quot;PJMBGYUCAW&quot;}" data-component-name="LatexBlockToDOM"></div><p>Use:</p><ul><li><p><code>(log&#8322;(0.8) &#8776; -0.322)</code></p></li><li><p><code>(log&#8322;(0.2) &#8776; -2.322)</code></p></li></ul><p>So:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H_L \\approx -0.8(-0.322) -0.2(-2.322)&quot;,&quot;id&quot;:&quot;DTMMMCUFRO&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H_L \\approx 0.258 + 0.464 = 0.722&quot;,&quot;id&quot;:&quot;NTQVGQUHPK&quot;}" data-component-name="LatexBlockToDOM"></div><p>Right entropy (2/5 approve, 3/5 deny):</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H_R = -0.4\\log_2(0.4) -0.6\\log_2(0.6)&quot;,&quot;id&quot;:&quot;TDNHOYTJWL&quot;}" data-component-name="LatexBlockToDOM"></div><p>Notice this is the same numbers as before but swapped, so:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H_R \\approx 0.971&quot;,&quot;id&quot;:&quot;GPCNUNIRFH&quot;}" data-component-name="LatexBlockToDOM"></div><p>Weighted children entropy:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H_{kids} = 0.5(0.722) + 0.5(0.971)&quot;,&quot;id&quot;:&quot;XOXNZKPJQZ&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H_{kids} = 0.361 + 0.486 = 0.847&quot;,&quot;id&quot;:&quot;ZEXZIUMXER&quot;}" data-component-name="LatexBlockToDOM"></div><p>Information gain:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;IG = 0.971 - 0.847 = 0.124&quot;,&quot;id&quot;:&quot;NKODLTJVKS&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">That number, <code>(0.124)</code>, is the &#8220;<em>value</em>&#8221; of this question according to entropy-based information gain. The tree repeats this calculation for many candidate questions and chooses the split with the highest gain.</p><div><hr></div><p style="text-align: center;"><strong>&#9888;&#65039; A strong read for founders and leaders &#9888;&#65039;</strong></p><div class="embedded-publication-wrap" data-attrs="{&quot;id&quot;:3001805,&quot;embedding_publication_id&quot;:null,&quot;name&quot;:&quot;Millennial Masters&quot;,&quot;logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!aDj5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9da0814-a366-4354-8eaa-71fcd3df0c2d_1280x1280.png&quot;,&quot;base_url&quot;:&quot;https://millennialmasters.net&quot;,&quot;hero_text&quot;:&quot;Business, growth, and AI. The newsletter and podcast for founders and leaders.&quot;,&quot;author_name&quot;:&quot;Daniel Ionescu&quot;,&quot;show_subscribe&quot;:true,&quot;logo_bg_color&quot;:&quot;#212124&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="EmbeddedPublicationToDOMWithSubscribe"><div class="embedded-publication show-subscribe"><a class="embedded-publication-link-part" native="true" href="https://millennialmasters.net?utm_source=substack&amp;utm_campaign=publication_embed&amp;utm_medium=web"><img class="embedded-publication-logo" src="https://substackcdn.com/image/fetch/$s_!aDj5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9da0814-a366-4354-8eaa-71fcd3df0c2d_1280x1280.png" width="56" height="56" style="background-color: rgb(33, 33, 36);"><span class="embedded-publication-name">Millennial Masters</span><div class="embedded-publication-hero-text">Business, growth, and AI. The newsletter and podcast for founders and leaders.</div><div class="embedded-publication-author-name">By Daniel Ionescu</div></a><form class="embedded-publication-subscribe" method="GET" action="https://millennialmasters.net/subscribe?"><input type="hidden" name="source" value="publication-embed"><input type="hidden" name="autoSubmit" value="true"><input type="email" class="email-input" name="email" placeholder="Type your email..."><input type="submit" class="button primary" value="Subscribe"></form></div></div><p style="text-align: justify;"><em><strong>Millennial Masters</strong></em> by <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Daniel Ionescu&quot;,&quot;id&quot;:317340,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/88332294-b135-41ea-815d-6cbaef47bb1a_1834x1834.png&quot;,&quot;uuid&quot;:&quot;acf91449-d7ff-4051-bddc-6cbf392c9070&quot;}" data-component-name="MentionToDOM"></span> explores how modern businesses are <em>built</em> and <em>grown</em> &#8212; through founder judgment, AI, growth systems, and the leadership behaviors that shape teams long before strategy does.</p><div><hr></div><h2>What a &#8220;<em>split</em>&#8221; really is: drawing boundaries you can explain out loud</h2><p style="text-align: justify;">Most of the time, a tree&#8217;s questions are not philosophical. They&#8217;re just boundaries.</p><p>For a numeric feature <code>(x)</code>, the question is typically:</p><ul><li><p>&#8220;<em>Is </em><code>(x &#8804; t)</code><em>?</em>&#8221;</p></li></ul><p>For a categorical feature (like &#8220;<em>has a co-signer: yes/no</em>&#8221;), it might be:</p><ul><li><p>&#8220;<em>Is category equal to </em><code>(c)</code>?&#8221;</p></li></ul><p style="text-align: justify;">If you&#8217;ve ever made a quick human rule like &#8220;<em>If the temperature is below 32&#176;F, it might be snow</em>&#8221;, you&#8217;ve already used the basic shape of a decision-tree split. It&#8217;s a threshold rule. The only difference is that the tree <em>learns</em> the threshold <code>(t)</code> from data by checking which threshold produces the cleanest separation of labels.</p><div class="callout-block" data-callout="true"><p>Geometrically, each split is carving the feature space into regions. </p></div><p style="text-align: justify;">In one dimension (one feature), &#8220;<code>(x &#8804; t)</code>&#8221; cuts the line into two intervals. In two dimensions, you get axis-aligned cuts: &#8220;<code>(x&#8321; &#8804; t)</code>&#8221; is a vertical line; &#8220;<code>(x&#8322; &#8804; t)</code>&#8221; is a horizontal line. In higher dimensions, it&#8217;s the same idea: hyperplanes aligned with feature axes.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!45G7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff274b8-590f-4cbe-a891-ebf4ef277e17_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!45G7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff274b8-590f-4cbe-a891-ebf4ef277e17_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!45G7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff274b8-590f-4cbe-a891-ebf4ef277e17_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!45G7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff274b8-590f-4cbe-a891-ebf4ef277e17_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!45G7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff274b8-590f-4cbe-a891-ebf4ef277e17_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!45G7!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff274b8-590f-4cbe-a891-ebf4ef277e17_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4ff274b8-590f-4cbe-a891-ebf4ef277e17_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/032f10b0-f0a6-4211-b141-17384821fae9_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:973741,&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://iam.slys.dev/i/187836635?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F032f10b0-f0a6-4211-b141-17384821fae9_1672x941.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_!45G7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff274b8-590f-4cbe-a891-ebf4ef277e17_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!45G7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff274b8-590f-4cbe-a891-ebf4ef277e17_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!45G7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff274b8-590f-4cbe-a891-ebf4ef277e17_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!45G7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff274b8-590f-4cbe-a891-ebf4ef277e17_1672x941.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 style="text-align: justify;">This is where interpretability comes from. A tree&#8217;s decision is a conjunction of simple statements:</p><ul><li><p><code>(x&#8321; &#8804; t&#8321;)</code></p></li><li><p><code>(x&#8323; &gt; t&#8323;)</code></p></li><li><p>category is in <code>{A, B}</code></p></li></ul><p style="text-align: justify;">You can read them top to bottom and say them out loud. That matters in practice because it makes debugging possible. If a tree is making an embarrassing prediction, you can trace the path and often find the exact question that sent the example down the wrong branch.</p><p style="text-align: justify;">There&#8217;s a trade-off hiding here, though. Axis-aligned splits are easy to explain, but they can be an awkward fit when the &#8220;<em>true</em>&#8221; boundary is diagonal or smooth. Trees can approximate those boundaries, but they often need many small steps - many questions-to do it. So interpretability and expressiveness are always negotiating with each other.</p><h2>Growing the tree one choice at a time</h2><p style="text-align: justify;">Once you&#8217;ve understood the first split, you&#8217;ve understood almost the entire tree-growing process, because the algorithm is fundamentally recursive.</p><p>Here&#8217;s the story the model is telling itself:</p><ol><li><p>At the root, I have a mix of labels.</p></li><li><p>I will ask one question that best reduces the mix.</p></li><li><p>Now I have two smaller piles of examples (left and right).</p></li><li><p>For each pile, I will again ask: &#8220;<em>What question best reduces this pile&#8217;s mix?</em>&#8221;</p></li><li><p>I keep doing this until I decide I&#8217;m done.</p></li></ol><p style="text-align: justify;">The recursion is not just an implementation detail; it&#8217;s the essence of how trees &#8220;<em>think</em>&#8221;. The tree never tries to plan an optimal sequence of questions globally. It doesn&#8217;t search over all possible trees (that would be combinatorially enormous). </p><div class="callout-block" data-callout="true"><p>Instead, it makes a locally greedy choice: pick the best split <em>right now</em>, then repeat inside each branch.</p></div><p style="text-align: justify;">This greediness is both a strength and a weakness. It&#8217;s a strength because it makes training fast and conceptually simple. It&#8217;s a weakness because a locally best question might block a globally better structure. You can easily construct cases where a suboptimal first split enables much better splits later. But in many real datasets, the greedy heuristic is &#8220;<em>good enough</em>&#8221;, especially when combined with regularization strategies like stopping rules and pruning.</p><p style="text-align: justify;">It also helps to notice what changes as you recurse: the candidate questions are the same <em>types</em> of questions, but they are evaluated on different subsets of the data. A threshold that is useless globally might become useful inside a branch. For example, &#8220;<code>income &#8804; 50k</code>&#8221; might not help much at the root, but once you&#8217;ve restricted to applicants with short credit history, income might become decisive.</p><p style="text-align: justify;">So a tree gradually carves the world into smaller regions where simpler rules become valid. That is a very general pattern in ML:</p><div class="callout-block" data-callout="true"><p><em>complex behavior often emerges from repeating a simple operation many times</em>.</p></div><h2>When does the tree stop asking questions?</h2><p style="text-align: justify;">If you let a tree keep asking questions indefinitely, it will eventually do something that looks impressive and is actually dangerous: it will memorize the training set.</p><p style="text-align: justify;">To avoid that, tree-building needs stopping rules - human judgments encoded as hyperparameters. In human terms, you stop asking questions when additional questions stop being &#8220;<em>clarifying</em>&#8221; and start being &#8220;<em>nitpicky</em>&#8221;.</p><p>Common stopping conditions include:</p><ul><li><p><strong>Purity threshold:</strong> If a node is already &#8220;<em>mostly one label</em>&#8221;, you stop. For instance, if 98% of examples in a node are &#8220;<em>approve</em>&#8221;, you might accept that as good enough and not try to chase the remaining 2%.</p></li><li><p><strong>Minimum samples in a node:</strong> If a node contains very few examples, any split you learn there is likely to be unstable. It&#8217;s like building a theory of human behavior from three anecdotes.</p></li><li><p><strong>Maximum depth:</strong> You cap how many questions can be asked from root to leaf. Depth is a rough proxy for complexity.</p></li><li><p><strong>Minimum impurity decrease:</strong> You only split if the impurity reduction (information gain, Gini decrease, etc.) is &#8220;<em>large enough</em>&#8221; to justify the added complexity.</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_!1ysw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af6e056-f480-4106-8b07-232f5c1830b5_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1ysw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af6e056-f480-4106-8b07-232f5c1830b5_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!1ysw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af6e056-f480-4106-8b07-232f5c1830b5_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!1ysw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af6e056-f480-4106-8b07-232f5c1830b5_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!1ysw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af6e056-f480-4106-8b07-232f5c1830b5_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1ysw!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af6e056-f480-4106-8b07-232f5c1830b5_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8af6e056-f480-4106-8b07-232f5c1830b5_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1424583,&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://iam.slys.dev/i/187836635?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af6e056-f480-4106-8b07-232f5c1830b5_1672x941.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_!1ysw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af6e056-f480-4106-8b07-232f5c1830b5_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!1ysw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af6e056-f480-4106-8b07-232f5c1830b5_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!1ysw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af6e056-f480-4106-8b07-232f5c1830b5_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!1ysw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af6e056-f480-4106-8b07-232f5c1830b5_1672x941.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 style="text-align: justify;">These are all different ways of expressing the same bias: prefer simpler explanations unless the data strongly argues for additional detail.</p><p style="text-align: justify;">It&#8217;s worth pausing on why stopping is needed <em>even if</em> your training accuracy keeps improving. Training accuracy is a seductive metric because it always rewards complexity. But our real goal is performance on new data. A question that perfectly separates three training points might be capturing an accident: a quirk of measurement, a temporary market condition, or a label error.</p><div class="callout-block" data-callout="true"><p style="text-align: justify;">So stopping is not a hack. It&#8217;s the core generalization idea: we want the model to capture patterns that persist beyond the dataset, and that means refusing to &#8220;<em>learn</em>&#8221; patterns that are too specific to the sample we happened to collect.</p></div><h2>A concrete example: approving a loan by asking a small set of questions</h2><p style="text-align: justify;">Let&#8217;s build a small, intentionally simplified loan-approval story. The goal here is not to propose a real credit policy; it&#8217;s to make the mechanics of a decision tree feel vivid.</p><p>Assume we train on past applicants with three features:</p><ul><li><p>Annual income (in thousands): <code>(I)</code></p></li><li><p>Existing debt (in thousands): <code>(D)</code></p></li><li><p>Credit history length (years): <code>(Y)</code></p></li></ul><p>Label:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;L \\in {\\text{Approve}, \\text{Deny}}&quot;,&quot;id&quot;:&quot;DDDVMBHWCB&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">A plausible first question might be about debt burden, because it often separates low-risk from high-risk applicants:</p><ol><li><p><strong>Q1:</strong> &#8220;<em>Is </em><code>(D &#8804; 25)</code><em>?</em>&#8221;</p><ul><li><p>If yes, go left (lower debt).</p></li><li><p>If no, go right (higher debt).</p></li></ul></li></ol><p style="text-align: justify;">Inside the low-debt branch, the tree might find that income becomes a clean separator:</p><ol start="2"><li><p><strong>Q2 (left branch):</strong> &#8220;<em>Is </em><code>(I &#10878; 55)</code><em>?</em>&#8221;</p><ul><li><p>If yes, most examples might be approved.</p></li><li><p>If no, you might need one more refinement.</p></li></ul></li></ol><p style="text-align: justify;">Now consider those low-debt but lower-income applicants. Perhaps credit history length distinguishes &#8220;<em>new to credit</em>&#8221; from &#8220;<em>stable track record</em>&#8221;:</p><ol start="3"><li><p><strong>Q3 (left-left branch):</strong> &#8220;<em>Is </em><code>(Y &#10878; 3)</code>?&#8221;</p><ul><li><p>If yes, approve (stable).</p></li><li><p>If no, deny (too little history).</p></li></ul></li></ol><p style="text-align: justify;">On the high-debt side, the tree might quickly decide that most examples are denied, but it could still carve out an exception for very high income:</p><ol start="4"><li><p><strong>Q2 (right branch):</strong> &#8220;<em>Is </em><code>(I &#10878; 90)</code>?&#8221;</p><ul><li><p>If yes, maybe approve (they can service debt).</p></li><li><p>If no, deny.</p></li></ul></li></ol><p style="text-align: justify;">Notice what&#8217;s happening: each question is locally reasonable, and the final decision path is something a human could narrate. But also notice the brittleness: &#8220;<em>25k debt&#8221;</em> and &#8220;<em>55k income</em>&#8221; are crisp thresholds. In reality, risk changes smoothly, and different datasets would likely push those thresholds around. Still, for many operational settings, a readable rule set like this is extremely useful - even if it&#8217;s only an approximation.</p><h2>Leaves, predictions, and what the tree &#8220;<em>believes</em>&#8221; at the end of a path</h2><p>A decision tree&#8217;s terminal nodes are called <strong>leaves</strong>. A leaf is where the tree stops asking questions and produces a prediction.</p><p>A key point that beginners sometimes miss is that a leaf is not &#8220;<em>a logical conclusion</em>&#8221; in some deductive sense. It is a summary of training examples that landed there. If a leaf contains 120 training examples and 90 of them are &#8220;<em>Approve</em>&#8221;, the leaf&#8217;s prediction is usually &#8220;<em>Approve</em>&#8221;, and it may also store the estimated probability:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat p(\\text{Approve}) = 90/120 = 0.75&quot;,&quot;id&quot;:&quot;OWRZWZCUAF&quot;}" data-component-name="LatexBlockToDOM"></div><p>That estimate is not a deep belief about the world. It&#8217;s a frequency statement: <em>among similar examples in the training data, 75% were approved.</em></p><h3>Worked example: leaf probabilities and decisions</h3><p style="text-align: justify;">Suppose a particular leaf contains 8 training applicants:</p><ul><li><p>6 were approved</p></li><li><p>2 were denied</p></li></ul><p>Then:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat p(\\text{Approve}) = 6/8 = 0.75&quot;,&quot;id&quot;:&quot;ENENIPCBLQ&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat p(\\text{Deny}) = 2/8 = 0.25&quot;,&quot;id&quot;:&quot;VKPFEIXEKW&quot;}" data-component-name="LatexBlockToDOM"></div><p style="text-align: justify;">If your tree is doing hard classification, it picks the majority class:</p><ul><li><p>Predict &#8220;<em>Approve</em>&#8221;.</p></li></ul><p style="text-align: justify;">If you need a score (for ranking or thresholding), you can use <code>(0.75)</code> as a probability-like output. But you should also feel the fragility: with only 8 examples, one mislabeled case changes the estimate from <code>(6/8)</code> to <code>(5/8)</code>, i.e., from <code>(0.75)</code> to <code>(0.625)</code>. This is why minimum-samples stopping rules matter, and why ensembles (like random forests) are often used to stabilize these estimates.</p><div class="callout-block" data-callout="true"><p style="text-align: justify;">Also notice something philosophically important: the tree is not &#8220;<em>thinking ahead</em>&#8221;. It doesn&#8217;t say, &#8220;<em>If I ask this now, I&#8217;ll be able to ask a better question later.</em>&#8221; It makes a chain of local choices, and at the end it votes based on what happened to similar past examples.</p></div><h1>The quiet trade-off: clarity versus fragility</h1><p style="text-align: justify;">The reason trees feel so clear is that they commit to a small number of crisp questions. The reason trees can feel so fragile is&#8230; they commit to a small number of crisp questions.</p><p style="text-align: justify;">A lot of the model&#8217;s fate is decided near the root. If the first split changes, then all the downstream splits are learned on different subsets, which means the entire structure can change. Two trees trained on nearly identical datasets can look meaningfully different, not because the algorithm is &#8220;<em>random</em>&#8221;, but because the choice it makes is often a close call.</p><p style="text-align: justify;">Here&#8217;s the intuitive mechanism. Imagine two candidate root questions, A and B, with very similar information gain:</p><ul><li><p>Question A produces slightly cleaner groups than B on your current dataset.</p></li><li><p>You pick A (because that&#8217;s what the algorithm does).</p></li><li><p>Now, inside the left branch of A, you learn a set of follow-up splits specialized to that subset.</p></li><li><p>If you had picked B, you would have created different subsets, and therefore learned different follow-up splits.</p></li></ul><p style="text-align: justify;">So small data perturbations can cascade. This is particularly visible when:</p><ul><li><p>the dataset is small,</p></li><li><p>features are correlated (many questions are &#8220;<em>almost</em>&#8221; equivalent),</p></li><li><p>labels are noisy,</p></li><li><p>the optimal split is not sharply better than alternatives.</p></li></ul><p style="text-align: justify;">This is not a reason to dislike trees; it&#8217;s a reason to understand what you&#8217;re buying. Trees buy you interpretability and the ability to model non-linear, interaction-heavy relationships. In exchange, you accept variance: the learned structure can be sensitive to the sample.</p><p style="text-align: justify;">It&#8217;s also why tree ensembles are so common. A random forest, in one sentence, is &#8220;<em>a way of averaging many fragile trees into a stable predictor</em>&#8221;. But even if you later move to ensembles, it&#8217;s worth learning single trees first, because they teach you the anatomy of the question-asking process.</p><h2>Overfitting looks like an over-curious interviewer</h2><p style="text-align: justify;">Overfitting in decision trees has a particularly relatable flavor. It looks like an interviewer who keeps asking questions - not to understand stable traits, but to memorize one-off details.</p><p style="text-align: justify;">A shallow tree might ask:</p><ul><li><p>&#8220;<em>Is your income above 60k?</em>&#8221;</p></li><li><p>&#8220;<em>Is your debt below 25k?</em>&#8221;</p></li></ul><p style="text-align: justify;">A wildly overfit tree starts asking things that are technically true in the training set but not meaningfully predictive:</p><ul><li><p>&#8220;<em>Is your income above 61k and your history length exactly 2 years?</em>&#8221;</p></li><li><p>&#8220;<em>Did you have a blue mug on Tuesday?</em>&#8221; (the human parody of a spurious feature)</p></li></ul><p style="text-align: justify;">What&#8217;s happening is that deep trees have enough degrees of freedom to carve the feature space into tiny regions. If you keep splitting, you can often create leaves that contain one or two training examples, at which point the leaf can &#8220;<em>predict</em>&#8221; perfectly - because it is basically memorizing.</p><p style="text-align: justify;">In impurity terms, every split can reduce impurity on training data. If you split until each leaf is pure (all one label), training entropy becomes zero at every leaf. But that is not a victory. That is the model declaring: &#8220;<em>I have a special rule for each training case</em>&#8221;, which usually generalizes poorly.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CfAI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71c0f0a9-a04b-46f2-b1f8-9a59c1639b16_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CfAI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71c0f0a9-a04b-46f2-b1f8-9a59c1639b16_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!CfAI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71c0f0a9-a04b-46f2-b1f8-9a59c1639b16_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!CfAI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71c0f0a9-a04b-46f2-b1f8-9a59c1639b16_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!CfAI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71c0f0a9-a04b-46f2-b1f8-9a59c1639b16_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CfAI!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71c0f0a9-a04b-46f2-b1f8-9a59c1639b16_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/71c0f0a9-a04b-46f2-b1f8-9a59c1639b16_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e46ccec4-29ab-4afe-9ca5-587456b3dfd4_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1346008,&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://iam.slys.dev/i/187836635?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe46ccec4-29ab-4afe-9ca5-587456b3dfd4_1672x941.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_!CfAI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71c0f0a9-a04b-46f2-b1f8-9a59c1639b16_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!CfAI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71c0f0a9-a04b-46f2-b1f8-9a59c1639b16_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!CfAI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71c0f0a9-a04b-46f2-b1f8-9a59c1639b16_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!CfAI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71c0f0a9-a04b-46f2-b1f8-9a59c1639b16_1672x941.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 style="text-align: justify;">This connects to a broader ML theme: fitting is easy; generalization is hard. Trees make this painfully concrete because the failure mode is visible. You can literally point to a branch and say, &#8220;<em>This rule exists only to handle three examples</em>&#8221;.</p><p style="text-align: justify;">Deep trees <em>can</em> be appropriate when you truly have complex interactions and lots of data. The problem is that depth is a blunt instrument: it increases both expressive power and the ability to chase noise. So you need explicit controls (stopping and pruning) to keep curiosity from turning into memorization.</p><h2>What pruning is: teaching the tree to shut up at the right time</h2><p style="text-align: justify;">Pruning is the idea that the tree should be allowed to grow (so it can discover useful structure), but then be encouraged to simplify itself by removing branches that don&#8217;t justify their complexity.</p><p>There are two broad styles:</p><ul><li><p><strong>Pre-pruning (early stopping):</strong> you stop splits during growth using rules like max depth or min samples.</p></li><li><p><strong>Post-pruning:</strong> you grow a large tree, then cut it back.</p></li></ul><p style="text-align: justify;">The intuition behind post-pruning is very human: when you first draft an explanation, you may include lots of details; when you edit, you remove the details that don&#8217;t improve the explanation for a new reader. Pruning is editing.</p><p style="text-align: justify;">One common formalization is cost-complexity pruning. You balance training error against tree size:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;J(T) = R(T) + \\alpha |T|&quot;,&quot;id&quot;:&quot;ANGTLXMORR&quot;}" data-component-name="LatexBlockToDOM"></div><p>Let&#8217;s interpret each term carefully.</p><ul><li><p><code>(T)</code> is the tree.</p></li><li><p><code>(R(T))</code> is a measure of how wrong the tree is (often misclassification error on training data, or another impurity-based proxy).</p></li><li><p><code>(|T|)</code> is a measure of complexity (often the number of leaves).</p></li><li><p>(&#120572;) controls how much you penalize complexity.</p></li></ul><p style="text-align: justify;">If <code>(&#120572; = 0)</code>, you don&#8217;t care about complexity and you keep the big tree. If (&#120572;) is large, you strongly prefer a smaller tree even if it makes more mistakes on training data.</p><p style="text-align: justify;">The core message is not the specific objective. It&#8217;s the stance: a simpler tree that generalizes is better than a complicated tree that over-explains the past. Pruning operationalizes that stance by removing branches that look like &#8220;<em>exceptions</em>&#8221; rather than &#8220;<em>patterns</em>&#8221;.</p><h2>Common misunderstandings that trip people up</h2><p style="text-align: justify;">The first misunderstanding is the most common and the most costly: believing that a decision tree has discovered the <em>true causal rules</em> of the world. A tree discovers a set of splits that are useful for prediction on the data you gave it, under the splitting criterion you chose. That can align with causal structure, but it does not have to.</p><p style="text-align: justify;">If, historically, a certain neighborhood correlates with loan default due to economic factors, a tree might split on ZIP code. That could improve prediction, but it is not a causal explanation of repayment behavior, and it might be ethically unacceptable or legally constrained depending on context. Trees are very good at exploiting correlations; they are not automatically good at identifying causes.</p><p style="text-align: justify;">A second misunderstanding is about feature &#8220;<em>importance</em>&#8221;. People often look at the root split and say, &#8220;<em>Ah, this must be the most important feature.</em>&#8221; Sometimes it is, but not always in a human sense. The root feature is the one that produced the largest impurity reduction <em>at that moment</em>, given:</p><ul><li><p>the available features,</p></li><li><p>the label distribution,</p></li><li><p>the greedy nature of the algorithm,</p></li><li><p>and the specific criterion (entropy, Gini, etc.).</p></li></ul><p style="text-align: justify;">If two features are correlated, the tree might pick one arbitrarily and never use the other, even if both are genuinely predictive. So &#8220;<em>appears early in the tree</em>&#8221; is not the same thing as &#8220;<em>most important in the world</em>&#8221;.</p><p style="text-align: justify;">A third misunderstanding is believing that trees are always interpretable in practice. A tiny tree with depth 3 is interpretable. A tree with depth 25 and hundreds of nodes is technically readable but practically incomprehensible - like a legal contract that is &#8220;<em>just words</em>&#8221;. Interpretability is not a binary property; it degrades with size.</p><p style="text-align: justify;">So the right takeaway is nuanced: trees <em>can</em> be interpretable, and they often are in constrained form, but you must actively manage complexity if interpretability is one of your goals.</p><h2>When decision trees shine &#8212; and when they struggle</h2><p style="text-align: justify;">Decision trees shine in settings where the world plausibly behaves like a set of interacting rules, or where we want a model that can represent interactions without feature engineering.</p><p>They often feel natural when:</p><ul><li><p>you have a mix of numeric and categorical features,</p></li><li><p>relationships are strongly non-linear,</p></li><li><p>feature interactions matter (e.g., &#8220;<em>income matters given debt level</em>&#8221;),</p></li><li><p>you care about explanation paths for debugging, policy, or communication.</p></li></ul><p style="text-align: justify;">Trees also handle monotonic transformations and scaling reasonably well, because a threshold split doesn&#8217;t care whether income is measured in dollars or thousands, as long as the order is preserved.</p><p>Where trees struggle is equally instructive:</p><ul><li><p><strong>Noisy labels:</strong> a tree can keep splitting to &#8220;<em>explain</em>&#8221; noise, unless you stop it.</p></li><li><p><strong>Small datasets:</strong> the best split can be unstable, leading to high variance.</p></li><li><p><strong>Smooth relationships:</strong> if the true relationship is gradual (risk increases smoothly with debt-to-income ratio), hard thresholds can be a crude approximation unless the tree becomes deep.</p></li><li><p><strong>Extrapolation:</strong> trees are poor at extrapolating beyond the range of observed feature values. They partition what they&#8217;ve seen; they don&#8217;t naturally extend linear trends.</p></li></ul><p style="text-align: justify;">A good rule of thumb is: trees are excellent at carving and categorizing within the observed space, but they can be brittle when the signal is subtle and continuous.</p><h2>What decision trees teach us about machine learning as a whole</h2><p style="text-align: justify;">Decision trees are a lesson in how far you can go with a simple learning primitive: choose a question that reduces uncertainty, then repeat. That&#8217;s not just a tree idea; it&#8217;s a recurring pattern across ML. Many models can be understood as building internal representations that make the label more predictable - trees just do it with explicit questions you can inspect.</p><p style="text-align: justify;">They also teach a second lesson that is easy to ignore when you first learn the math: learning from finite data is inherently uncertain. A tree can look like pure logic - clean thresholds, crisp branches, decisive conclusions - yet it is still a statistical object, shaped by sampling variation, label noise, and whatever features you decided to measure. The &#8220;logic&#8221; is learned, not revealed.</p><p style="text-align: justify;">If you take that seriously, you stop expecting a tree (or any model) to hand you the true rules of reality. Instead, you start treating models as tools that compress experience into actionable guidance, with errors that have to be managed, not wished away.</p><p style="text-align: justify;">And finally, trees offer a gentle but important moral: interpretability is not the opposite of complexity. A decision tree can be perfectly interpretable at the level of each step, and still be fragile in the overall story it tells. The art is knowing when the next question is real insight - and when it&#8217;s the model becoming an over-curious interviewer.</p><p style="text-align: justify;">A decision tree is, in the end, a learned conversation with the data. Good tree-building is learning when to keep asking - and when to stop.</p><div><hr></div><p><strong>&#129504; What if complex models are just simple ideas&#8230; repeated at scale?</strong></p><p style="text-align: justify;">The same mindset behind decision trees powers neural networks - just bigger, deeper, and layered. What looks like complexity is often just repetition.</p><p><strong>&#128172; Did this change how you see machine learning?</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/decision-trees-how-machines-learn/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/decision-trees-how-machines-learn/comments"><span>Leave a comment</span></a></p><div><hr></div><ol><li><p><strong><a href="https://a.co/d/0dK0vp8Y">The Elements of Statistical Learning: Data Mining, Inference, and Prediction, Second Edition</a></strong> <em>by Trevor Hastie, Robert Tibshirani, Jerome Friedman</em></p></li><li><p><strong><a href="https://a.co/d/05a0ROji">An Introduction to Statistical Learning: with Applications in Python</a></strong> <em>by Gareth James, Daniela Witten, Trevor Hastie, Robert Tibshirani, Jonathan Taylor</em></p></li><li><p><strong><a href="https://a.co/d/02kYNKuh">Pattern Recognition and Machine Learning (Information Science and Statistics)</a></strong></p><p><em>by Christopher M. Bishop</em></p></li><li><p><strong><a href="https://a.co/d/09EXQ1VD">Classification and Regression Trees 1st Edition</a></strong> <em>by Leo Breiman, Jerome Friedman, R.A. Olshen, Charles J. Stone</em></p></li><li><p><strong><a href="https://a.co/d/06rRvkME">Machine Learning: A Probabilistic Perspective (Adaptive Computation and Machine Learning series)</a></strong> <em>by Kevin P. Murphy</em></p></li><li><p><strong><a href="https://doi.org/10.1007/BF00993309">C4.5 Programs for Machine Learning</a></strong> <em>by J. Ross Quinlan</em></p></li><li><p><strong>scikit-learn documentation for <a href="https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html">DecisionTreeClassifier</a> and <a href="https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeRegressor.html">DecisionTreeRegressor</a></strong></p></li></ol>]]></content:encoded></item><item><title><![CDATA[CQRS — when Reads and Writes take different paths]]></title><description><![CDATA[Systems Literacy]]></description><link>https://iam.slys.dev/p/cqrs-when-reads-and-writes-take-different</link><guid isPermaLink="false">https://iam.slys.dev/p/cqrs-when-reads-and-writes-take-different</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Wed, 20 May 2026 20:57:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!LtxW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcf9719c-f460-4781-956f-814fdc5b122e_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LtxW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcf9719c-f460-4781-956f-814fdc5b122e_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LtxW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcf9719c-f460-4781-956f-814fdc5b122e_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!LtxW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcf9719c-f460-4781-956f-814fdc5b122e_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!LtxW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcf9719c-f460-4781-956f-814fdc5b122e_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!LtxW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcf9719c-f460-4781-956f-814fdc5b122e_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LtxW!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcf9719c-f460-4781-956f-814fdc5b122e_1672x941.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fcf9719c-f460-4781-956f-814fdc5b122e_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b3e9e644-335c-4cf9-8d0a-44b2c2a0d0fb_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1948848,&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://iam.slys.dev/i/176729552?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3e9e644-335c-4cf9-8d0a-44b2c2a0d0fb_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LtxW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcf9719c-f460-4781-956f-814fdc5b122e_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!LtxW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcf9719c-f460-4781-956f-814fdc5b122e_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!LtxW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcf9719c-f460-4781-956f-814fdc5b122e_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!LtxW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcf9719c-f460-4781-956f-814fdc5b122e_1672x941.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><blockquote><p><strong>Imagine a restaurant.</strong></p><p>Customers ask the waiter what&#8217;s on the menu - they want an answer <em>right away</em>. But when they place an order, the process changes: the waiter writes it down, passes it to the kitchen, and only later brings back the meal.</p><p>In that restaurant, reading and writing already take different paths. That&#8217;s exactly what CQRS is about.</p></blockquote><p>In this little story, getting information (what&#8217;s on the menu) is a <strong>read</strong> operation that demands immediacy. Placing an order is a <strong>write</strong> operation that takes a longer path through preparation. We intuitively separate the two. In software systems, however, we often try to do both in the same place - and that can cause friction. Mixing reading and writing in one process is like two people trying to write on the same sheet of paper simultaneously; it&#8217;s bound to slow things down.</p><div class="callout-block" data-callout="true"><p>Reading and deciding are different modes of action, and if we force them through a single channel, they get in each other&#8217;s way. This introduction sets the scene for a pattern that formally <strong>separates reads and writes</strong> so each can do what it does best, without stepping on each other&#8217;s toes.</p></div><h1>&#129513; Why the classic approach breaks down</h1><p>Before introducing CQRS, let&#8217;s understand the pain that it addresses. In a traditional architecture, we often use <strong>one database (and one data model)</strong> to handle both reads and writes (the classic CRUD approach). This is simple and works fine at small scale, but as traffic grows, problems emerge.</p><p><strong>Contention between reads and writes.</strong> If many users are reading and writing at the same time, they start to compete. Every write operation can lock part of the data that others want to read, causing queries to wait. For example, in a SQL database, inserting or updating a row can lock a table or index that a read query needs, leading to contention.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p><p><strong>Performance degradation.</strong> As the single database does more work, reads and writes both slow down. Complex queries have to sift through data that is also being updated in real-time, adding load on the system. The more we try to optimize one side (say, by adding indexes for faster reads), the more we might hurt the other side (since those same indexes slow down writes).</p><p><strong>Scaling limits.</strong> Eventually, scaling a one-database-fits-all system becomes difficult. You can scale the database vertically (more CPU, RAM), but at some point the mix of workloads (read-heavy vs write-heavy) prevents optimal scaling. The data model becomes a bottleneck because it&#8217;s trying to serve two masters with different needs.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vrd4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb914a1e4-b1d5-44c8-8342-e450e6850968_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vrd4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb914a1e4-b1d5-44c8-8342-e450e6850968_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!vrd4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb914a1e4-b1d5-44c8-8342-e450e6850968_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!vrd4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb914a1e4-b1d5-44c8-8342-e450e6850968_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!vrd4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb914a1e4-b1d5-44c8-8342-e450e6850968_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vrd4!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb914a1e4-b1d5-44c8-8342-e450e6850968_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b914a1e4-b1d5-44c8-8342-e450e6850968_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7147c5a2-28ef-402b-9d34-a0fd9875893c_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1532912,&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://iam.slys.dev/i/176729552?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7147c5a2-28ef-402b-9d34-a0fd9875893c_1672x941.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_!vrd4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb914a1e4-b1d5-44c8-8342-e450e6850968_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!vrd4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb914a1e4-b1d5-44c8-8342-e450e6850968_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!vrd4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb914a1e4-b1d5-44c8-8342-e450e6850968_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!vrd4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb914a1e4-b1d5-44c8-8342-e450e6850968_1672x941.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>It&#8217;s like trying to take notes on the <strong>same sheet</strong> of paper where someone else is simultaneously writing &#8211; a messy and inefficient process. When reads and writes interfere with each other, <strong>everyone waits</strong>. The result is slower queries, frustrated users, and headaches when the system grows beyond a certain load.</p><div class="callout-block" data-callout="true"><p>Reading and writing in the same place is like trying to take notes on the same sheet someone else is writing on. Sooner or later, neither of you can write clearly.</p></div><p>In short, the classic all-in-one approach breaks down under heavy load or complex requirements. We need a better way to handle the difference in nature between querying data and changing data. <strong>Enter CQRS.</strong></p><div><hr></div><p><strong>&#9888;&#65039; A practical career resource worth following</strong></p><div class="embedded-publication-wrap" data-attrs="{&quot;id&quot;:4523411,&quot;embedding_publication_id&quot;:null,&quot;name&quot;:&quot;Career Compass&quot;,&quot;logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G-6Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F687928a3-235e-463c-88e0-944c8e8de7b3_1024x1024.png&quot;,&quot;base_url&quot;:&quot;https://careercompass1.substack.com&quot;,&quot;hero_text&quot;:&quot;Sharing practical advice, job-market insights, and proven strategies to help you turn skills into income, navigate career transitions, land meaningful jobs, and grow with clarity and confidence.&quot;,&quot;author_name&quot;:&quot;Skill to Income by Yusup&quot;,&quot;show_subscribe&quot;:true,&quot;logo_bg_color&quot;:&quot;#eff6ff&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="EmbeddedPublicationToDOMWithSubscribe"><div class="embedded-publication show-subscribe"><a class="embedded-publication-link-part" native="true" href="https://careercompass1.substack.com?utm_source=substack&amp;utm_campaign=publication_embed&amp;utm_medium=web"><img class="embedded-publication-logo" src="https://substackcdn.com/image/fetch/$s_!G-6Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F687928a3-235e-463c-88e0-944c8e8de7b3_1024x1024.png" width="56" height="56" style="background-color: rgb(239, 246, 255);"><span class="embedded-publication-name">Career Compass</span><div class="embedded-publication-hero-text">Sharing practical advice, job-market insights, and proven strategies to help you turn skills into income, navigate career transitions, land meaningful jobs, and grow with clarity and confidence.</div><div class="embedded-publication-author-name">By Skill to Income by Yusup</div></a><form class="embedded-publication-subscribe" method="GET" action="https://careercompass1.substack.com/subscribe?"><input type="hidden" name="source" value="publication-embed"><input type="hidden" name="autoSubmit" value="true"><input type="email" class="email-input" name="email" placeholder="Type your email..."><input type="submit" class="button primary" value="Subscribe"></form></div></div><p><em><strong>Career Compass</strong></em> by <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Skill to Income by Yusup&quot;,&quot;id&quot;:328782625,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/40483e9a-1016-44c8-a6c2-22986e46b51e_200x200.jpeg&quot;,&quot;uuid&quot;:&quot;be8e0440-526d-4807-b997-5cc84d8028e6&quot;}" data-component-name="MentionToDOM"></span>  helps people turn skills into income with clear, actionable advice on job searching, freelancing, content creation, and career growth. What I like is the focus on reality: <em>positioning</em>, <em>visibility</em>, <em>proof</em>, and the changing rules of the job market.</p><div><hr></div><h1>&#9881;&#65039; What CQRS really means</h1><p>CQRS stands for <strong>Command Query Responsibility Segregation</strong> - which sounds academic, but the idea is quite straightforward. Let&#8217;s break it down in simple terms:</p><ul><li><p>A <strong>Command</strong> is an operation that <strong>changes state</strong>. It&#8217;s an instruction to do something (e.g., &#8220;<em>Place Order</em>&#8221;, &#8220;<em>Update Account</em>&#8221;, &#8220;<em>Book a Hotel Room</em>&#8221;). In other words, commands are writes &#8211; they express an intent to modify data or trigger an action.</p></li><li><p>A <strong>Query</strong> is an operation that <strong>reads state</strong>. It&#8217;s a request for information (e.g., &#8220;<em>Get Order Details</em>&#8221;, &#8220;<em>List My Orders</em>&#8221;, &#8220;<em>What rooms are available?</em>&#8221;). Queries do <strong>not</strong> change data; they only retrieve it.</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_!kJz3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20945f60-73c7-456c-b061-82ec424291e1_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kJz3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20945f60-73c7-456c-b061-82ec424291e1_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!kJz3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20945f60-73c7-456c-b061-82ec424291e1_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!kJz3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20945f60-73c7-456c-b061-82ec424291e1_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!kJz3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20945f60-73c7-456c-b061-82ec424291e1_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kJz3!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20945f60-73c7-456c-b061-82ec424291e1_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/20945f60-73c7-456c-b061-82ec424291e1_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6a356aba-5cd9-40bc-ad2d-65cde5b0e2db_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1394989,&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://iam.slys.dev/i/176729552?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a356aba-5cd9-40bc-ad2d-65cde5b0e2db_1672x941.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_!kJz3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20945f60-73c7-456c-b061-82ec424291e1_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!kJz3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20945f60-73c7-456c-b061-82ec424291e1_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!kJz3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20945f60-73c7-456c-b061-82ec424291e1_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!kJz3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20945f60-73c7-456c-b061-82ec424291e1_1672x941.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></p><p>The principle of CQRS is to <strong>separate the two responsibilities</strong> so that reads and writes go through different paths or models. In traditional systems, we often use the same data structures, objects, or databases for both updating and reading data. CQRS says: <strong>don&#8217;t do that</strong> &#8211; use one model (or set of objects) for commands (writes) and a different model for queries (reads).<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a></p><p>Why separate? Because the way you <strong>update</strong> information can be very different from the way you <strong>read</strong> it. A single unified model often becomes a compromise that doesn&#8217;t serve either side perfectly. As Martin Fowler puts it, having the same conceptual model for commands and queries often &#8220;<em>leads to a more complex model that does neither well</em>&#8221;. By splitting them, each side can be simpler and more focused on its job.</p><div><hr></div><p><strong>&#129504; What if architectural patterns are really just recurring solutions to recurring problems?</strong></p><p>CQRS, caching, queues, sharding - each pattern exists because the same problems appear repeatedly at scale.</p><p><strong>&#128073; Get more practical breakdowns - straight to your inbox.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Think of a company with two departments: one department handles <strong>orders</strong> (taking actions), and another handles <strong>information</strong> (answering questions). They are part of the same company (the overall system), but they operate differently and have different processes. If you route a <strong>request to purchase</strong> through the information desk, it&#8217;ll be inefficient; conversely, if you ask the ordering department a purely informational question, they might have to dig through records unnecessarily. It makes sense to direct queries to the <strong>information department</strong> and commands to the <strong>orders department</strong>.</p><div class="callout-block" data-callout="true"><p>Imagine an organization splitting work between two teams &#8211; one excels at <strong>taking action</strong> (processing changes), the other excels at <strong>providing answers</strong> (serving read requests). Same organization, different pace and process. CQRS formalizes this separation in software architecture.</p></div><p>In summary, <strong>CQRS means using different models (and possibly different paths or components) for writes and reads</strong>. A &#8220;<em>Command</em>&#8221; goes to the write side of the system to change something, while a &#8220;<em>Query</em>&#8221; goes to the read side to fetch something. By segregating these, each side can be optimized and evolved independently.</p><h1>&#129518; How it works in practice</h1><p>It&#8217;s one thing to describe CQRS in theory, but how does it actually look in a system? Let&#8217;s sketch a high-level picture of a CQRS architecture and then break down the steps:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O4qF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3246feb7-1d57-477d-a924-94e66e0da888_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O4qF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3246feb7-1d57-477d-a924-94e66e0da888_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!O4qF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3246feb7-1d57-477d-a924-94e66e0da888_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!O4qF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3246feb7-1d57-477d-a924-94e66e0da888_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!O4qF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3246feb7-1d57-477d-a924-94e66e0da888_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!O4qF!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3246feb7-1d57-477d-a924-94e66e0da888_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3246feb7-1d57-477d-a924-94e66e0da888_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b4babfc-2c24-4b4f-8ed1-b8b80d535f6d_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1453129,&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://iam.slys.dev/i/176729552?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b4babfc-2c24-4b4f-8ed1-b8b80d535f6d_1672x941.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_!O4qF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3246feb7-1d57-477d-a924-94e66e0da888_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!O4qF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3246feb7-1d57-477d-a924-94e66e0da888_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!O4qF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3246feb7-1d57-477d-a924-94e66e0da888_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!O4qF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3246feb7-1d57-477d-a924-94e66e0da888_1672x941.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 a typical CQRS setup, you might have something like this:</p><ol><li><p><strong>The user sends a command.</strong> For example, a customer clicks &#8220;<em>Place Order</em>&#8221; in an e-commerce app. This request is sent to a <strong>Command API</strong> (or service) dedicated to handling write operations.</p></li><li><p><strong>The write model processes the command.</strong> The command is validated and business logic is applied. The system updates the <strong>write model</strong> &#8211; this could mean changing the state of domain objects or, commonly, <strong>storing an event</strong> that represents the change (more on events in a moment). The write side is concerned with ensuring the command is executed correctly (all rules checked, data consistency maintained for the operation). It does <em>not</em> immediately update what the user sees; instead, it records the change in the system&#8217;s source of truth (for instance, saving an &#8220;<em>OrderPlaced</em>&#8221; event to an <strong>Event Store</strong> or updating a orders table).<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a></p></li><li><p><strong>The read model updates asynchronously.</strong> Here&#8217;s where CQRS differs from a traditional approach: the system doesn&#8217;t try to serve the query from the write database in real-time. Instead, the change from step 2 (the new order event or updated data) is communicated to a <strong>read model</strong> <strong>-</strong> often via an asynchronous message or event. The read model is a separate representation of the data, typically stored in a format optimized for fast queries (it could be a denormalized SQL view, a NoSQL database, an in-memory cache, etc. - whatever makes reads speedy for the use case). A background process (sometimes called a <strong>denormalizer</strong> or projector) takes the event (&#8220;<em>OrderPlaced</em>&#8221;) and updates the read model accordingly, e.g. inserting a record into an &#8220;<em>orders view</em>&#8221; with the order&#8217;s details.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a> This update happens asynchronously, meaning the user doesn&#8217;t wait on it in order to complete the command.</p></li><li><p><strong>The user makes a query.</strong> Now suppose the user (or another system component) asks, &#8220;<em>What&#8217;s the status of my order?</em>&#8221; &#8211; this is a <strong>query</strong>. The query is handled by a <strong>Query API</strong> (or service) dedicated to reads. This query doesn&#8217;t hit the write database; instead, it goes straight to the <strong>read model</strong> (the optimized data store from step 3). Because that read model is built for queries, it can return the result quickly (for instance, it might return the order status and details from a precomputed view).</p></li></ol><p>A key consequence of this design is that the user&#8217;s reads don&#8217;t slow down the writes, and vice versa. </p><div class="callout-block" data-callout="true"><p>The read model is a <strong>projection</strong> of the write model&#8217;s data, shaped specifically for answering questions efficiently. </p></div><p>Meanwhile, the write model (the source of truth) only worries about processing commands and ensuring the integrity of transactions, not about serving queries under load.</p><p>It&#8217;s worth noting that the <strong>read model might not reflect the very latest write instantly</strong>. In step 3, we updated the read model asynchronously. This means there&#8217;s a short window where the user&#8217;s query might be answered from slightly older data if the new event hasn&#8217;t been applied yet. This is known as <strong>eventual consistency</strong>: the read side will catch up and become consistent with the write side, typically very quickly, but not necessarily at the exact same millisecond as the write.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a> We&#8217;ll discuss this trade-off more in the challenges section.</p><p>To solidify the concept, here&#8217;s a very simple pseudo-code illustration of a CQRS flow in action using Python-like pseudocode:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;8a9c9027-0346-49c1-a458-e075a37adef8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Simple CQRS-like pseudo-code for an order system

# The write side (command handling):
event_store = []            # This will record events (our &#8220;truth&#8221; of what happened)

def place_order(order_id, customer, item):
    # 1. Validate and apply business rules
    if item not in inventory:
        raise ValueError(&#8221;Item not in stock&#8221;)
    # ... (other validations)
    # 2. Record the state change as an event in the write model (event store)
    event = {&#8221;type&#8221;: &#8220;OrderPlaced&#8221;, &#8220;order_id&#8221;: order_id, &#8220;customer&#8221;: customer, &#8220;item&#8221;: item}
    event_store.append(event)
    # Simulate publishing an event to update the read model
    update_read_model(event)
    return &#8220;Order placed successfully.&#8221;</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;474992df-e9e4-485d-920a-1011c2e438d3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Simple CQRS-like pseudo-code for an order system

# The read side (query handling):
read_database = {}          # A simple read model (e.g., a denormalized view of orders)

def update_read_model(event):
    # This function is called when a new event is recorded, to update the read model.
    if event[&#8221;type&#8221;] == &#8220;OrderPlaced&#8221;:
        order_id = event[&#8221;order_id&#8221;]
        # Initialize the order record in read DB with relevant info
        read_database[order_id] = {
            &#8220;customer&#8221;: event[&#8221;customer&#8221;],
            &#8220;item&#8221;: event[&#8221;item&#8221;],
            &#8220;status&#8221;: &#8220;Placed&#8221;
        }
    # (In a real system, more event types and handling would be here.)

def get_order_status(order_id):
    # 3. Query the read model for the current state (fast read)
    order = read_database.get(order_id)
    if order:
        return f&#8221;Status: {order[&#8217;status&#8217;]}&#8221;
    else:
        return &#8220;Order not found.&#8221;</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;b195b032-d3eb-446d-9e2a-04fb7fc1ad17&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Example usage:
print(place_order(101, &#8220;Alice&#8221;, &#8220;Book&#8221;))   # User places an order (write operation)
print(get_order_status(101))               # User queries the order status (read operation)</code></pre></div><p>In the above pseudo-code, <code>place_order</code> acts as the <strong>command handler</strong> (validating and recording the command), while <code>get_order_status</code> is the <strong>query handler</strong> retrieving data from a read-optimized store. The <code>update_read_model</code> function simulates the asynchronous projection of events into the read model (here we call it directly for simplicity). In a real system, this might be done by a separate process listening to events (e.g., via a message queue or event bus).</p><p>This separation means that the read function (<code>get_order_status</code>) doesn&#8217;t need to traverse the whole order processing logic or the normalized write schema; it just looks up a pre-populated result (fast!). The write function (<code>place_order</code>) doesn&#8217;t have to worry about how to present data for queries; it just focuses on correctly handling the transaction and recording the outcome.</p><h1>&#129504; Why CQRS matters &#8212; the benefits</h1><p>Why go through the trouble of separating reads and writes? Here are some <strong>practical advantages</strong> that CQRS can bring.</p><p><strong>Performance and scalability.</strong> Because read operations and write operations are isolated, each can be scaled independently. If your application has 100x more reads than writes (common in many systems), you can scale out the read side (more read replicas, caching, or a high-performance query database) without over-complicating the write side. Likewise, heavy write workloads can be tuned and scaled without affecting read query performance. The decoupling leads to higher overall performance since neither side is waiting on the other. Microsoft&#8217;s Azure Architecture guide notes that as applications grow, read and write paths have different performance needs &#8211; CQRS addresses this asymmetry and allows each to be optimized without the other becoming a bottleneck.</p><p><strong>Optimized data models for each side.</strong> In CQRS, the <strong>write model</strong> and the <strong>read model</strong> can be designed differently. The write side focuses on data consistency and business rules, often using a normalized data model or rich domain model to ensure integrity. The read side can use a totally different schema &#8211; even a different database technology &#8211; tailored for queries. For example, you might use a relational database for writes (to handle transactions and relationships) but a fast document or key-value store for the read model to retrieve data quickly. You could even have multiple read models optimized for different query scenarios (one for reporting analytics, one for real-time UI views, etc.), all derived from the same source of truth. This is a huge <strong>flexibility</strong> gain &#8211; adding a new way to present data doesn&#8217;t require rewriting the core business logic, you just build a new read model from the events or data.</p><p><strong>Clarity and maintainability.</strong> By separating responsibilities, your code and design can become cleaner. Each part of the system has a single job &#8211; handling commands or serving queries. This separation of concerns means the intent of code is clearer: you know a command handler is all about making a decision or change, while a query handler is just about assembling data to return. It aligns with the idea that &#8220;<em>commands decide, queries inform</em>&#8221;. This can make the system easier to reason about and maintain. In fact, having distinct command and query models often results in simpler models that don&#8217;t have to cover every scenario in one place. The Azure guidance explicitly states that this separation <strong>&#8220;</strong><em><strong>improves clarity</strong></em><strong>&#8221;</strong> in design.</p><p><strong>Independent Evolution.</strong> The read side and write side can evolve on separate timelines. If you need to change how data is displayed or aggregated for reads, you can often do so without touching the write side (as long as the necessary raw data is being captured). For instance, you might denormalize some data in the read model to speed up a new kind of query, without any changes to the transactional database. Similarly, if business rules change for writes, you update the write model and perhaps emit new events, but the read subscribers can adapt separately. This modularity adds a level of agility in development.</p><p><strong>Naturally fits event-driven systems.</strong> CQRS pairs naturally with event-driven architecture and event sourcing (we&#8217;ll explore this more in the next section). The benefit here is that if you&#8217;re already capturing events (each state change), constructing multiple read models from those events is straightforward. You get the performance benefits described above while also retaining a full history of changes (events). As one source puts it, CQRS can give you <strong>&#8220;</strong><em><strong>the benefits of event-level storage, but also much higher performance</strong></em><strong>&#8221;</strong> by decoupling reads from writes. In other words, you don&#8217;t pay the price of recomputing history on each read because you compute and store the relevant views at write-time.</p><div class="callout-block" data-callout="true"><p><strong>CQRS can make your system more scalable, more responsive, and often simpler to understand by giving reads and writes the freedom to follow their own optimal path.</strong> </p></div><p>In real-world complex systems (like large e-commerce platforms, high-traffic web apps, financial systems), these benefits can be the difference between an application that bogs down and one that flies. One author describes that CQRS&#8217;s separation can <strong>improve performance and maintainability, especially in complex systems</strong>.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a></p><p>However, CQRS is not a silver bullet. Those improvements come at the cost of additional complexity. Let&#8217;s talk about what you have to be mindful of when using CQRS.</p><h1>&#9878;&#65039; The challenges and trade-offs</h1><p>Every architectural pattern has its price, and CQRS is no exception. It&#8217;s important to approach it with eyes open to the <strong>trade-offs</strong> involved!</p><p><strong>Increased complexity.</strong> You are essentially maintaining two systems where before there was one. That means more code (e.g., separate classes for commands and queries, separate database schemas or tables, messaging infrastructure to link writes to reads). Martin Fowler cautions that while CQRS can be valuable in some situations, &#8220;<em>for most systems CQRS adds risky complexity</em>&#8221;. If your team is small or not experienced with distributed systems, the complexity of keeping two models in sync can lead to bugs and confusion. There is also the mental overhead - developers have to think in terms of eventual consistency, asynchronous updates, and multiple data representations, which is a step up in complexity from straightforward CRUD.</p><p><strong>Eventual consistency (latency of reads).</strong> In a CQRS system (especially one that uses separate databases for read and write), the data a user reads may not reflect the very latest write if it just happened. There&#8217;s an inherent delay in propagating changes from the write side to the read side. This is known as eventual consistency &#8211; the system guarantees that if no new updates occur, eventually all reads will catch up to the last write, but there is a time window where a query might get slightly stale data. For example, if a user just updated their profile picture, a subsequent query might still show the old picture for a short time until the read model is updated. Designing the system and user experience to handle this (e.g., showing a &#8220;<em>your change is processing</em>&#8221; message, or updating the UI optimistically) is important. It&#8217;s a trade-off: you gain throughput and scalability, but lose <em>immediate</em> consistency across reads and writes.</p><p><strong>Debugging and troubleshooting.</strong> With two separate paths and eventual consistency, it can be harder to trace the flow of data. When something goes wrong (say a read model isn&#8217;t updating correctly), developers need to chase events through asynchronous handlers and possibly across different services or processes. It&#8217;s not as straightforward as looking at a single database state. Tools and practices for monitoring, logging, and tracing become essential to understand what happened. For instance, if a query returns an unexpected result, one must determine if the write didn&#8217;t occur, the event didn&#8217;t get published, or the read model logic had a bug. This distributed flow can make <strong>testing and debugging more challenging</strong> than in a simple CRUD app. (On the flip side, having an event log can aid in debugging by providing an audit trail, but analyzing it requires effort.)</p><p><strong>Data duplication and storage costs.</strong> Often the read model contains data duplicated from the write model (perhaps in a different shape, such as denormalized form). You might be storing the same information twice &#8211; once in the write DB and once (or multiple times) in various read models. This can increase storage needs and the complexity of keeping the data in sync. There&#8217;s also the possibility of <strong>code duplication</strong>, where some logic might be repeated on both sides (though ideally commands and queries do different things, sometimes validation or data shaping logic can appear in both). Chris Richardson notes in his discussion of CQRS that one drawback can be <em>potential code duplication and replication lag</em> due to maintaining multiple views.</p><p><strong>Operational overhead.</strong> With CQRS, you might need additional infrastructure. For example, to feed changes from write model to read model, many systems use a <strong>message broker or event bus</strong> (like RabbitMQ, Kafka, etc.). Operating and monitoring these components adds to the DevOps burden. There is also the need to handle failure cases &#8211; e.g., what if the read model update fails? You need a strategy (replay the event, retry mechanism, etc.) to ensure eventual consistency. All of this means CQRS can be more expensive to build and run.</p><p>In short, CQRS solves certain problems at the cost of making your overall architecture more <strong>sophisticated</strong>. As one engineering blog puts it, &#8220;<em>CQRS is a powerful pattern ... but it also adds extra complexity to your system</em>&#8221;. It&#8217;s like orchestrating an ensemble instead of a solo performance &#8211; when it&#8217;s in sync it&#8217;s great, but there are more parts that could fall out of tune. The key is to use this pattern <strong>when its benefits outweigh the costs</strong>, and to manage the complexity with good tooling and practices.</p><p>A good rule of thumb is to start with the simplest thing that could work (often CRUD) and only introduce CQRS if you start hitting the limitations of the simple approach (e.g., read performance issues, overly complex domain model trying to handle everything, etc.). Next, we&#8217;ll look at a scenario where CQRS particularly shines: when combined with event sourcing in an event-driven system.</p><div><hr></div><p><strong>&#9878;&#65039; What if every architecture decision is really a trade-off?</strong></p><p>Scalability, simplicity, consistency, flexibility - improving one thing often means sacrificing another. There&#8217;s rarely a &#8220;<em>best</em>&#8221; solution - only the best fit for a context.</p><p><strong>&#128172; What trade-off do you think engineers underestimate the most?</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/cqrs-when-reads-and-writes-take-different/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/cqrs-when-reads-and-writes-take-different/comments"><span>Leave a comment</span></a></p><div><hr></div><h1>&#128257; CQRS + Event Sourcing - a natural match</h1><p>CQRS often goes hand-in-hand with <strong>Event Sourcing</strong>, and for good reason. Let&#8217;s first clarify event sourcing in a nutshell:</p><p><strong>Event Sourcing</strong> is a pattern where instead of storing just the current state in your database, you store a <strong>log of every change (event)</strong> that happens to your data. The system&#8217;s state at any time can be derived by replaying or aggregating these events. For example, instead of storing the final balance of a bank account, you store each deposit and withdrawal event; the current balance is the sum of all those events. This gives you a complete audit trail of all changes (every &#8220;<em>story</em>&#8221; that happened to the data, not just the latest chapter).</p><p>The challenge with event sourcing is that <strong>reading data directly from an event log can be slow</strong>. If you want to know the current state, you might have to crunch through a long list of events. As the number of events grows, reconstructing state on-the-fly becomes impractical. For instance, imagine having to read through 10 years of transaction events just to show a user their current account balance &#8211; that&#8217;s not efficient.</p><p><strong>CQRS is the solution to this problem</strong> of reading from an event-sourced system. Instead of computing the state at query time, CQRS suggests computing it at write time. In other words, when an event happens, <strong>immediately update a read model (a precomputed view)</strong> for that data. This way, each event&#8217;s impact on the query-able state is processed once (when the event occurs), and subsequent reads can just fetch the already-calculated result. As the Confluent developers put it, CQRS &#8220;<em>performs computations when the data is written, not when it&#8217;s read... each computation is performed only once, no matter how many times the data is read in the future</em>&#8221;.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9kY0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a9f8386-512c-4eb5-bb17-429781268d2a_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9kY0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a9f8386-512c-4eb5-bb17-429781268d2a_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!9kY0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a9f8386-512c-4eb5-bb17-429781268d2a_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!9kY0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a9f8386-512c-4eb5-bb17-429781268d2a_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!9kY0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a9f8386-512c-4eb5-bb17-429781268d2a_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9kY0!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a9f8386-512c-4eb5-bb17-429781268d2a_1672x941.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7a9f8386-512c-4eb5-bb17-429781268d2a_1672x941.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/240e2097-7b7b-4958-9fcd-20072def4250_1672x941.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1533608,&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://iam.slys.dev/i/176729552?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F240e2097-7b7b-4958-9fcd-20072def4250_1672x941.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_!9kY0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a9f8386-512c-4eb5-bb17-429781268d2a_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!9kY0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a9f8386-512c-4eb5-bb17-429781268d2a_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!9kY0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a9f8386-512c-4eb5-bb17-429781268d2a_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!9kY0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a9f8386-512c-4eb5-bb17-429781268d2a_1672x941.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>Here&#8217;s how CQRS and Event Sourcing typically work together:</p><ul><li><p>The <strong>write side</strong> of the system is event-sourced. That means every command that changes state produces one or more <strong>events</strong> that are saved to an <strong>Event Store</strong> (an append-only log of events). This event store is the &#8220;<em>truth</em>&#8221; of what happened.</p></li><li><p>The <strong>read side</strong> of the system subscribes to those events. For each event, the read side updates its own database or cache. For example, if an <code>OrderPlaced</code> event is emitted, the read model (say, an &#8220;<em>Orders View</em>&#8221; stored in a fast lookup table) will create a new record for that order or update relevant counts.</p></li><li><p>Now, when a query comes in (like &#8220;<em>Get order status</em>&#8221; or &#8220;<em>Show recent orders</em>&#8221;), the system simply reads from the <strong>read model&#8217;s database</strong>, which already has the answer in a convenient form (no complex joins or heavy computation needed at query time).</p></li></ul><p>This pairing gives you the best of both worlds:</p><ul><li><p>You have a <strong>complete history</strong> of all changes (thanks to the event store). This is great for auditing, debugging, or reconstructing state if needed. You can always re-build the read model from scratch by replaying events, which gives a lot of power and flexibility.</p></li><li><p>You also have <strong>fast queries</strong> (thanks to the precomputed read models). Users get their answers quickly because we did the work upfront, at the time of the writes. The read and write workloads are separated, so each can be optimized.</p></li></ul><p>It&#8217;s no surprise that CQRS is <em>&#8220;by far the most common way that event sourcing is implemented in real-world applications&#8221;</em>. The two patterns complement each other: event sourcing ensures every state change is captured as an event, and CQRS ensures those events are turned into queryable views efficiently.</p><p>Another benefit of this combination is the ability to create multiple different projections of the same data. Since you have the log of events as the source of truth, you can build different read models for different purposes. For example, you could have one read model for real-time user-facing queries, another read model feeding an analytics dashboard, and maybe another serving a search index &#8211; all built from the same event stream. If you need a new type of view, you can create a new projector that processes the past events (since they&#8217;re all stored) and generates the new view. This gives a lot of flexibility to adapt the system to new requirements without disturbing the transactional core. Confluent&#8217;s guide notes that it&#8217;s common to create <strong>multiple views from the same event log</strong> for different use cases.</p><div class="callout-block" data-callout="true"><p>CQRS says <em>&#8220;separate reads from writes&#8221;</em>. Event Sourcing says <em>&#8220;don&#8217;t throw away any of the changes &#8211; record every event&#8221;</em>. Together, they make your system both <strong>scalable</strong> (because of the separation) and <strong>truthful</strong> (because of the complete log of events). Writes tell the full story; reads present whatever chapter of that story you need, in the form you need it.</p></div><p>Of course, using both patterns means you are committing to an even more sophisticated architecture &#8211; you&#8217;ll likely need robust messaging to deliver events, careful design to handle eventual consistency, and so on. But for domains where audit trails and high performance are critical (finance, ordering systems, collaborative applications), CQRS + Event Sourcing can be a game-changer.</p><h1>&#129513; When (and when not) to use CQRS</h1><p>With all the pros and cons laid out, a natural question is: <strong>Should I apply CQRS to my project?</strong> The answer, as with most architecture decisions, is &#8220;<em>it depends</em>&#8221;. CQRS is not a one-size-fits-all solution &#8211; in fact, in many cases it might be overkill. Here are some guidelines:</p><h2><strong>&#9989; Use CQRS when</strong></h2><ul><li><p><strong>High read/write imbalance.</strong> Your system has a very high volume of reads compared to writes (or vice versa) and you need to scale them differently. For instance, an application where thousands of users mostly query data (reads) but only admins occasionally update it (writes) could benefit from separate scaling of read vs. write models.</p></li><li><p><strong>Complex domain logic.</strong> If the write side of your system (the business logic) is complex and you want to keep that code isolated from the simpler task of data retrieval, CQRS can help. A complex domain might involve lots of validations, calculations, and rules on the command side, which you can model with rich domain objects, while keeping queries lean and straightforward.</p></li><li><p><strong>Performance and scalability requirements.</strong> You&#8217;ve identified performance bottlenecks due to the read/write contention or you anticipate scaling challenges. CQRS can be a way to break those bottlenecks by tuning each side separately. Large, data-intensive applications (e.g., an e-commerce site with heavy product catalog reads and fewer writes for orders, or a social media platform where reads of profiles and posts far exceed writes) are potential candidates.</p></li><li><p><strong>Multiple view or integration requirements.</strong> If you foresee the need for multiple representations of the data (perhaps different services or UI views that each need the data shaped differently), CQRS can provide a clean path to maintain several read models. Also, if you are integrating with other systems via events, CQRS naturally feeds into that by treating writes as events that other components can subscribe to.</p></li><li><p><strong>Event sourcing or audit log needed.</strong> As discussed, if having an audit log of all changes is valuable (compliance, debugging, analytic replays), and you want to utilize event sourcing, then CQRS will likely be a beneficial companion pattern. In fact, some experts say CQRS is almost necessary to effectively query an event-sourced system.</p></li><li><p><strong>Independent team development.</strong> In some large organizations, you might even separate teams for developing the read side and write side (since they require different expertise &#8211; e.g., database optimization for reads vs business logic for writes). CQRS can enforce a boundary that teams can work on without too much stepping on each other.</p></li></ul><h2><strong>&#10060; Avoid CQRS when:</strong></h2><ul><li><p><strong>Simple or small applications.</strong> If your application is small, straightforward, or has low throughput, CQRS is probably unnecessary. For example, a personal notes app or a simple blog website with low traffic doesn&#8217;t need this level of architectural separation &#8211; a single well-designed database and model will do just fine, and will be easier to build. As one source puts it, if you don&#8217;t have significant performance or scaling issues, CQRS might be <em>&#8220;overkill&#8221;</em>. You won&#8217;t gain much, and you&#8217;ll pay a complexity tax.</p></li><li><p><strong>Tightly coupled operations.</strong> If every action in your system immediately needs a result that depends on up-to-the-moment data (strong consistency requirements), the asynchronous nature of CQRS can be troublesome. For instance, if you have a workflow where a write is immediately followed by a read of the same data in the same user flow, and you cannot tolerate stale data, you might need to stick to a linear, strongly-consistent approach (or implement CQRS with careful workarounds, which complicates things).</p></li><li><p><strong>Lack of resources or expertise.</strong> Implementing CQRS (especially with event sourcing) successfully requires understanding of distributed systems, messaging, eventual consistency, etc. If your team is very small or new to these concepts, and the project timeline is tight, adopting CQRS could introduce risk. As one expert humorously summarized: <em>&#8220;When should you avoid CQRS? The answer is most of the time&#8221;</em>.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a> This doesn&#8217;t mean CQRS is <em>never</em> useful &#8211; it means default to simpler architecture unless you have a clear reason to use CQRS.</p></li><li><p><strong>Already sufficient solutions.</strong> Sometimes, you can solve performance issues with simpler tweaks like <strong>read replicas</strong> of your database, or caching, without a full CQRS split. Read replicas (database copies for serving queries) can alleviate read load on the primary database and might be much easier to implement. If that works for your scenario, it&#8217;s a lot less effort than a whole CQRS rewrite. CQRS is a more extreme measure and should be justified by requirements that truly need it.</p></li></ul><blockquote><p><strong>CQRS makes sense in an e-commerce platform</strong> that gets, say, 100 read requests (product views, searches, recommendations) for every 1 write request (placing an order, updating a cart). Such a system benefits from scaling out many read databases or caching layers, and isolating the complex ordering logic on the write side. </p><p>On the other hand, <strong>CQRS is overkill for a personal notes app</strong> that one user interacts with; the volume is low and a single database can handle both reads and writes easily.</p></blockquote><p>It&#8217;s also worth mentioning that you don&#8217;t have to apply CQRS to the entire system. In many cases, <strong>hybrid approaches</strong> are used &#8211; only the hot spots or complex subsystems use CQRS, while other parts of the application remain simple. For example, you might use CQRS+Event Sourcing for the core domain (like orders and payments), but use a straightforward CRUD approach for something like user profile management if it&#8217;s simple and low-volume. One engineering article advises <em>not</em> to use CQRS for the whole system, only for the parts where the complexity and scale demand it.</p><p>Martin Fowler also suggests that CQRS should be used only where it really adds value, because it&#8217;s a &#8220;<em>significant mental leap</em>&#8221; and can cause serious difficulties if done inappropriately. In summary, evaluate CQRS like any tool: great for some jobs, a liability for others.</p><p>Before we conclude, one more thing: What about implementation in practice? CQRS is a pattern, not tied to a specific technology. It can be implemented in any programming language or platform. There are frameworks that help with CQRS:</p><ul><li><p>In the <strong>.NET/Java world</strong>, for instance, libraries like <strong>MediatR</strong> (for .NET), <strong>Axon Framework</strong> (Java), or <strong>Apache Camel</strong> (for routing events) can assist in setting up pipelines for commands and queries.</p></li><li><p>In <strong>Python</strong>, there are libraries like <strong>Diator</strong> which provide abstractions for Command Handlers, Query Handlers, and integrate with message brokers for event handling.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-9" href="#footnote-9" target="_self">9</a> These frameworks handle some of the boilerplate, allowing you to focus on your business logic. For example, Diator allows you to define command classes and handlers, query classes and handlers, and wires up the messaging so that when a command is executed, events are published to update query models, etc. Using such a library can accelerate development if you choose to go the CQRS route.</p></li></ul><p>Ultimately, whether or not to use CQRS boils down to the specific needs of your project. </p><div class="callout-block" data-callout="true"><p>Start by asking: <strong>Do I really need to separate reads and writes to meet my goals?</strong> </p></div><p>If yes, CQRS is a powerful pattern to consider. If not, keeping things simple is usually the wise choice.</p><h1>&#128172; CQRS is a way of thinking</h1><p>As we wrap up, it&#8217;s worth reflecting that CQRS isn&#8217;t just a technical pattern &#8211; it&#8217;s almost a philosophy in system design. It forces you to think about <strong>operations in two distinct modes</strong>: the act of making a decision or change, and the act of retrieving information. This separation can influence how you design APIs, how you model your data, and how you think about consistency and user experience.</p><p>Many problems in software (and even in everyday life) become simpler when you acknowledge that <strong>not everything that asks a question needs to trigger an immediate change, and not every action should require an immediate answer</strong>. CQRS embodies this idea. It says: let the writes (decisions) happen in their own pipeline, and let the reads (questions) happen in their own pipeline. Each at its own speed, with its own optimizations.</p><p>Think back to the restaurant analogy in the introduction. The waiter doesn&#8217;t try to cook your food on the spot when you ask for it, nor does the chef come out to recite the menu to you. They have a system that matches the nature of the task: quick answers versus slower fulfillment. CQRS, in a sense, is applying that common-sense separation to software design.</p><p>In a world where users demand both real-time information and complex processing of their requests, CQRS offers a way to deliver <strong>both</strong> by not forcing one mode to always wait for the other. It&#8217;s about acknowledging the different speeds and purposes of <strong>questions</strong> versus <strong>commands</strong>.</p><p>To close on a slightly philosophical note: In life, when we make decisions (commands), we often need time to see their effects play out; when we seek information (queries), we value quick and clear responses. CQRS mirrors this in software. It&#8217;s a pattern that reminds us that <strong>focus and flow</strong> matter &#8211; do one thing at a time, do it well, and don&#8217;t let unrelated tasks impede each other.</p><p>Not every system needs CQRS, just like not every situation demands a formal separation of duties. But understanding this pattern gives you one more lens through which to view system design: sometimes, splitting things by their nature (write vs read) can turn a sluggish, tangled system into one that&#8217;s more elegant and efficient.</p><div><hr></div><p><strong>&#129513; What if system design is really about managing complexity?</strong></p><p>Most architectural patterns exist to make complexity easier to control - not disappear. Good architecture doesn&#8217;t remove trade-offs - it makes them visible. As systems grow, complexity becomes the real scaling problem.</p><p><strong>&#128257; Share this with someone learning system design.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/cqrs-when-reads-and-writes-take-different?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/cqrs-when-reads-and-writes-take-different?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h2>&#9997;&#65039; Recap</h2><p><strong>CQRS (Command Query Responsibility Segregation)</strong> is an architectural pattern where you <strong>separate write operations (commands)</strong> from <strong>read operations (queries)</strong>. Instead of one model or database doing double duty, you have distinct pathways for modifications and for queries.</p><div class="callout-block" data-callout="true"><p>It&#8217;s like a restaurant with a separate process for taking orders and answering questions - each task follows a different path optimized for its purpose.</p></div><p><strong>Why use it?</strong> In traditional one-size-fits-all systems, reads and writes compete for resources, causing contention and slowdowns. CQRS alleviates that by letting reads and writes scale and evolve independently. This often leads to <strong>better performance, scalability, and clarity</strong> in complex or high-traffic applications.</p><p><strong>How it works. </strong>A command goes through the write model (applying business logic and updating state, often by emitting events), and the read model is updated (asynchronously) to reflect those changes in a query-friendly form. A query then hits the read model (which is fast and optimized for retrieval) without disturbing the write process.</p><p><strong>Benefits.</strong> You can tailor the read database for efficient queries (even have multiple read views for different needs), while keeping the write side focused on business rules and consistency. Systems can handle larger load (since, for example, read-heavy traffic can be served by scaled-out read replicas). It also naturally fits with event-driven designs, giving you an <strong>audit log</strong> of all changes.</p><p><strong>Trade-offs.</strong> CQRS adds complexity. You introduce <strong>eventual consistency</strong>, meaning recent writes might not show up immediately in reads. There&#8217;s additional overhead in maintaining two sets of models and keeping them in sync. Debugging and testing require careful thought due to the asynchronous, distributed nature of data flow.</p><p><strong>When to apply.</strong> Use CQRS in <strong>domains that are complex, collaborative, or high-scale</strong> - where the extra complexity is justified by performance gains or design clarity. Good examples are systems with heavy read loads (social feeds, shopping catalogs, analytics dashboards) or where business transactions are complex. Avoid it for <strong>simple, low-scale applications</strong> or parts of an app where straightforward CRUD is more than enough.</p><p><strong>CQRS + Event Sourcing.</strong> They complement each other. Event sourcing gives you the history of changes, and CQRS ensures you don&#8217;t pay a performance penalty for that history by updating read models on the fly. Together, they yield systems that are both <strong>scalable and maintain a full history</strong> of state changes.</p><p><strong>Philosophy.</strong> More than just a pattern, CQRS encourages thinking about the intent of an operation. <strong>Commands decide (and change)</strong>, <strong>queries ask (and inform)</strong>. By honoring the difference, we allow each to &#8220;<em>move at its own rhythm</em>&#8221;, leading to a design optimized for both <em>focus</em> (each part does one job well) and <em>flow</em> (the system can handle more work overall).</p><p>With CQRS in your toolbox, you have the option to let reads and writes take different paths when the scenario calls for it &#8211; just like our waiter and kitchen, each doing what they do best.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p><a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs">CQRS pattern</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p><a href="https://martinfowler.com/bliki/CQRS.htm">martinFowler.com | CQRS</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p><a href="https://deviq.com/design-patterns/cqrs-pattern/">CQRS - Command Query Responsibility Segregation</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p><a href="https://developer.confluent.io/courses/event-sourcing/cqrs/">Command Query Responsibility Segregation (CQRS)</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p><a href="https://blog.risingstack.com/cqrs-explained-node-js-at-scale/">CQRS Explained</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p><a href="https://microservices.io/patterns/data/cqrs.html">Pattern: Command Query Responsibility Segregation (CQRS) </a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p><a href="https://solutionsarchitecture.medium.com/cqrs-a-deep-dive-into-command-query-responsibility-segregation-4fd83d79f756">CQRS: A Deep Dive into Command Query Responsibility Segregation</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p><a href="https://udidahan.com/2011/04/22/when-to-avoid-cqrs">When to avoid CQRS</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-9" href="#footnote-anchor-9" class="footnote-number" contenteditable="false" target="_self">9</a><div class="footnote-content"><p><a href="https://dev.to/akhundmurad/implementing-cqrs-in-python-41aj">Implementing CQRS in Python</a></p></div></div>]]></content:encoded></item><item><title><![CDATA[Thinking clearly in interviews — Longest Substring Without Repeating Characters]]></title><description><![CDATA[Understanding the why before the code (Rust &#183; Python &#183; Scala)]]></description><link>https://iam.slys.dev/p/thinking-clearly-in-interviews-longest</link><guid isPermaLink="false">https://iam.slys.dev/p/thinking-clearly-in-interviews-longest</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 30 Mar 2026 21:28:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Dzji!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Dzji!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Dzji!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!Dzji!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!Dzji!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!Dzji!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Dzji!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2892303,&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://iam.slys.dev/i/187834348?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Dzji!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!Dzji!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!Dzji!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!Dzji!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F867dd58b-6bc9-4163-9331-b847a489b7d7_1536x1024.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><strong>&#9888;&#65039; Before we dive in &#8212; a recommendation for makers and builders</strong></p><div class="embedded-publication-wrap" data-attrs="{&quot;id&quot;:3053309,&quot;embedding_publication_id&quot;:null,&quot;name&quot;:&quot;The Founders Corner&#174;&quot;,&quot;logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!3lwY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1007f51a-8c4c-425f-bda5-da428bc29120_1024x1024.png&quot;,&quot;base_url&quot;:&quot;https://www.the-founders-corner.com&quot;,&quot;hero_text&quot;:&quot;Your weekly BOOST to Raise and Grow Like A Pro. Tips and Techniques used by prolific Founders.\n&quot;,&quot;author_name&quot;:&quot;Ruben Dominguez&quot;,&quot;show_subscribe&quot;:true,&quot;logo_bg_color&quot;:&quot;#fafafa&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="EmbeddedPublicationToDOMWithSubscribe"><div class="embedded-publication show-subscribe"><a class="embedded-publication-link-part" native="true" href="https://www.the-founders-corner.com?utm_source=substack&amp;utm_campaign=publication_embed&amp;utm_medium=web"><img class="embedded-publication-logo" src="https://substackcdn.com/image/fetch/$s_!3lwY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1007f51a-8c4c-425f-bda5-da428bc29120_1024x1024.png" width="56" height="56" style="background-color: rgb(250, 250, 250);"><span class="embedded-publication-name">The Founders Corner&#174;</span><div class="embedded-publication-hero-text">Your weekly BOOST to Raise and Grow Like A Pro. Tips and Techniques used by prolific Founders.
</div><div class="embedded-publication-author-name">By Ruben Dominguez</div></a><form class="embedded-publication-subscribe" method="GET" action="https://www.the-founders-corner.com/subscribe?"><input type="hidden" name="source" value="publication-embed"><input type="hidden" name="autoSubmit" value="true"><input type="email" class="email-input" name="email" placeholder="Type your email..."><input type="submit" class="button primary" value="Subscribe"></form></div></div><p><em><strong>The Founders Corner&#174;</strong></em> by <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Chris Tottman&quot;,&quot;id&quot;:4208729,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!dFRK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c896550-de04-4929-9967-52d09137f397_380x380.jpeg&quot;,&quot;uuid&quot;:&quot;dd85361a-e953-4090-902c-cf3ec543115c&quot;}" data-component-name="MentionToDOM"></span> is one of the most practical founder newsletters out there. He writes about turning big ambition into repeatable execution, using frameworks like OKRs and SMART goals, and helping founders think clearly rather than grind blindly.</p><div><hr></div><p>You&#8217;re walking through a busy farmers market with a friend. You agree on a simple rule: &#8220;<em>Let&#8217;s only buy one of each item</em>&#8221;. It sounds easy - until you realize how quickly your basket becomes a messy history of decisions. You pick up apples, then bread, then cheese&#8230; and a few stalls later you see apples again. You&#8217;re not doing anything wrong; you just forgot that apples were already in the basket.</p><p>So you pause. You don&#8217;t dump everything and start over. You don&#8217;t keep walking pretending the duplicate doesn&#8217;t exist. You do something more practical: you look back to the point where apples first showed up, and you adjust your plan so that your &#8220;<em>current run of unique items</em>&#8221; starts after that. Your basket still contains the past, but your <em>current constraint</em> (&#8220;<em>no duplicates</em>&#8221;) applies only to the window of choices you&#8217;re actively considering.</p><p>That little moment - realizing you&#8217;re carrying history, but you only need to reason about a moving slice of it - is a surprisingly good model for how a lot of interview problems work. Nothing is broken. The confusion comes from trying to track &#8220;<em>everything so far</em>&#8221; when the problem only cares about the best <em>contiguous stretch</em> that satisfies a rule.</p><blockquote><p><strong>Problem</strong></p><p>Given a string, find the length of the longest substring without repeating characters.</p><p><strong>Examples</strong></p><p>Example 1:</p><p>Input: <code>&#8220;abcabcbb&#8221;</code></p><p>Output: 3</p><p>Explanation: The answer is <code>&#8220;abc&#8221;</code>, with the length of 3.</p><p>Example 2:</p><p>Input: <code>&#8220;bbbbb&#8221;</code></p><p>Output: 1</p><p>Explanation: The answer is <code>&#8220;b&#8221;</code>, with the length of 1.</p><p>Example 3:</p><p>Input: <code>&#8220;pwwkew&#8221;</code></p><p>Output: 3</p><p>Explanation: The answer is <code>&#8220;wke&#8221;</code>, with the length of 3.</p><p>Note that the answer must be a substring, <code>&#8220;pwke&#8221;</code> is a subsequence and not a substring.</p><p><strong>Constraints</strong></p><ul><li><p><em>No explicit constraints found in extracted text.</em></p></li></ul></blockquote><p>This is exactly the mindset behind finding the longest substring with no repeating characters: keep a clean, up-to-date &#8220;<em>current window</em>&#8221;, and when a repeat appears, move the start forward intelligently rather than rebuilding from scratch.</p><h1>&#127897;&#65039; Why interviewers like this problem</h1><p>This problem looks simple on the surface: &#8220;<em>scan a string and find the longest stretch without repeats</em>&#8221;. But interviewers aren&#8217;t really testing whether you can write a loop. They&#8217;re testing whether you can manage state under pressure, explain an invariant, and avoid subtle off-by-one errors when the input fights back.</p><p>A strong solution requires you to recognize a classic pattern: a <strong>sliding window</strong> where the &#8220;<em>valid region</em>&#8221; expands and contracts as you scan. That pattern appears everywhere: rate limiting, caching, deduplication, streaming analytics, log processing, and even UI behaviors like &#8220;<em>longest recent sequence without repeating events</em>&#8221;.</p><p>From an interviewer&#8217;s perspective, this question reveals:</p><ul><li><p><strong>Whether you can translate an English requirement into a precise invariant.</strong> What does &#8220;<em>substring</em>&#8221; mean operationally? What counts as &#8220;<em>repeating</em>&#8221;? What does the window represent at every moment?</p></li><li><p><strong>Whether you can optimize with intent.</strong> Many candidates start with a brute force approach and either get stuck or optimize blindly. The best candidates can say: &#8220;<em>This is quadratic because we restart work; we need to avoid re-scanning</em>&#8221;.</p></li><li><p><strong>Whether you can communicate while coding.</strong> The optimal solution is not long, but it&#8217;s easy to get wrong if you can&#8217;t narrate what each pointer means.</p></li><li><p><strong>Whether you handle edge cases calmly.</strong> Empty string, repeated characters early, repeated characters far apart, and strings with mixed character sets all stress the details.</p></li></ul><p>It&#8217;s also a good &#8220;<em>live coding</em>&#8221; prompt because it&#8217;s small enough to finish, but rich enough to show how you think.</p><h1>&#128214; Understanding the problem in plain language</h1><p>You&#8217;re given a string. You want the length of the longest contiguous chunk (a substring) where every character appears at most once inside that chunk.</p><p>&#8220;<em>Contiguous</em>&#8221; is the whole point. You&#8217;re not allowed to skip characters. If the string is <code>"pwwkew"</code>, the characters <code>p-w-k-e</code> exist in order, but not as one continuous slice, so that doesn&#8217;t count.</p><p>So the output is a single integer: the maximum length among all substrings that contain no duplicates.</p><p>There are a few clarifications worth saying out loud in an interview:</p><ul><li><p>We&#8217;re looking for <strong>length</strong>, not the substring itself (though you can often extend the solution to return the substring).</p></li><li><p>Characters are considered repeating <strong>within the substring</strong>, not across the whole string.</p></li><li><p>The best substring can start and end anywhere. It doesn&#8217;t have to align with word boundaries or anything &#8220;<em>semantic</em>&#8221;.</p></li><li><p>We should assume the string can be reasonably large unless told otherwise - large enough that an obviously quadratic solution will time out or be frowned upon.</p></li></ul><p>If constraints aren&#8217;t provided (as in many prompts), it&#8217;s fair to ask: &#8220;<em>Should I assume the string can be up to, say, 100k characters?</em>&#8221; Interviewers will usually say yes, and that immediately pushes you toward an <code>O(n)</code> solution.</p><h1>&#128099; Reasoning through examples</h1><p>Let&#8217;s walk through <code>"abcabcbb"</code> slowly, the way you&#8217;d narrate it on a whiteboard.</p><p>You start at the first character:</p><ul><li><p>Consider <code>"a"</code>: valid, length 1</p></li><li><p>Extend to <code>"ab"</code>: still valid, length 2</p></li><li><p>Extend to <code>"abc"</code>: still valid, length 3</p></li></ul><p>Now you try to extend with the next character, which is <code>"a"</code> again. If you naively keep growing, you&#8217;d have <code>"abca"</code>, which is invalid because <code>a</code> repeats.</p><p>Here&#8217;s the key interview moment: what do you do when you hit a repeat?</p><p>A weaker approach is: &#8220;<em>Start over from the next character</em>&#8221;. That leads to lots of repeated scanning.</p><p>A stronger approach is: &#8220;<em>I already know where the previous </em><code>a</code><em> was. The longest valid substring ending here must start after that previous </em><code>a</code>&#8221;. So instead of restarting from scratch, you slide the start pointer forward to exclude the earlier <code>a</code>.</p><p>So for <code>"abcabcbb"</code>:</p><ul><li><p>When you hit the second <code>a</code> (at index 3), you move the start to index 1 (right after the previous <code>a</code> at index 0).</p></li><li><p>Your window becomes <code>"bca"</code> (indices 1..3), length 3.</p></li><li><p>Then you see <code>b</code> again, slide start to after the previous <code>b</code>, and so on.</p></li></ul><p>You keep a running best: it stays 3.</p><p>Now consider <code>"bbbbb"</code>:</p><ul><li><p>Start with <code>"b"</code>, length 1.</p></li><li><p>Next <code>b</code> repeats immediately. The best valid substring ending here is still length 1, because the window collapses to just <code>"b"</code> each time.</p></li></ul><p>And <code>"pwwkew"</code> is the example where intuition often fails:</p><ul><li><p>You see <code>"p"</code>, then <code>"pw"</code>, then <code>"pww"</code> becomes invalid at the second <code>w</code>.</p></li><li><p>Sliding the start past the first <code>w</code> gives you <code>"w"</code> (not <code>"pw"</code> - because you must keep contiguity).</p></li><li><p>Then you extend: <code>"wk"</code>, <code>"wke"</code>, length 3.</p></li></ul><p>A strong candidate explicitly distinguishes &#8220;<em>contiguous substring</em>&#8221; from &#8220;<em>subsequence</em>&#8221; while narrating this. Interviewers love that, because it shows you&#8217;re reading carefully.</p><h1>&#128207; Constraints and what they tell us</h1><p>When constraints are missing, you have to infer what the interviewer expects. This is a real interview skill: <em>use the shape of the problem</em> to pick an approach, and confirm assumptions verbally.</p><p>There are usually three regimes:</p><ul><li><p>If the string length is tiny (say, under a few thousand), you can get away with <code>O(n&#178;) </code>brute force. But that&#8217;s rarely the intent in a top-tier interview, because it doesn&#8217;t test much beyond nested loops.</p></li><li><p>If the string length is moderate to large (tens of thousands or more), <code>O(n&#178;) </code>becomes risky. A 100k string would be catastrophic with a quadratic approach.</p></li><li><p>If the character set is small and known (e.g., ASCII), you can optimize further using fixed-size arrays. If it&#8217;s general Unicode, you typically use a hash map.</p></li></ul><p>So what should you say out loud?</p><ul><li><p>&#8220;<em>If the string can be large, we should aim for </em><code>O(n)</code>&#8221;.</p></li><li><p>&#8220;<em>We can do that with a sliding window and a map of last-seen positions</em>&#8221;.</p></li><li><p>&#8220;<em>Space is </em><code>O(min(n, alphabet_size))</code><em> because we store at most one entry per distinct character in the window</em>&#8221;.</p></li></ul><p>Interviewers want to see you connect constraints to design choices. Even if the prompt doesn&#8217;t include them, your reasoning should.</p><h1>&#129300; The obvious first idea &#8212; and why it&#8217;s not enough</h1><p>The natural first idea is brute force:</p><ul><li><p>Enumerate every possible start index <code>i</code>.</p></li><li><p>From <code>i</code>, expand <code>j</code> forward until you hit a repeated character.</p></li><li><p>Track the maximum length.</p></li></ul><p>To detect repeats, you keep a set of seen characters as you expand <code>j</code>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fW6h!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2190af07-dc73-4ed2-a5e0-e677238438d2_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fW6h!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2190af07-dc73-4ed2-a5e0-e677238438d2_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!fW6h!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2190af07-dc73-4ed2-a5e0-e677238438d2_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!fW6h!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2190af07-dc73-4ed2-a5e0-e677238438d2_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!fW6h!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2190af07-dc73-4ed2-a5e0-e677238438d2_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fW6h!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2190af07-dc73-4ed2-a5e0-e677238438d2_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2190af07-dc73-4ed2-a5e0-e677238438d2_1536x1024.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;:329512,&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://iam.slys.dev/i/187834348?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2190af07-dc73-4ed2-a5e0-e677238438d2_1536x1024.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_!fW6h!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2190af07-dc73-4ed2-a5e0-e677238438d2_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!fW6h!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2190af07-dc73-4ed2-a5e0-e677238438d2_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!fW6h!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2190af07-dc73-4ed2-a5e0-e677238438d2_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!fW6h!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2190af07-dc73-4ed2-a5e0-e677238438d2_1536x1024.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>This feels good because it matches how humans reason about substrings: &#8220;<em>Try starting here, grow until it breaks</em>&#8221;.</p><p>But the performance is the issue. In the worst case - like a string of all distinct characters - this does a lot of redundant work:</p><ul><li><p>From index 0 you scan almost the whole string.</p></li><li><p>From index 1 you scan almost the whole string again.</p></li><li><p>And so on.</p></li></ul><p>That&#8217;s <code>O(n&#178;)</code> time.</p><p>In interviews, the naive approach is not &#8220;<em>wrong</em>&#8221;. It&#8217;s often a perfectly acceptable starting point. What matters is whether you recognize <em>why</em>it&#8217;s inefficient and can articulate the source of redundancy:</p><ul><li><p>&#8220;<em>We&#8217;re re-scanning the same characters many times</em>&#8221;.</p></li><li><p>&#8220;<em>We need a way to move the left boundary forward without re-checking everything inside</em>&#8221;.</p></li></ul><p>That statement is the bridge to the real solution.</p><h1>&#128161; The key insight</h1><p>The turning point is realizing that you don&#8217;t need to consider all substrings explicitly. You only need to maintain the best valid substring that ends at the current position.</p><p>Think of scanning the string left to right with a moving window <code>[left, right]</code> such that:</p><ul><li><p>The window contains no repeating characters.</p></li><li><p><code>right</code> moves forward one step at a time.</p></li><li><p>When the next character would violate the &#8220;<em>no repeats</em>&#8221; rule, you move <code>left</code> forward just enough to restore validity.</p></li></ul><p>The subtle part - and the part that separates &#8220;<em>good</em>&#8221; from &#8220;<em>strong</em>&#8221; - is how you move <code>left</code>.</p><p>A common but slightly clumsy version is to move <code>left</code> one step at a time, removing characters from a set until the duplicate is gone. That can still be <code>O(n)</code> overall if implemented carefully, but it&#8217;s harder to explain and easier to get wrong live.</p><p>The cleaner version uses <strong>last seen positions</strong>:</p><ul><li><p>Keep a map <code>lastSeen[c] = index</code> for each character <code>c</code>.</p></li><li><p>When you see character <code>c</code> at index <code>right</code>, and <code>c</code> was last seen at index <code>k</code>:</p><ul><li><p>If <code>k &lt; left</code>, it&#8217;s not a problem (the previous <code>c</code> is outside the window).</p></li><li><p>If <code>k &gt;= left</code>, it is a problem, and the new valid window must start at <code>k + 1</code>.</p></li></ul></li></ul><p>So you update: <code>left = max(left, lastSeen[c] + 1)</code>.</p><p>That one line is the heart of the algorithm. It encodes the idea: &#8220;<em>Jump the start forward past the previous occurrence, but never move it backward</em>&#8221;.</p><div><hr></div><h3>&#128161; Did this mental shift click for you?</h3><p>What was your &#8220;<em>aha</em>&#8221; moment while reading this?</p><p>&#128172; Leave a comment - I&#8217;d love to know how you think about Longest Substring Without Repeating Characters.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/thinking-clearly-in-interviews-longest/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/thinking-clearly-in-interviews-longest/comments"><span>Leave a comment</span></a></p><div><hr></div><h1>&#9881;&#65039; The final approach</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5qYK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69503202-d601-4cdc-85ba-8203de14809b_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5qYK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69503202-d601-4cdc-85ba-8203de14809b_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!5qYK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69503202-d601-4cdc-85ba-8203de14809b_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!5qYK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69503202-d601-4cdc-85ba-8203de14809b_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!5qYK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69503202-d601-4cdc-85ba-8203de14809b_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5qYK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69503202-d601-4cdc-85ba-8203de14809b_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/69503202-d601-4cdc-85ba-8203de14809b_1536x1024.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;:1297441,&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://iam.slys.dev/i/187834348?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69503202-d601-4cdc-85ba-8203de14809b_1536x1024.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_!5qYK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69503202-d601-4cdc-85ba-8203de14809b_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!5qYK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69503202-d601-4cdc-85ba-8203de14809b_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!5qYK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69503202-d601-4cdc-85ba-8203de14809b_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!5qYK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69503202-d601-4cdc-85ba-8203de14809b_1536x1024.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>Narratively, here&#8217;s what you do:</p><p>You walk <code>right</code> from the start of the string to the end. You maintain:</p><ul><li><p><code>left</code>: the start index of the current window (the best candidate window ending at <code>right</code> that has no repeats).</p></li><li><p><code>lastSeen</code>: a mapping from character to the most recent index where it appeared.</p></li><li><p><code>best</code>: the maximum window length seen so far.</p></li></ul><p>At each character <code>c = s[right]</code>:</p><ol><li><p>If <code>c</code> has been seen before at index <code>k</code> and <code>k</code> is inside the current window (<code>k &gt;= left</code>), then the window is about to contain a duplicate. Slide <code>left</code> to <code>k + 1</code>.</p></li><li><p>Update <code>lastSeen[c] = right</code>.</p></li><li><p>Compute current window length as <code>right - left + 1</code>, update <code>best</code>.</p></li></ol><p>Correctness comes from maintaining an invariant:</p><ul><li><p>After processing each position <code>right</code>, the substring <code>s[left..right]</code> contains no repeated characters.</p></li><li><p>Among all valid substrings that end at <code>right</code>, <code>s[left..right]</code> is the longest (because <code>left</code> is as far left as possible while staying valid).</p></li></ul><p>Edge cases are naturally handled:</p><ul><li><p>Empty string: <code>best</code> stays 0.</p></li><li><p>All same character: window repeatedly collapses to length 1.</p></li><li><p>Repeats far apart: <code>left</code> jumps forward, never backward.</p></li></ul><p>This is exactly what you want in an interview solution: simple moving parts, one clear invariant, and no backtracking.</p><h1>&#9201;&#65039; Performance under interview constraints</h1><p>Time complexity is <code>O(n)</code> because each index is processed once, and <code>left</code> only moves forward. The map operations are expected <code>O(1)</code> on average.</p><p>Space complexity is <code>O(k)</code>, where <code>k</code> is the number of distinct characters you track. In the worst case it can be <code>O(n)</code> if the string has all unique characters, but practically it&#8217;s bounded by the character set size.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7CKu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3150f73-6cae-45b2-be9b-775ec951bf1c_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7CKu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3150f73-6cae-45b2-be9b-775ec951bf1c_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!7CKu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3150f73-6cae-45b2-be9b-775ec951bf1c_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!7CKu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3150f73-6cae-45b2-be9b-775ec951bf1c_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!7CKu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3150f73-6cae-45b2-be9b-775ec951bf1c_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7CKu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3150f73-6cae-45b2-be9b-775ec951bf1c_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f3150f73-6cae-45b2-be9b-775ec951bf1c_1536x1024.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;:1213304,&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://iam.slys.dev/i/187834348?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3150f73-6cae-45b2-be9b-775ec951bf1c_1536x1024.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_!7CKu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3150f73-6cae-45b2-be9b-775ec951bf1c_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!7CKu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3150f73-6cae-45b2-be9b-775ec951bf1c_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!7CKu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3150f73-6cae-45b2-be9b-775ec951bf1c_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!7CKu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3150f73-6cae-45b2-be9b-775ec951bf1c_1536x1024.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>How do you justify this verbally?</p><ul><li><p>&#8220;<code>right</code><em> moves from 0 to n-1 exactly once</em>&#8221;.</p></li><li><p>&#8220;<code>left</code><em> only ever increases, at most n times</em>&#8221;.</p></li><li><p>&#8220;<em>Each character&#8217;s last-seen index is updated once per occurrence; we do </em><code>O(1)</code><em> expected work per character</em>&#8221;.</p></li></ul><p>If the interviewer presses on worst-case hash map behavior, a calm response is:</p><ul><li><p>&#8220;<em>In practice, standard library hash maps give good average behavior. If the character set is known to be small (like ASCII), we can replace the map with a fixed array to guarantee </em><code>O(1)</code><em> worst-case lookups</em>.&#8221;</p></li></ul><p>That&#8217;s the kind of trade-off talk that reads as engineering maturity.</p><div><hr></div><h3>&#9888;&#65039; Know someone preparing for interviews?</h3><p>Send this to them - it might save them from an avoidable mistake.</p><p>&#128257; Share this post with a friend grinding interview prep.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/thinking-clearly-in-interviews-longest?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/thinking-clearly-in-interviews-longest?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h1>&#128187; Implementation</h1><p>In interviews, implementation is secondary to clarity of thought - but the code still has to be correct, readable, and robust.</p><p>The most common implementation bugs here are:</p><ul><li><p>Updating <code>left</code> incorrectly (moving it backward, or not taking <code>max</code>).</p></li><li><p>Miscomputing window length (<code>right - left</code> instead of <code>+ 1</code>).</p></li><li><p>Confusing &#8220;<em>seen ever</em>&#8221; with &#8220;<em>seen in current window</em>&#8221;.</p></li></ul><p>So when you code, keep the structure aligned with the invariant:</p><ul><li><p>Read <code>right</code>, compute new <code>left</code> if needed, then update <code>lastSeen</code>, then update <code>best</code>.</p></li></ul><h2>&#128013; Python implementation</h2><p>Python is ideal for demonstrating the idea cleanly. Use a dictionary from character to last index.</p><pre><code>from typing import Dict

def length_of_longest_substring(s: str) -&gt; int:
    last_seen: Dict[str, int] = {}
    left = 0
    best = 0

    for right, ch in enumerate(s):
        if ch in last_seen and last_seen[ch] &gt;= left:
            # ch repeats inside the current window; jump left past the previous ch
            left = last_seen[ch] + 1

        last_seen[ch] = right
        best = max(best, right - left + 1)

    return best</code></pre><p>A good way to narrate this while typing:</p><ul><li><p>&#8220;<code>left</code><em> is the start of my current unique window</em>&#8221;.</p></li><li><p>&#8220;<code>last_seen</code><em> tells me where I last saw each character</em>&#8221;.</p></li><li><p>&#8220;<em>When I see a repeat inside the window, I jump </em><code>left</code><em> forward</em>&#8221;.</p></li><li><p>&#8220;<em>Then I compute the window length and update the best</em>&#8221;.</p></li></ul><p>If asked to return the substring itself, you can store best window boundaries (<code>best_left</code>, <code>best_right</code>) when updating <code>best</code>.</p><h2>&#129408; Rust implementation</h2><p>Rust adds one twist: <code>String</code> is UTF-8, and indexing by position is not allowed because indices are byte offsets, not character offsets. For this problem, we can iterate over <code>chars()</code> and treat &#8220;<em>length</em>&#8221; as the number of Unicode scalar values processed. That&#8217;s typically acceptable unless the interviewer explicitly wants byte-based behavior.</p><p>We&#8217;ll keep a <code>HashMap&lt;char, usize&gt;</code> storing the last seen <em>character index</em> (not byte index), maintained via <code>enumerate()</code> over <code>chars()</code>.</p><pre><code>use std::collections::HashMap;

pub fn length_of_longest_substring(s: &amp;str) -&gt; usize {
    let mut last_seen: HashMap&lt;char, usize&gt; = HashMap::new();
    let mut left: usize = 0;
    let mut best: usize = 0;

    for (right, ch) in s.chars().enumerate() {
        if let Some(&amp;prev) = last_seen.get(&amp;ch) {
            if prev &gt;= left {
                left = prev + 1;
            }
        }

        last_seen.insert(ch, right);
        let window_len = right - left + 1;
        if window_len &gt; best {
            best = window_len;
        }
    }

    best
}</code></pre><p>What to say out loud in a Rust interview:</p><ul><li><p>&#8220;<em>I&#8217;m iterating by </em><code>chars()</code><em>, so my indices are character positions</em>&#8221;.</p></li><li><p>&#8220;<em>Rust strings are UTF-8, so I avoid direct indexing</em>&#8221;.</p></li><li><p>&#8220;<em>Ownership is simple here: I borrow </em><code>&amp;str</code><em>, store </em><code>char</code><em> keys in the map, and store integer indices</em>&#8221;.</p></li></ul><p>If the interviewer wants maximum performance under an ASCII assumption, you can mention an optimization:</p><ul><li><p>Replace the <code>HashMap</code> with a <code>[i32; 256]</code> or <code>[usize; 128]</code> array (initialized to sentinel values), which avoids hashing entirely.</p></li></ul><h2>&#128995; Scala implementation</h2><p>In Scala, a straightforward approach uses a mutable map and integer indices. Scala&#8217;s <code>String</code> indexing uses UTF-16 code units, which is usually what interviewers assume when they say &#8220;<em>string</em>&#8221; in a typical backend context.</p><pre><code>import scala.collection.mutable

object LongestUniqueSubstring {
  def lengthOfLongestSubstring(s: String): Int = {
    val lastSeen = mutable.Map[Char, Int]()
    var left = 0
    var best = 0

    for (right &lt;- s.indices) {
      val ch = s.charAt(right)
      lastSeen.get(ch) match {
        case Some(prev) if prev &gt;= left =&gt;
          left = prev + 1
        case _ =&gt;
          // no action
      }

      lastSeen.update(ch, right)
      val windowLen = right - left + 1
      if (windowLen &gt; best) best = windowLen
    }

    best
  }
}</code></pre><p>Design choices worth briefly mentioning:</p><ul><li><p>Using a mutable map keeps the code close to the sliding window idea and avoids copying.</p></li><li><p>You could implement a purely functional version, but it tends to obscure the invariant under interview time constraints.</p></li><li><p>If you know the input is limited to ASCII, an array of size 128 or 256 is faster and simpler than hashing.</p></li></ul><h1>&#9888;&#65039; Common interview mistakes</h1><p>The most frequent mistake is forgetting that repeats only matter <strong>inside the current window</strong>, not &#8220;<em>ever</em>&#8221;. Candidates will sometimes do:</p><ul><li><p>&#8220;<em>If I&#8217;ve seen this character before, move left</em>&#8221;,</p></li></ul><p>&#8230;but without checking whether the previous occurrence is still inside the active window. That breaks cases like <code>"abba"</code>.</p><p>In <code>"abba"</code>, when you reach the last <code>'a'</code>, the previous <code>'a'</code> is at index 0, but the window might already start at index 2. If you move <code>left</code>back to 1, you violate the sliding window invariant. That&#8217;s why the condition <code>lastSeen[ch] &gt;= left</code> is non-negotiable.</p><p>Another classic mistake is updating <code>left</code> without taking <code>max</code> (in variants that compute <code>left = lastSeen[ch] + 1</code> unconditionally). If you don&#8217;t guard against moving <code>left</code> backward, you can &#8220;<em>re-include</em>&#8221; duplicates and produce inflated lengths.</p><p>Off-by-one errors show up constantly:</p><ul><li><p>Window length is <code>right - left + 1</code>.</p></li><li><p>If you set <code>left = prev</code> instead of <code>prev + 1</code>, you haven&#8217;t actually removed the duplicate.</p></li></ul><p>Finally, candidates sometimes choose a &#8220;<em>remove from set one-by-one</em>&#8221; approach and then get stuck in implementation details - forgetting to remove correctly, or accidentally making it quadratic by repeatedly re-scanning. It can be done correctly, but the last-seen-index jump is typically clearer and more robust under time pressure.</p><h1>&#128269; Edge cases interviewers love to ask about</h1><p>Interviewers often probe whether you truly understand the invariant by throwing targeted cases at you.</p><p>An empty string should return 0. This sounds trivial, but it tests whether your initialization is thoughtful or accidental. In many languages, it also tests whether you handle loops that never execute.</p><p>Strings with immediate repeats like <code>"aa"</code> or <code>"abba"</code> test the correctness of the <code>left</code> update logic. <code>"abba"</code> in particular is a favorite because it punishes &#8220;move left to lastSeen + 1 without checking window boundaries.&#8221;</p><p>Strings with spaces and punctuation (like <code>"a b!a"</code>) test whether you treat &#8220;<em>character</em>&#8221; broadly, not just letters. In most interpretations, every distinct character counts, including whitespace.</p><p>Very long strings test whether your method is truly linear. If you propose brute force, a good interviewer will ask, &#8220;<em>What happens at 100k characters</em>?&#8221; The best response isn&#8217;t panic - it&#8217;s a calm complexity argument and a shift to sliding window.</p><p>Sometimes you&#8217;ll get a follow-up: &#8220;<em>Can you return the substring, not just its length?</em>&#8221; If you wrote the window logic correctly, this is easy: track the best window boundaries whenever you update <code>best</code>. It&#8217;s also an opportunity to show you understand what your variables mean.</p><p>Another follow-up: &#8220;<em>What if the input is streaming and you can&#8217;t store the entire string?</em>&#8221; This pushes you to reason about what state you actually need (last seen positions and current index) and how you might handle eviction. It&#8217;s not always solvable perfectly without storing something, but the discussion itself reveals engineering judgment.</p><h1>&#127891; What this problem trains you to do</h1><p>This problem trains a specific kind of interview competence: maintaining a small, precise state that summarizes &#8220;<em>everything that matters so far</em>&#8221;.</p><p>You&#8217;re not memorizing a trick. You&#8217;re practicing how to:</p><ul><li><p>Define an invariant (&#8220;<em>this window is always valid</em>&#8221;).</p></li><li><p>Update state incrementally without recomputation.</p></li><li><p>Use auxiliary memory (a map) to convert &#8220;<em>searching backward</em>&#8221; into <code>O(1)</code> jumps.</p></li><li><p>Explain correctness as a story: &#8220;<em>When I see a repeat, the only way to restore validity is to move the left boundary past the previous occurrence</em>&#8221;.</p></li></ul><p>Once you internalize that, you start seeing the same shape in many other problems:</p><ul><li><p>Longest or shortest subarray satisfying a constraint.</p></li><li><p>Counting distinct elements in a window.</p></li><li><p>Minimum window containing a set of required characters.</p></li><li><p>Two-pointer techniques on arrays (sum constraints, unique constraints).</p></li><li><p>Streaming de-duplication with &#8220;<em>most recent occurrence</em>&#8221; metadata.</p></li></ul><p>More importantly, it teaches you how to stay calm when requirements are deceptively small. The interviewer isn&#8217;t asking for heroics. They&#8217;re asking whether you can be precise.</p><h1>&#127793; Closing thoughts</h1><p>The core skill here is resisting the urge to &#8220;<em>restart</em>&#8221; whenever you hit a problem. In live coding, restarts show up as nested loops, rescans, and tangled conditionals - symptoms of not having a stable invariant.</p><p>A sliding window solution is what it looks like when you trust your state: you keep a clear boundary around what&#8217;s currently valid, and when reality violates your expectation, you adjust the boundary instead of throwing away your work.</p><p>If you practice explaining this problem out loud - what <code>left</code> means, why <code>left</code> never moves backward, and how <code>lastSeen</code> prevents rescanning - you&#8217;ll notice a shift. You stop sounding like someone searching for a solution and start sounding like someone executing a plan.</p><div><hr></div><h3>&#129504; If you want to build real interview intuition&#8230;</h3><p>I write deep, calm breakdowns of classic problems - without tricks or memorization.</p><p>&#128236; Subscribe to get the next one directly.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Why is my LoadBalancer still <pending>?]]></title><description><![CDATA[How Cilium's LB IPAM and L2 Announcements turn a pending LoadBalancer Service into a reachable VIP on your LAN.]]></description><link>https://iam.slys.dev/p/why-is-my-loadbalancer-still-pending</link><guid isPermaLink="false">https://iam.slys.dev/p/why-is-my-loadbalancer-still-pending</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 23 Mar 2026 21:28:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5sE8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5sE8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5sE8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic 424w, https://substackcdn.com/image/fetch/$s_!5sE8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic 848w, https://substackcdn.com/image/fetch/$s_!5sE8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic 1272w, https://substackcdn.com/image/fetch/$s_!5sE8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5sE8!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:106876,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/190302892?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5sE8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic 424w, https://substackcdn.com/image/fetch/$s_!5sE8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic 848w, https://substackcdn.com/image/fetch/$s_!5sE8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic 1272w, https://substackcdn.com/image/fetch/$s_!5sE8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae29c03-ff4a-4536-af69-ae53fec9c271_1920x1080.heic 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>&#9888;&#65039; <strong>Before we dive in &#8212; highly worth your attention</strong></p><div class="embedded-publication-wrap" data-attrs="{&quot;id&quot;:3343898,&quot;embedding_publication_id&quot;:null,&quot;name&quot;:&quot;Unpromptable.&quot;,&quot;logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!102n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd215e8b4-a9c5-49a0-a2ef-c911251bfff0_500x500.png&quot;,&quot;base_url&quot;:&quot;https://unpromptable.substack.com&quot;,&quot;hero_text&quot;:&quot;Helping founders, creatives, and professionals become irreplaceable in the Age of AI || Subscribe: forever free 2x deep dives, community events, and free Active Prompt Vault (+ more AI &amp; marketing tools).&quot;,&quot;author_name&quot;:&quot;James Presbitero&quot;,&quot;show_subscribe&quot;:true,&quot;logo_bg_color&quot;:&quot;#ecfdf5&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="EmbeddedPublicationToDOMWithSubscribe"><div class="embedded-publication show-subscribe"><a class="embedded-publication-link-part" native="true" href="https://unpromptable.substack.com?utm_source=substack&amp;utm_campaign=publication_embed&amp;utm_medium=web"><img class="embedded-publication-logo" src="https://substackcdn.com/image/fetch/$s_!102n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd215e8b4-a9c5-49a0-a2ef-c911251bfff0_500x500.png" width="56" height="56" style="background-color: rgb(236, 253, 245);"><span class="embedded-publication-name">Unpromptable.</span><div class="embedded-publication-hero-text">Helping founders, creatives, and professionals become irreplaceable in the Age of AI || Subscribe: forever free 2x deep dives, community events, and free Active Prompt Vault (+ more AI &amp; marketing tools).</div><div class="embedded-publication-author-name">By James Presbitero</div></a><form class="embedded-publication-subscribe" method="GET" action="https://unpromptable.substack.com/subscribe?"><input type="hidden" name="source" value="publication-embed"><input type="hidden" name="autoSubmit" value="true"><input type="email" class="email-input" name="email" placeholder="Type your email..."><input type="submit" class="button primary" value="Subscribe"></form></div></div><p><span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;James Presbitero&quot;,&quot;id&quot;:112381382,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!Zwf9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0763a29d-cc76-47d0-b18a-5567aee11ade_572x572.png&quot;,&quot;uuid&quot;:&quot;83071405-edc2-4c60-b486-63c9500decf4&quot;}" data-component-name="MentionToDOM"></span> writes about <strong>startups</strong>, <strong>leadership</strong>, and the realities of <strong>building companies</strong> from the inside. His posts combine <strong>founder experience with practical reflections</strong> on decision-making, growth, and navigating uncertainty - the kind of thinking that&#8217;s useful if you&#8217;re actually building something.</p><div><hr></div><p>You deploy the standard Kubernetes pattern: an app Deployment, plus a Service of <code>type: LoadBalancer</code>. You run <code>kubectl get svc</code> and expect an external IP you can curl from your laptop. Instead:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">$ kubectl get svc -n demo
NAME          TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
demo-web      LoadBalancer   10.96.120.200    &lt;pending&gt;     80:31234/TCP   20s</code></pre></div><p>On cloud-managed clusters, this works because a cloud controller provisions a managed load balancer and writes the resulting IP into the Service's <code>.status.loadBalancer</code> field. Kubernetes delegates this provisioning to an external implementation - it doesn't provide one itself.</p><p>On bare metal, that cloud integration doesn't exist. Nothing chooses an IP from your LAN, and nothing announces that IP on your local network so traffic can find it. That's why the Service stays pending: <strong>no component has taken responsibility for the Service's external presence.</strong></p><p>This article builds that missing responsibility using Cilium's documented bare-metal path: <strong>LB IPAM<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></strong> allocates an IP for your <code>LoadBalancer</code> Service, and <strong>L2 Announcements</strong><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> makes that IP reachable on your local network by responding to ARP and NDP queries. These are distinct problems with distinct mechanisms - understanding the separation is the whole game.</p><h1>What Cilium is (and what it isn't)</h1><p>Kubernetes networking has two separate layers that are easy to conflate.</p><p><strong>Pod networking</strong>: when a pod starts, something must create its network interface, assign an IP, and wire up routes. Kubernetes delegates this to the Container Network Interface (CNI)<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> model - a runtime calls a CNI plugin to configure container networking. The CNI boundary is "<em>getting pods on the network</em>", nothing more.</p><p><strong>Service exposure</strong>: kube-proxy (or a replacement) takes a virtual ClusterIP/port and forwards traffic to backend pods. <code>LoadBalancer</code> Services extend this with an external IP - but Kubernetes explicitly leaves the external load balancer implementation to outside components. On bare metal, that means the cluster can be internally healthy while having no way to make a Service real on your LAN.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!crti!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f714d91-05a1-4b9e-bb00-835c56546fa7_1600x900.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!crti!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f714d91-05a1-4b9e-bb00-835c56546fa7_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!crti!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f714d91-05a1-4b9e-bb00-835c56546fa7_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!crti!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f714d91-05a1-4b9e-bb00-835c56546fa7_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!crti!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f714d91-05a1-4b9e-bb00-835c56546fa7_1600x900.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!crti!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f714d91-05a1-4b9e-bb00-835c56546fa7_1600x900.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5f714d91-05a1-4b9e-bb00-835c56546fa7_1600x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:118103,&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://iam.slys.dev/i/190302892?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f714d91-05a1-4b9e-bb00-835c56546fa7_1600x900.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!crti!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f714d91-05a1-4b9e-bb00-835c56546fa7_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!crti!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f714d91-05a1-4b9e-bb00-835c56546fa7_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!crti!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f714d91-05a1-4b9e-bb00-835c56546fa7_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!crti!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f714d91-05a1-4b9e-bb00-835c56546fa7_1600x900.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>Cilium spans both layers. As a CNI plugin it handles pod networking. It also implements service load balancing in-kernel using its own datapath, and can run in <strong>kube-proxy replacement mode</strong> - a prerequisite for L2 Announcements. For bare metal specifically, Cilium provides two building blocks:</p><ul><li><p><strong>LB IPAM</strong>: allocates IPs from a configured pool and assigns them to <code>LoadBalancer</code> Services. It is always enabled but dormant until you create at least one pool.</p></li><li><p><strong>L2 Announcements</strong>: responds to ARP (IPv4) and NDP (IPv6) queries for Service VIPs, making them reachable on the local network. Only one node responds for a given Service IP at a time, then load-balances to pods.</p></li></ul><blockquote><p>IP allocation (LB IPAM) and IP reachability on the LAN (L2 Announcements) are separate steps. Debug them separately.</p></blockquote><h1>Installing and Validating Cilium</h1><h2>Installing the Cilium CLI</h2><p>The Cilium CLI<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a> is your workstation-side tool: you run it from a machine with kubeconfig access. Its job is installing Cilium, inspecting a Cilium installation, and enabling or disabling features like ClusterMesh or Hubble. It is not "<em>Cilium running</em>" - it's a client. The actual datapath work happens inside the cluster via the agent DaemonSet and operator Deployment.</p><p>The documented install method downloads the latest stable CLI from the CLI repository's <code>stable.txt</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi

curl -L --fail --remote-name-all \
  https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}

sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}</code></pre></div><p>Verify it's present and connects to your environment:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">which cilium
cilium version</code></pre></div><p>This distinction between CLI and cluster components matters later: when you edit the <code>cilium-config</code> ConfigMap, you'll need to restart pods to pick up the change. The CLI doesn't do that automatically.</p><h2>Installing Cilium into the cluster</h2><p>From a "<em>bring your own CNI</em>" bare-metal cluster, the CLI-driven install is the shortest supported path:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">cilium install</code></pre></div><p>The installer attempts to pick appropriate defaults for your distribution and stores state as CRDs. If you need strict reproducibility, pin a version with <code>--version 1.19.1</code>. For this walkthrough we rely on validation rather than pinned versions.</p><p>After installation, wait for readiness:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">cilium status --wait
   /&#175;&#175;\
/&#175;&#175;\__/&#175;&#175;\    Cilium:         OK
\__/&#175;&#175;\__/    Operator:       OK
/&#175;&#175;\__/&#175;&#175;\    Hubble:         disabled
\__/&#175;&#175;\__/    ClusterMesh:    disabled
   \__/

DaemonSet         cilium             Desired: 2, Ready: 2/2, Available: 2/2
Deployment        cilium-operator    Desired: 2, Ready: 2/2, Available: 2/2
Containers:       cilium-operator    Running: 2
                  cilium             Running: 2
Image versions    cilium             quay.io/cilium/cilium:v1.9.5: 2
                  cilium-operator    quay.io/cilium/operator-generic:v1.9.5: 2</code></pre></div><p>This reports both the DaemonSet (agent) and Deployment (operator) reaching desired/ready counts. You can confirm with Kubernetes primitives in parallel:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl -n kube-system get pods -l k8s-app=cilium
kubectl -n kube-system get deploy cilium-operator</code></pre></div><p>One prerequisite to carry forward: L2 Announcements requires <strong>kube-proxy replacement mode</strong><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a> to be enabled. At this stage you don't need to configure anything - just keep it in mind before expecting LoadBalancer VIPs to work externally.</p><h1>Reproducing the pending LoadBalancer and fixing IP allocation</h1><h2>Creating a LoadBalancer service (it will be Pending)</h2><p>To understand what we're fixing, create the failure deliberately. Deploy a minimal HTTP server and expose it as a <code>LoadBalancer</code><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a> Service:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl create namespace demo
kubectl -n demo create deployment demo-web --image=nginx --port=80
kubectl -n demo expose deployment demo-web --type=LoadBalancer --port=80</code></pre></div><p>Check the Service:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl -n demo get svc</code></pre></div><p>On a bare-metal cluster with no external load balancer integration, <code>EXTERNAL-IP</code> shows <code>&lt;pending&gt;</code>. Kubernetes treats <code>type: LoadBalancer</code> as an abstraction that requires an external implementation to allocate an external IP and write it into <code>.status.loadBalancer</code>.</p><p>Cilium's LB IPAM documentation is explicit about what <code>&lt;pending&gt;</code> means: no LB IPs have been assigned. It expresses this state through Service status conditions - for example <code>io.cilium/lb-ipam-request-satisfied</code> with reason <code>no_pool</code> when no matching pool exists.</p><p>The important checkpoint here: <strong>nothing is wrong with your Deployment</strong>. Pods may be healthy, ClusterIP traffic may work, NodePort may work. What's absent:</p><ul><li><p>No IP allocation policy for LoadBalancer Services (no pool exists or matches)</p></li><li><p>No L2 reachability mechanism (L2 Announcements is not enabled; nothing answers ARP for the VIP)</p></li></ul><p>Fix these in order: allocate an IP first, then make it reachable.</p><div><hr></div><p><strong>Which tools are </strong><em><strong>you</strong></em><strong> using?</strong></p><p>Have you tried Nginx, Envoy, or maybe you&#8217;re wrestling with AWS&#8217;s ELB quirks? I&#8217;d love to hear your thoughts, questions, or stories from the trenches.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/why-is-my-loadbalancer-still-pending/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/why-is-my-loadbalancer-still-pending/comments"><span>Leave a comment</span></a></p><div><hr></div><h2>Configuring LoadBalancer IP pool (LB-IPAM)</h2><p>LB IPAM answers: "<em>Which IP addresses are allowed to be assigned to LoadBalancer Services in this cluster?</em>" It is designed for environments where cloud facilities are absent, and is always enabled but dormant until the first pool exists.</p><p>The CRD for pools is <code>CiliumLoadBalancerIPPool</code> (<code>apiVersion: cilium.io/v2</code>). The <code>spec.blocks</code> field defines allocatable IP space, either as CIDRs or as explicit start/stop ranges.</p><p>A practical bare-metal pattern: carve out a dedicated sub-range in your LAN that your DHCP server doesn't touch, reachable within the same L2 domain as your nodes. The numbers depend on your LAN; below is an example using a small "<em>VIP slice</em>" inside <code>192.168.111.0/24</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;yaml&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-yaml">apiVersion: "cilium.io/v2"
kind: CiliumLoadBalancerIPPool
metadata:
  name: lan-vips
spec:
  blocks:
    - start: "192.168.111.200"
      stop:  "192.168.111.250"</code></pre></div><p>Apply it and verify capacity:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl apply -f lan-vips.yaml
kubectl get ippools
kubectl describe ippools/lan-vips</code></pre></div><p><code>kubectl get ippools</code> shows <code>DISABLED</code>, <code>CONFLICTING</code>, and <code>IPS AVAILABLE</code>. If you accidentally create overlapping pools, LB IPAM marks the later pool as conflicting and stops allocating from it.</p><p>Once a matching pool exists, LB IPAM assigns an IP to the pending Service. Check:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl -n demo get svc
kubectl -n demo get svc/demo-web -o jsonpath='{.status.conditions}'
echo</code></pre></div><p>At this point <strong>allocation is solved</strong>. Reachability is not. Your network has no reason to send traffic for that VIP to any node - nothing is answering ARP for it yet.</p><p><strong>Trade-off worth naming here</strong>: L2 Announcements is the simpler bare-metal path - no BGP router required, no additional configuration on your upstream network. The cost is scope: it only works within a single L2 domain. If your nodes span multiple VLANs or subnets, L2 Announcements won't bridge them. In that case, Cilium's BGP control plane<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a> is the right approach instead. Everything that follows assumes a flat L2 network.</p><h1>Enabling L2 Announcements for bare metal</h1><h3>Enabling L2 Announcements (ConfigMap method)</h3><p>L2 Announcements makes Service VIPs reachable on the local network by responding to ARP (IPv4) or NDP (IPv6) queries. Because these VIPs aren't literally configured as addresses on any NIC, one elected node replies with its MAC and acts as the north/south entry point for that Service.</p><p>Two prerequisites:</p><ul><li><p><strong>kube-proxy replacement must be enabled</strong></p></li><li><p>Network interfaces you intend to announce on must be part of the devices Cilium uses for service handling (relevant if you've set device filtering explicitly)</p></li></ul><p>Enable L2 Announcements in the <code>cilium-config</code> ConfigMap:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl edit configmap -n kube-system cilium-config</code></pre></div><p>Add (or confirm) under <code>data:</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;yaml&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-yaml">enable-l2-announcements: "true"</code></pre></div><p>Changing configuration via ConfigMap requires a pod restart to take effect. Complete the RBAC step below first, then restart both together.</p><p><strong>RBAC</strong>: L2 Announcements uses Kubernetes leader election based on Lease<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a> objects (<code>coordination.k8s.io</code> API group). Without the right permissions, the agent cannot create or update the Lease objects it needs, and the feature fails even with the ConfigMap key set. Cilium's own troubleshooting documentation calls this out directly: if you see "<em>forbidden</em>" errors against <code>leases.coordination.k8s.io</code>, you must update the <code>cilium</code> ClusterRole.</p><p>Edit the ClusterRole:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl edit clusterrole cilium</code></pre></div><p>Add this rule block under <code>rules:</code> if it's not already present:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;yaml&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-yaml">- apiGroups:
  - coordination.k8s.io
  resources:
  - leases
  verbs:
  - create
  - get
  - update
  - list
  - delete</code></pre></div><p>This RBAC change is not optional for manual enablement. Lease-based leader election is fundamental to how L2 Announcements works - each selected Service maps to a Lease, and the Lease holder is the sole responder to ARP/NDP for that Service's VIP.</p><h2>Restarting Cilium and verifying L2 is active</h2><p>Restart the DaemonSet to pick up both changes:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl -n kube-system rollout restart ds/cilium
kubectl -n kube-system rollout status ds/cilium</code></pre></div><p>Verify in three layers.</p><p><strong>Internal configuration</strong> - confirm the feature flags are active inside a running agent pod:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl -n kube-system exec ds/cilium -- cilium-dbg config --all | grep EnableL2Announcements
kubectl -n kube-system exec ds/cilium -- cilium-dbg config --all | grep KubeProxyReplacement</code></pre></div><p><strong>Overall health</strong>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">cilium status --wait
    /&#175;&#175;\
 /&#175;&#175;\__/&#175;&#175;\    Cilium:         OK
 \__/&#175;&#175;\__/    Operator:       OK
 /&#175;&#175;\__/&#175;&#175;\    Hubble:         OK
 \__/&#175;&#175;\__/    ClusterMesh:    disabled
    \__/

Deployment        cilium-operator    Desired: 2, Ready: 2/2, Available: 2/2
DaemonSet         cilium             Desired: 2, Ready: 2/2, Available: 2/2
Containers:       cilium-operator    Running: 2
                  cilium             Running: 2
Cluster Pods:     5/5 managed by Cilium</code></pre></div><p><strong>Service state</strong> - confirm IP allocation is still satisfied after the restart:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl -n demo get svc demo-web
kubectl -n demo get svc demo-web -o jsonpath='{.status.conditions}'
echo</code></pre></div><p>The agent now participates in Service-specific leader election and can respond to ARP/NDP for selected VIPs. But nothing is announced yet: <strong>L2 Announcements won't announce any VIP until you define at least one </strong><code>CiliumL2AnnouncementPolicy</code>.</p><h2>Creating a CiliumL2AnnouncementPolicy</h2><p>A <code>CiliumL2AnnouncementPolicy</code> is the selection logic that turns "<em>L2 Announcements is enabled</em>" into "<em>this particular Service VIP is announced on this particular interface by one eligible node</em>".</p><p>Current stable API:</p><ul><li><p><code>apiVersion: cilium.io/v2alpha1</code></p></li><li><p><code>kind: CiliumL2AnnouncementPolicy</code></p></li></ul><p>A minimal policy for bare metal:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;yaml&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-yaml">apiVersion: "cilium.io/v2alpha1"
kind: CiliumL2AnnouncementPolicy
metadata:
  name: demo-l2
spec:
  serviceSelector:
    matchLabels:
      app: demo-web
  nodeSelector:
    matchExpressions:
      - key: node-role.kubernetes.io/control-plane
        operator: DoesNotExist
  interfaces:
    - ^eth[0-9]+
  loadBalancerIPs: true</code></pre></div><p>A few things about this policy worth understanding:</p><ul><li><p><code>serviceSelector</code> uses a standard label selector. Cilium also supports special keys like <code>io.kubernetes.service.namespace</code> and <code>io.kubernetes.service.name</code> to match by namespace or name without adding labels to Services.</p></li><li><p><code>nodeSelector</code> here excludes control-plane nodes - a reasonable default for bare-metal clusters where you don't want ingress traffic landing on the control plane.</p></li><li><p><code>interfaces</code> takes Go regex patterns. <code>^eth[0-9]+</code> matches <code>eth0</code>, <code>eth1</code>, and so on. Interface patterns only take effect on interfaces Cilium already considers for service handling - verify with <code>cilium-dbg shell -- db/show devices</code>.</p></li><li><p><code>loadBalancerIPs: true</code> is required. Both <code>externalIPs</code> and <code>loadBalancerIPs</code> default to false; at least one must be explicitly enabled or no VIPs are announced.</p></li></ul><p>Apply the policy and label the Service so it matches:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl -n demo label svc demo-web app=demo-web --overwrite
kubectl apply -f demo-l2-policy.yaml</code></pre></div><p>One matching detail that often goes unnoticed: a Service must have <code>loadBalancerClass</code> unset or set to <code>io.cilium/l2-announcer</code> to be eligible for announcement. If <code>loadBalancerClass</code> is set to something else, the policy won't select it even if every other selector matches.</p><p>Once the policy exists and matches, Cilium creates a per-Service Lease and begins announcing the VIP from the current lease-holder node.</p><h1>Using specific VIPs and understanding the traffic path</h1><h2>Assigning a specific IP to a Service</h2><p>Once LB IPAM has a pool, you can either accept any free VIP or request a specific one. Use a specific IP when the address becomes part of an external contract - a DNS A record, a NAT rule on your router, an upstream allowlist - because those external references need to stay stable across Service restarts.</p><p>The Cilium-supported annotation for this is <code>lbipam.cilium.io/ips</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl -n demo annotate svc demo-web lbipam.cilium.io/ips="192.168.111.11" --overwrite</code></pre></div><p>Or in manifest form:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;yaml&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: demo-web
  namespace: demo
  annotations:
    "lbipam.cilium.io/ips": "192.168.111.11"
spec:
  type: LoadBalancer
  selector:
    app: demo-web
  ports:
    - port: 80
      targetPort: 80</code></pre></div><p>The legacy <code>.spec.loadBalancerIP</code> field was deprecated in Kubernetes v1.24 and its behavior varies across implementations. Prefer the annotation.</p><p>LB IPAM validates two things: the requested IP must fall within an existing pool, and the pool's <code>serviceSelector</code> must match the Service. A request that doesn't satisfy both stays unallocated. Also avoid requesting the first or last IP in a pool - many IPv4 networks treat those as network and broadcast addresses, and Cilium's docs explicitly warn against them.</p><div><hr></div><p><strong>Know someone who&#8217;s just getting into backend development or cloud architecture</strong></p><p>If this post helped you, pass it along - help them level up too. &#128071;</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/leaderboard?&amp;utm_source=post&quot;,&quot;text&quot;:&quot;Refer a friend&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/leaderboard?&amp;utm_source=post"><span>Refer a friend</span></a></p><div><hr></div><h3>End-to-end traffic flow</h3><p>Once LB IPAM assigns a VIP and L2 Announcements is active, the path from the outside becomes deterministic. Using the common home-lab pattern of public DNS &#8594; router NAT &#8594; LAN VIP.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Vvj1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bd5ac0-ed3f-459b-8b27-1fad00903d6a_1600x900.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Vvj1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bd5ac0-ed3f-459b-8b27-1fad00903d6a_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!Vvj1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bd5ac0-ed3f-459b-8b27-1fad00903d6a_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!Vvj1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bd5ac0-ed3f-459b-8b27-1fad00903d6a_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!Vvj1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bd5ac0-ed3f-459b-8b27-1fad00903d6a_1600x900.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Vvj1!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bd5ac0-ed3f-459b-8b27-1fad00903d6a_1600x900.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/99bd5ac0-ed3f-459b-8b27-1fad00903d6a_1600x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74512,&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://iam.slys.dev/i/190302892?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bd5ac0-ed3f-459b-8b27-1fad00903d6a_1600x900.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Vvj1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bd5ac0-ed3f-459b-8b27-1fad00903d6a_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!Vvj1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bd5ac0-ed3f-459b-8b27-1fad00903d6a_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!Vvj1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bd5ac0-ed3f-459b-8b27-1fad00903d6a_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!Vvj1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bd5ac0-ed3f-459b-8b27-1fad00903d6a_1600x900.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>A few steps worth understanding explicitly.</p><p><strong>The VIP is an ordinary LAN IP.</strong> Your router forwards to it the same way it would forward to any host on the LAN - not to a cloud load balancer appliance, not through any tunnel. The VIP address is real to the LAN; it's only "<em>virtual</em>" in the sense that no NIC has it configured as a static address.</p><p><strong>ARP provides the binding.</strong> To forward a packet to the VIP, the router must resolve which MAC address owns it. Cilium L2 Announcements responds to those ARP queries (or NDP for IPv6) on behalf of the VIP, returning the MAC of the current leader node.</p><p><strong>One node holds the Lease.</strong> ARP caches keep one MAC per IP - multiple responders would cause flapping. Cilium enforces single ownership through Kubernetes Lease-based leader election: one node holds the Lease per Service and is the sole ARP responder for that VIP.</p><p><strong>Failover is real but not instant.</strong> If the leader stops renewing the Lease, other eligible nodes race to take over. The new leader sends gratuitous ARP to update neighbor caches. Some clients ignore gratuitous ARP for security reasons, which can extend the failover window beyond the nominal lease timers. This is worth understanding before putting L2 Announcements in front of latency-sensitive workloads.</p><h1>Troubleshooting</h1><h2>Debugging layer by layer</h2><p>When a <code>LoadBalancer</code> Service on bare metal fails, debug in the same order traffic must succeed: Service state &#8594; IP allocation &#8594; L2 selection &#8594; ARP visibility &#8594; Lease ownership.</p><p><strong>Service status (Kubernetes view)</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl -n demo get svc demo-web
kubectl -n demo describe svc demo-web
kubectl -n demo get svc/demo-web -o jsonpath='{.status.conditions}'
echo</code></pre></div><p><code>&lt;pending&gt;</code> means no LB IPs have been assigned. The condition <code>io.cilium/lb-ipam-request-satisfied</code> with reason <code>no_pool</code> means no enabled pool matched the Service.</p><p><strong>IP allocation (LB IPAM view)</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl get ippools
kubectl describe ippools/lan-vips</code></pre></div><p>Check <code>DISABLED</code> and <code>CONFLICTING</code>. Overlapping pools cause a conflict and block allocation from the conflicting pool. Also confirm your Service labels match any pool <code>serviceSelector</code> you configured.</p><p><strong>L2 policy match (Cilium L2 view)</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl get CiliumL2AnnouncementPolicy
kubectl describe CiliumL2AnnouncementPolicy demo-l2</code></pre></div><p>Invalid policies surface errors as status conditions (for example <code>io.cilium/bad-service-selector</code>). Also check <code>loadBalancerClass</code> on the Service - L2 Announcements selects Services with <code>loadBalancerClass</code> unset or set to <code>io.cilium/l2-announcer</code>.</p><p><strong>ARP visibility (network view)</strong></p><p>From a host on the same LAN as your nodes:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">arp -a | grep 192.168.111.11</code></pre></div><p>If ARP never resolves, the VIP isn't being announced. The problem is in Cilium configuration, not in pod health.</p><p><strong>Lease objects (leader election view)</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl -n kube-system get lease | grep cilium-l2announce</code></pre></div><p>If no Leases exist, either the policy doesn't match or the agent can't create them. If agent logs mention "<em>forbidden</em>" access to <code>leases.coordination.k8s.io</code>, the RBAC step was missed.</p><p>For deeper inspection from inside an agent pod:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">kubectl -n kube-system exec ds/cilium -- cilium-dbg shell -- db/show l2-announce
kubectl -n kube-system exec ds/cilium -- cilium-dbg shell -- db/show devices</code></pre></div><p>These show which IPs are announced on which interfaces, and whether the interface is in Cilium's device list.</p><div><hr></div><p><strong>&#128161; Want more like this delivered to your inbox?</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>Closing thoughts</h1><p>Two problems, two mechanisms.</p><p><strong>LB IPAM</strong> answers "<em>which IP?</em>" - it gives your <code>LoadBalancer</code> Service an address from a pool you define. Without it, the Service stays <code>&lt;pending&gt;</code> indefinitely.</p><p><strong>L2 Announcements</strong> answers "<em>which MAC?</em>" - it makes that IP visible on your LAN by responding to ARP and NDP on behalf of the VIP. Without it, the IP exists in Kubernetes but is unreachable from the network.</p><p>Things to keep close:</p><ul><li><p>LB IPAM is always running but dormant - it activates the moment you create the first pool.</p></li><li><p>L2 Announcements requires kube-proxy replacement mode and Lease RBAC. Both must be in place before the feature does anything.</p></li><li><p>L2 scope is one L2 domain. If your nodes span VLANs or subnets, use Cilium's BGP control plane instead.</p></li><li><p>One node holds the Lease per Service. Failover exists but isn't instant - gratuitous ARP helps, but some clients ignore it.</p></li><li><p>Debug in order: Service conditions &#8594; pool status &#8594; policy match &#8594; ARP table &#8594; Lease objects.</p></li><li><p>Request specific IPs with the <code>lbipam.cilium.io/ips</code> annotation, not the deprecated <code>.spec.loadBalancerIP</code> field.</p></li><li><p>Interface patterns in <code>CiliumL2AnnouncementPolicy</code> are Go regex and must match interfaces in Cilium's device list - confirm with <code>db/show devices</code>.</p></li></ul><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p><a href="https://docs.cilium.io/en/stable/network/lb-ipam/">Cilium: LoadBalancer IP Address Management (LB IPAM)</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p><a href="https://docs.cilium.io/en/stable/network/l2-announcements/">Cilium: L2 Announcements / L2 Aware LB (Beta)</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p><a href="https://github.com/containernetworking/cni">CNI - the Container Network Interface</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p><a href="https://github.com/cilium/cilium-cli">Cilium CLI</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p><a href="https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/">Kubernetes Without kube-proxy</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p><a href="https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer">Kubernetes / Service / type: LoadBalancer</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p><a href="https://docs.cilium.io/en/stable/network/bgp-control-plane/bgp-control-plane/">Cilium BGP Control Plane</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p><a href="https://docs.cilium.io/en/stable/network/l2-announcements/#leases">L2 Announcements / L2 Aware LB (Beta) / Leases</a></p></div></div>]]></content:encoded></item><item><title><![CDATA[Logistic Regression — when machines learn to say yes or no]]></title><description><![CDATA[AI Literacy]]></description><link>https://iam.slys.dev/p/logistic-regression-when-machines</link><guid isPermaLink="false">https://iam.slys.dev/p/logistic-regression-when-machines</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 16 Mar 2026 21:38:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5HaR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5HaR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5HaR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic 424w, https://substackcdn.com/image/fetch/$s_!5HaR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic 848w, https://substackcdn.com/image/fetch/$s_!5HaR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic 1272w, https://substackcdn.com/image/fetch/$s_!5HaR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5HaR!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:86623,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/187836360?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5HaR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic 424w, https://substackcdn.com/image/fetch/$s_!5HaR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic 848w, https://substackcdn.com/image/fetch/$s_!5HaR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic 1272w, https://substackcdn.com/image/fetch/$s_!5HaR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e578d4-d44c-4e77-b3c0-212b9dc9352e_1920x1080.heic 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><strong>&#9888;&#65039; Before we begin &#8212; a practical AI newsletter worth following</strong></p><div class="embedded-publication-wrap" data-attrs="{&quot;id&quot;:6335167,&quot;embedding_publication_id&quot;:null,&quot;name&quot;:&quot;GenAI Unplugged&quot;,&quot;logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bW7t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59c51495-c9ae-4442-8cab-2bf1341fee46_1024x1024.png&quot;,&quot;base_url&quot;:&quot;https://genaiunplugged.substack.com&quot;,&quot;hero_text&quot;:&quot;Simple AI automation systems for solopreneurs who are done doing everything by hand.&quot;,&quot;author_name&quot;:&quot;Dheeraj Sharma&quot;,&quot;show_subscribe&quot;:true,&quot;logo_bg_color&quot;:&quot;#ffffff&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="EmbeddedPublicationToDOMWithSubscribe"><div class="embedded-publication show-subscribe"><a class="embedded-publication-link-part" native="true" href="https://genaiunplugged.substack.com?utm_source=substack&amp;utm_campaign=publication_embed&amp;utm_medium=web"><img class="embedded-publication-logo" src="https://substackcdn.com/image/fetch/$s_!bW7t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59c51495-c9ae-4442-8cab-2bf1341fee46_1024x1024.png" width="56" height="56" style="background-color: rgb(255, 255, 255);"><span class="embedded-publication-name">GenAI Unplugged</span><div class="embedded-publication-hero-text">Simple AI automation systems for solopreneurs who are done doing everything by hand.</div><div class="embedded-publication-author-name">By Dheeraj Sharma</div></a><form class="embedded-publication-subscribe" method="GET" action="https://genaiunplugged.substack.com/subscribe?"><input type="hidden" name="source" value="publication-embed"><input type="hidden" name="autoSubmit" value="true"><input type="email" class="email-input" name="email" placeholder="Type your email..."><input type="submit" class="button primary" value="Subscribe"></form></div></div><p><em><strong>GenAI Unplugged</strong></em> by <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Dheeraj Sharma&quot;,&quot;id&quot;:394741552,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!mIDa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3edd1f31-6669-445d-8285-dd01139794ab_1080x1080.png&quot;,&quot;uuid&quot;:&quot;8ff1a2bf-9ee3-4113-9992-7f99041a8556&quot;}" data-component-name="MentionToDOM"></span> focuses on building simple AI automation systems for solopreneurs. Instead of abstract AI discussions, you&#8217;ll find step-by-step guides for creating agents, automating workflows, and turning repetitive work into systems that actually save time.</p><div><hr></div><p>Binary classification sounds crisp: <em>yes</em> or <em>no</em>, <em>spam</em> or <em>not spam</em>, <em>churn</em> or <em>stay</em>. But most real decisions don&#8217;t feel crisp at all. They feel like standing in fog with a flashlight: you can see <em>some</em> evidence, but not enough to feel certain.</p><p>Think about a simple question a product team might ask: &#8220;<em>Will this package arrive late?</em>&#8221; There&#8217;s a tracking history, a carrier, a destination, weather, time of year, warehouse load. You don&#8217;t look at those signals and instantly &#8220;know&#8221; the answer. What you actually form is a belief - an internal probability. <em>Probably late.</em> <em>Maybe on time.</em> And only after that belief forms do you decide what to do: proactively message the customer, offer a discount, or do nothing.</p><p>Logistic regression is the machine learning version of that habit. It does not begin by trying to shout &#8220;<em>YES</em>&#8221; or &#8220;<em>NO</em>&#8221;. It begins by learning how to <em>weigh evidence</em> and turn that evidence into a <em>probability</em>. The &#8220;<em>classification</em>&#8221; part - the conversion from probability to a label - comes later, and (crucially) it is not a law of nature. It&#8217;s a choice you make based on costs, risk tolerance, and what kind of mistakes your system can live with.</p><p>This post is a slow walk through that mental model. We&#8217;ll build logistic regression from the inside out: an evidence score, a squashing function, a probability, and then an optional decision rule. We&#8217;ll derive the loss function step by step (no &#8220;<em>it can be shown</em>&#8221;), and we&#8217;ll do small numeric examples you can reproduce with a calculator. If you&#8217;ve ever felt that logistic regression was &#8220;<em>just a formula</em>&#8221;, the goal here is to make it feel like a clear, reasonable machine for turning messy signals into honest odds.</p><h1>A yes-or-no question rarely feels like yes-or-no</h1><p>Imagine you run a subscription app and you&#8217;re looking at a particular user. The question on your dashboard reads: &#8220;<em>Will this customer churn in the next 30 days?</em>&#8221; That is technically a binary question. Either they will, or they won&#8217;t.</p><p>But your <em>experience</em> of the question isn&#8217;t binary. You don&#8217;t think in labels first. You think in likelihoods: <em>They haven&#8217;t logged in for two weeks&#8230; that&#8217;s bad. But they just upgraded&#8230; that&#8217;s good. They contacted support twice&#8230; unclear.</em> Your brain starts doing an informal accounting of evidence, and the output is rarely a hard label. It&#8217;s something like: &#8220;<em>I&#8217;m worried. Maybe 70%</em>&#8221;.</p><p>That &#8220;<em>odds-first</em>&#8221; habit is not a weakness; it&#8217;s often the most rational way to behave under uncertainty. If an action is expensive (a retention phone call), you might require high confidence. If an action is cheap (an automated helpful email), you might act on weaker suspicion.</p><p>Logistic regression fits neatly into this worldview. It aims to learn a mapping from features (signals) to a probability like 0.72, not directly to a label. It learns <em>how strongly each signal should influence the belief</em>, using past data as feedback.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DiIT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2de666-9edc-4991-aff7-40d65c4c0d45_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DiIT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2de666-9edc-4991-aff7-40d65c4c0d45_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!DiIT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2de666-9edc-4991-aff7-40d65c4c0d45_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!DiIT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2de666-9edc-4991-aff7-40d65c4c0d45_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!DiIT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2de666-9edc-4991-aff7-40d65c4c0d45_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DiIT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2de666-9edc-4991-aff7-40d65c4c0d45_1536x1024.png" width="727.9962768554688" height="485.49751705127755" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2d2de666-9edc-4991-aff7-40d65c4c0d45_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:727.9962768554688,&quot;bytes&quot;:3295212,&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://iam.slys.dev/i/187836360?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2de666-9edc-4991-aff7-40d65c4c0d45_1536x1024.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_!DiIT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2de666-9edc-4991-aff7-40d65c4c0d45_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!DiIT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2de666-9edc-4991-aff7-40d65c4c0d45_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!DiIT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2de666-9edc-4991-aff7-40d65c4c0d45_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!DiIT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d2de666-9edc-4991-aff7-40d65c4c0d45_1536x1024.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>A subtle point that matters later: the model&#8217;s probability is not a statement about a single individual&#8217;s soul. It&#8217;s a statement about <em>frequency in similar cases</em>. If the model outputs 0.7 on many users, you want roughly 70% of those users (in that slice of feature space) to actually churn. That&#8217;s the core: treat binary outcomes as probabilistic events with learnable structure.</p><div><hr></div><p>&#128270; <em><strong>Ever wondered how machines actually decide &#8220;yes&#8221; or &#8220;no&#8221;?</strong></em></p><p>If you enjoy breaking down machine learning ideas into clear intuition instead of heavy math, you&#8217;ll probably like the rest of this series. <strong>Subscribe &#128071;</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>Why probabilities are often more useful than labels</h1><p>If you force every prediction into a hard 0 or 1, you throw away information that could have driven better decisions.</p><p>Consider two churn predictions:</p><ul><li><p>User <code>A: (p = 0.51)</code></p></li><li><p>User <code>B: (p = 0.99)</code></p></li></ul><p>If you use a default threshold of <code>0.5</code>, both become &#8220;<em>yes: will churn</em>&#8221;. But treating these users identically is usually irrational. User A is barely over the line - almost a coin flip. User B is practically screaming.</p><p>In real systems, this difference affects:</p><ul><li><p><strong>Resource allocation.</strong> A retention team might have time to call only the riskiest users. You want to sort by probability, not by label.</p></li><li><p><strong>Human review.</strong> Borderline cases are perfect for a queue, where a person can decide with extra context.</p></li><li><p><strong>User experience.</strong> You might show a gentle in-app nudge at <code>0.6</code>, but a stronger offer at <code>0.95</code>.</p></li><li><p><strong>Cost-sensitive decisions.</strong> If false positives are expensive, you may require a higher probability before acting.</p></li></ul><p>Probabilities also let you talk about performance in a richer way. Accuracy collapses everything into &#8220;<em>right or wrong</em>&#8221;, but probabilities support calibration curves, expected cost calculations, and ranking metrics. A model that says <code>0.6</code> for everything might get some labels right, but it&#8217;s not <em>useful</em> in the same way a well-shaped probability model is.</p><p>So the goal is not merely to &#8220;<em>classify</em>&#8221;. The goal is to produce a probability that is as <em>honest</em> as possible - meaning: aligned with reality across many examples. Logistic regression is one of the simplest models that tries to do exactly that, and its simplicity is part of why it&#8217;s still worth learning deeply.</p><h1>The model&#8217;s inner voice: &#8220;<em>How much evidence points to yes?</em>&#8221;</h1><p>Before we name anything, let&#8217;s build the mental picture.</p><p>Logistic regression creates a single number - a kind of internal &#8220;<em>evidence score</em>&#8221;. Each feature contributes a push:</p><ul><li><p>Some features push the score upward (more evidence for &#8220;<em>yes</em>&#8221;).</p></li><li><p>Others push it downward (more evidence for &#8220;<em>n</em>o&#8221;).</p></li><li><p>The pushes add together.</p></li></ul><p>This is very close to how you might make a decision with a checklist. &#8220;<em>Late delivery risk</em>&#8221; might increase if it&#8217;s winter, if the destination is far, if the carrier is overloaded. Those are pushes. You add them up into an overall sense of risk.</p><p>Mathematically, this &#8220;<em>weighted checklist</em>&#8221; is a linear combination. Suppose your features are:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;x_1, x_2, \\dots, x_d&quot;,&quot;id&quot;:&quot;BYDLRTRECE&quot;}" data-component-name="LatexBlockToDOM"></div><p>The model learns weights:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;w_1, w_2, \\dots, w_d&quot;,&quot;id&quot;:&quot;QKTSGNNXVI&quot;}" data-component-name="LatexBlockToDOM"></div><p></p><p>And it also learns a baseline offset (often called an intercept):</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;b&quot;,&quot;id&quot;:&quot;RRRWYALYJV&quot;}" data-component-name="LatexBlockToDOM"></div><p>The evidence score is:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;z = b + \\sum_i w_i x_i&quot;,&quot;id&quot;:&quot;IFLZRVJWHE&quot;}" data-component-name="LatexBlockToDOM"></div><p>That&#8217;s the whole inner voice: <em>a sum of nudges</em>. If <code>(w&#7522;)</code> is positive, higher <code>(x&#7522;)</code> pushes toward &#8220;<em>yes</em>&#8221;. If <code>(w&#7522;)</code> is negative, higher <code>(x&#7522;)</code> pushes toward &#8220;<em>no</em>&#8221;.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!x9Cj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F106f1770-8966-4a6c-bad6-f0c4e6114b60_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!x9Cj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F106f1770-8966-4a6c-bad6-f0c4e6114b60_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!x9Cj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F106f1770-8966-4a6c-bad6-f0c4e6114b60_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!x9Cj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F106f1770-8966-4a6c-bad6-f0c4e6114b60_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!x9Cj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F106f1770-8966-4a6c-bad6-f0c4e6114b60_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!x9Cj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F106f1770-8966-4a6c-bad6-f0c4e6114b60_1536x1024.png" width="727.9962768554688" height="485.49751705127755" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/106f1770-8966-4a6c-bad6-f0c4e6114b60_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:727.9962768554688,&quot;bytes&quot;:2991566,&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://iam.slys.dev/i/187836360?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F106f1770-8966-4a6c-bad6-f0c4e6114b60_1536x1024.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_!x9Cj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F106f1770-8966-4a6c-bad6-f0c4e6114b60_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!x9Cj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F106f1770-8966-4a6c-bad6-f0c4e6114b60_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!x9Cj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F106f1770-8966-4a6c-bad6-f0c4e6114b60_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!x9Cj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F106f1770-8966-4a6c-bad6-f0c4e6114b60_1536x1024.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>Worked example: computing an evidence score</h2><p><strong>Problem.</strong> Compute the evidence score <code>(z)</code>.</p><p><strong>Given.</strong></p><ul><li><p><code>(b = -1.2)</code></p></li><li><p><code>(w&#8321; = 0.8), (x&#8321; = 2.0)</code></p></li><li><p><code>(w&#8322; = -0.5), (x&#8322; = 1.0)</code></p></li><li><p><code>(w&#8323; = 0.3), (x&#8323; = 4.0)</code></p></li></ul><p><strong>Step 1: compute each push.</strong></p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;w_1x_1 = 0.8 \\cdot 2.0 = 1.6&quot;,&quot;id&quot;:&quot;NGTNOYKSXZ&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;w_2x_2 = -0.5 \\cdot 1.0 = -0.5&quot;,&quot;id&quot;:&quot;YOZUDMYAKV&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;w_3x_3 = 0.3 \\cdot 4.0 = 1.2&quot;,&quot;id&quot;:&quot;SOIJHJBRIT&quot;}" data-component-name="LatexBlockToDOM"></div><p><strong>Step 2: add them with the intercept.</strong></p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;z = b + 1.6 - 0.5 + 1.2&quot;,&quot;id&quot;:&quot;OBDMHIUNAL&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;z = -1.2 + 2.3&quot;,&quot;id&quot;:&quot;MEUEUGAXRH&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;z = 1.1&quot;,&quot;id&quot;:&quot;QUNGRWEQAC&quot;}" data-component-name="LatexBlockToDOM"></div><p><strong>Meaning.</strong> The model&#8217;s internal evidence is positive <code>((z = 1.1))</code>, so it is leaning toward &#8220;<em>yes</em>&#8221;. But we still don&#8217;t have a probability - yet.</p><h1>The squashing step that turns evidence into a probability</h1><p>An evidence score like <code>(z)</code> can be any real number: <code>(-10)</code>, <code>(1.1)</code>, <code>(200)</code>. But a probability must live between <code>0</code> and <code>1</code>. So we need a function that converts an unbounded score into a bounded probability.</p><p>Even before we name the function, we can describe what we want:</p><ul><li><p>Very negative evidence should map to a probability near <code>0</code>.</p></li><li><p>Very positive evidence should map to a probability near <code>1</code>.</p></li><li><p>Evidence near <code>0</code> should map to something like &#8220;<em>uncertain</em>&#8221;, around <code>0.5</code>.</p></li><li><p>The mapping should be smooth, not a hard step, because small evidence changes shouldn&#8217;t cause discontinuous probability jumps.</p></li></ul><p>The classic choice is the <strong>sigmoid</strong> (also called the logistic function). It&#8217;s defined as:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\sigma(z) = \\frac{1}{1 + e^{-z}}&quot;,&quot;id&quot;:&quot;ZSTJXHRNZE&quot;}" data-component-name="LatexBlockToDOM"></div><p>And logistic regression uses:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p = \\sigma(z)&quot;,&quot;id&quot;:&quot;NMXQSSTWJV&quot;}" data-component-name="LatexBlockToDOM"></div><p>where <code>(p)</code> is the predicted probability of the positive class (&#8220;<em>yes</em>&#8221;).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!77WJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef3728f9-2f66-414f-a1ab-eb51b9bd7fc8_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!77WJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef3728f9-2f66-414f-a1ab-eb51b9bd7fc8_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!77WJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef3728f9-2f66-414f-a1ab-eb51b9bd7fc8_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!77WJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef3728f9-2f66-414f-a1ab-eb51b9bd7fc8_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!77WJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef3728f9-2f66-414f-a1ab-eb51b9bd7fc8_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!77WJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef3728f9-2f66-414f-a1ab-eb51b9bd7fc8_1536x1024.png" width="724.3749389648438" height="483.0824627299885" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ef3728f9-2f66-414f-a1ab-eb51b9bd7fc8_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:724.3749389648438,&quot;bytes&quot;:3273376,&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://iam.slys.dev/i/187836360?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef3728f9-2f66-414f-a1ab-eb51b9bd7fc8_1536x1024.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_!77WJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef3728f9-2f66-414f-a1ab-eb51b9bd7fc8_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!77WJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef3728f9-2f66-414f-a1ab-eb51b9bd7fc8_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!77WJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef3728f9-2f66-414f-a1ab-eb51b9bd7fc8_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!77WJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef3728f9-2f66-414f-a1ab-eb51b9bd7fc8_1536x1024.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>Why this shape? One intuitive way to see it is to look at extremes:</p><p>If <code>(z)</code> is very large and positive, then <code>(-z)</code> is very negative, so <code>(e&#8315;&#7611;)</code> is near <code>0</code>. Then:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p \\approx \\frac{1}{1 + 0} = 1&quot;,&quot;id&quot;:&quot;JJEZCALECO&quot;}" data-component-name="LatexBlockToDOM"></div><p>If <code>(z)</code> is very large and negative, then <code>(-z)</code> is very positive, so <code>(e&#8315;&#7611;)</code> is huge. Then the denominator is huge, and <code>(p)</code> is near <code>0</code>.</p><p>At <code>(z = 0)</code>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p = \\frac{1}{1 + e^0}&quot;,&quot;id&quot;:&quot;YQJGPYYRAF&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p = \\frac{1}{2}&quot;,&quot;id&quot;:&quot;YWAQFPOBNH&quot;}" data-component-name="LatexBlockToDOM"></div><p>So zero evidence corresponds to <code>50/50</code> odds. And the curve is S-shaped: slow at the ends, fast in the middle. That &#8220;<em>fast in the middle</em>&#8221; turns out to be where most of the learning action lives.</p><div><hr></div><p><strong>&#128161; </strong><em><strong>Did this visualization make logistic regression finally click?</strong></em></p><p>Sometimes a single diagram explains what pages of equations cannot.</p><p>&#128172; <strong>Leave a comment</strong> - I&#8217;m curious what part made the idea clearer for you.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/logistic-regression-when-machines/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/logistic-regression-when-machines/comments"><span>Leave a comment</span></a></p><div><hr></div><h2>Worked example: from evidence to probability</h2><p><strong>Problem.</strong> Convert <code>(z = 1.1)</code> into a probability.</p><p><strong>Given.</strong> <code>(z = 1.1)</code></p><p><strong>Step 1: compute </strong><code>(e&#8315;&#7611;)</code><strong>.</strong></p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;e^{-1.1} \\approx 0.3329&quot;,&quot;id&quot;:&quot;YUJUFSVRXN&quot;}" data-component-name="LatexBlockToDOM"></div><p><strong>Step 2: compute the denominator.</strong></p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;1 + e^{-1.1} \\approx 1.3329&quot;,&quot;id&quot;:&quot;PMVFPKINPU&quot;}" data-component-name="LatexBlockToDOM"></div><p><strong>Step 3: invert.</strong></p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p = \\frac{1}{1.3329} \\approx 0.7502&quot;,&quot;id&quot;:&quot;UPIYVUGXCP&quot;}" data-component-name="LatexBlockToDOM"></div><p><strong>Meaning.</strong> Evidence score <code>(1.1)</code> corresponds to about a <code>75%</code> predicted chance of &#8220;<em>yes</em>&#8221;. Notice how that feels: a positive lean, not absolute certainty.</p><h1>Why the middle matters: uncertainty lives near the boundary</h1><p>The sigmoid&#8217;s most important region is not near <code>0</code> or near <code>1</code>. It&#8217;s the middle.</p><p>When <code>(z)</code> is very large (say <code>(z = 8)</code>), the sigmoid is already extremely close to <code>1</code>. Changing <code>(z)</code> a little doesn&#8217;t change the probability much:</p><ul><li><p><code>(z = 8 &#8658; p &#8776; 0.9997)</code></p></li><li><p><code>(z = 7 &#8658; p &#8776; 0.9991)</code></p></li></ul><p>Those are different numbers, but they are operationally similar: <em>high confidence &#8220;yes&#8221;.</em></p><p>In contrast, near <code>(z = 0)</code>, small changes move the probability a lot:</p><ul><li><p><code>(z = 0 &#8658; p = 0.5)</code></p></li><li><p><code>(z = 0.4 &#8658; p &#8776; 0.598)</code></p></li><li><p><code>(z = -0.4 &#8658; p &#8776; 0.401)</code></p></li></ul><p>This region corresponds to borderline cases - users who might churn or might not, emails that look a bit spammy but not definitely spam, transactions that are suspicious but plausibly legitimate.</p><p>Two things make the middle region special:</p><ol><li><p><strong>During training</strong>, these examples carry a lot of information. If the model is uncertain but wrong, it needs to adjust weights so similar cases move the correct direction. If the model is already extremely confident and correct, there&#8217;s not much to learn from that point.</p></li><li><p><strong>During deployment</strong>, these examples are the risky ones. A slight shift in the real world (seasonality, new user behavior, a policy change) can push many borderline cases across your chosen threshold, causing visible behavior changes.</p></li></ol><p>If you remember only one operational lesson: pay attention to the probability mass near your decision boundary. That&#8217;s where your metrics will be sensitive, where calibration matters, and where product decisions about thresholds will show up as real outcomes.</p><h1>From probability to decision: the threshold is not part of the truth</h1><p>Logistic regression outputs a probability <code>(p)</code>. That&#8217;s the model&#8217;s belief.</p><p>A <strong>decision rule</strong> is something you add on top:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat{y} = \n\\begin{cases} \n1 &amp; \\text{if } p \\ge t \\\\ \n0 &amp; \\text{if } p < t \n\\end{cases}&quot;,&quot;id&quot;:&quot;ZEDESGZTUP&quot;}" data-component-name="LatexBlockToDOM"></div><p>Here <code>(t)</code> is the threshold you choose.</p><p>It&#8217;s tempting to treat <code>(t = 0.5)</code> as &#8220;<em>the default</em>&#8221;, almost like physics. But it&#8217;s not physics. It&#8217;s values.</p><p>To see why, imagine two scenarios:</p><p><strong>Medical screening.</strong> A test flags &#8220;<em>possible disease</em>&#8221;. Missing a true case (false negative) might be very costly. You might set <code>(t)</code> low, perhaps <code>0.2</code>, accepting more false alarms in exchange for catching more true positives. The follow-up test might be cheap and safe, so false positives are tolerable.</p><p><strong>Account bans for fraud.</strong> Now a false positive is a serious accusation that harms a user. You might set <code>(t)</code> high, maybe <code>0.95</code>, accepting that you will miss some fraud in exchange for avoiding wrongful bans.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!APZ4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa486e6e5-237b-4fbe-913a-ef1624517028_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!APZ4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa486e6e5-237b-4fbe-913a-ef1624517028_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!APZ4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa486e6e5-237b-4fbe-913a-ef1624517028_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!APZ4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa486e6e5-237b-4fbe-913a-ef1624517028_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!APZ4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa486e6e5-237b-4fbe-913a-ef1624517028_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!APZ4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa486e6e5-237b-4fbe-913a-ef1624517028_1536x1024.png" width="727.9962768554688" height="485.49751705127755" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a486e6e5-237b-4fbe-913a-ef1624517028_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:727.9962768554688,&quot;bytes&quot;:3039199,&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://iam.slys.dev/i/187836360?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa486e6e5-237b-4fbe-913a-ef1624517028_1536x1024.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_!APZ4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa486e6e5-237b-4fbe-913a-ef1624517028_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!APZ4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa486e6e5-237b-4fbe-913a-ef1624517028_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!APZ4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa486e6e5-237b-4fbe-913a-ef1624517028_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!APZ4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa486e6e5-237b-4fbe-913a-ef1624517028_1536x1024.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 both cases, the same predicted probability can lead to a different action. That&#8217;s not inconsistency; it&#8217;s a separation of responsibilities:</p><ul><li><p>The model estimates probabilities as honestly as it can.</p></li><li><p>The system chooses actions based on costs and risk tolerance.</p></li></ul><h2>Worked example: choosing a threshold by expected cost</h2><p><strong>Problem.</strong> Pick the cheaper action using expected cost.</p><p><strong>Given.</strong></p><ul><li><p>Model outputs <code>(p = 0.30)</code> for &#8220;<em>fraud</em>&#8221;.</p></li><li><p>Cost of blocking a legitimate user: <code>(C</code><sub>FP</sub><code> = 50)</code></p></li><li><p>Cost of letting fraud through: <code>(C</code><sub>FN</sub><code> = 10)</code></p></li></ul><p>If we <strong>block</strong>, we pay <code>(50)</code> only if it&#8217;s not fraud, which happens with probability <code>(1 - p)</code>.</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;E[\\text{cost}|\\text{block}] = (1-p)\\cdot 50&quot;,&quot;id&quot;:&quot;OORDACBVSZ&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;= 0.70 \\cdot 50 = 35&quot;,&quot;id&quot;:&quot;XEAHWETYNY&quot;}" data-component-name="LatexBlockToDOM"></div><p>If we <strong>allow</strong>, we pay <code>(10)</code> only if it <em>is</em> fraud, which happens with probability (p).</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;E[\\text{cost}|\\text{allow}] = p\\cdot 10&quot;,&quot;id&quot;:&quot;GWOPZZKRLP&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;= 0.30 \\cdot 10 = 3&quot;,&quot;id&quot;:&quot;XFUVLCFLMJ&quot;}" data-component-name="LatexBlockToDOM"></div><p><strong>Decision.</strong> Allow is much cheaper in expectation <code>(3 vs 35)</code>. Even though <code>30%</code> fraud risk sounds scary, the action costs dominate the choice. This is the kind of reasoning probabilities enable - and hard labels destroy.</p><h1>A grounded example: churn prediction as a weighted checklist</h1><p>Let&#8217;s make this concrete with a small churn story. Suppose we run a subscription app and define &#8220;<em>churn</em>&#8221; as canceling within 30 days. We build features for each user:</p><ul><li><p><code>(x&#8321;):</code> days since last login</p></li><li><p><code>(x&#8322;):</code> number of support tickets in last 30 days</p></li><li><p><code>(x&#8323;):</code> discount usage (0 or 1)</p></li><li><p><code>(x&#8324;):</code> plan type (0 for annual, 1 for monthly)</p></li></ul><p>We train logistic regression and get weights (these are fictional but plausible):</p><ul><li><p><code>(b = -2.0)</code></p></li><li><p><code>(w&#8321; = 0.12)</code></p></li><li><p><code>(w&#8322; = 0.40)</code></p></li><li><p><code>(w</code><sub>3</sub><code> = 0.90)</code></p></li><li><p><code>(w</code><sub>4</sub><code> = 0.70)</code></p></li></ul><p>Interpretation: more days since login increases churn risk; more support tickets increases churn risk; using discounts is a risk signal (maybe bargain-seekers churn); monthly plans churn more than annual.</p><p>Now compare two users.</p><h2>User A (mild risk)</h2><ul><li><p>days since login: <code>(x&#8321; = 8)</code></p></li><li><p>support tickets: <code>(x&#8322; = 1)</code></p></li><li><p>used discount: <code>(x</code><sub>3</sub><code> = 0)</code></p></li><li><p>monthly plan: <code>(x</code><sub>4</sub><code> = 1)</code></p></li></ul><p>Evidence score:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;z = -2.0 \\cdot 0.12 \\cdot 8 \\cdot 0.40\\cdot 1 \\cdot 0.90\\cdot 0 \\cdot 0.70\\cdot 1&quot;,&quot;id&quot;:&quot;MGZCXMWRJE&quot;}" data-component-name="LatexBlockToDOM"></div><p>Compute pushes:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;0.12\\cdot 8 = 0.96;\n 0.40\\cdot 1 = 0.40; \n0.90\\cdot 0 = 0;\n0.70\\cdot 1 = 0.70;&quot;,&quot;id&quot;:&quot;ALSLURIFWS&quot;}" data-component-name="LatexBlockToDOM"></div><p>Sum:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;z = -2.0 + 0.96 + 0.40 + 0 + 0.70 = 0.06&quot;,&quot;id&quot;:&quot;CKOHTDQNGW&quot;}" data-component-name="LatexBlockToDOM"></div><p>Probability:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p = \\sigma(0.06) \\approx 0.515&quot;,&quot;id&quot;:&quot;LQIOQZPQUZ&quot;}" data-component-name="LatexBlockToDOM"></div><p>User A is barely above <code>50/50</code>: &#8220;<em>keep an eye on them</em>&#8221;.</p><h2>User B (high risk)</h2><ul><li><p>days since login: <code>(x&#8321; = 20)</code></p></li><li><p>support tickets: <code>(x&#8322; = 3)</code></p></li><li><p>used discount: <code>(x</code><sub>3</sub><code> = 1)</code></p></li><li><p>monthly plan: <code>(x</code><sub>4</sub><code> = 1)</code></p></li></ul><p>Evidence:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;z = -2.0 \\cdot 0.12\\cdot 20 \\cdot 0.40\\cdot 3 \\cdot 0.90\\cdot 1 \\cdot 0.70\\cdot 1&quot;,&quot;id&quot;:&quot;COZNLNLGZI&quot;}" data-component-name="LatexBlockToDOM"></div><p>Compute pushes:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;0.12\\cdot 20 = 2.40; 0.40\\cdot 3 = 1.20&quot;,&quot;id&quot;:&quot;VDRVWANXIG&quot;}" data-component-name="LatexBlockToDOM"></div><p>Sum:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;z = -2.0 + 2.40 + 1.20 + 0.90 + 0.70  = 3.20&quot;,&quot;id&quot;:&quot;RABDKOUKYC&quot;}" data-component-name="LatexBlockToDOM"></div><p>Probability:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p = \\sigma(3.20) \\approx 0.961&quot;,&quot;id&quot;:&quot;LMATSKPQFW&quot;}" data-component-name="LatexBlockToDOM"></div><p>Now we have two &#8220;<em>likely churn</em>&#8221; users, but one is <code>~0.52</code> and the other is <code>~0.96</code>. In a real retention workflow, User A might get a gentle reminder email. User B might justify a retention call or a targeted offer. The power here is not the label; it&#8217;s the ranked, calibrated belief.</p><h1>What training is actually doing: adjusting how much each signal counts</h1><p>So far we&#8217;ve treated the weights as magic numbers we somehow obtained. Training is the process of choosing those weights so that the predicted probabilities match reality well across many examples.</p><p>Let&#8217;s formalize one data point. For user <code>(n)</code>:</p><ul><li><p>features: <code>(x&#8317;&#8319;&#8318;)</code></p></li><li><p>label: <code>(y&#8317;&#8319;&#8318; &#8712; 0, 1)</code></p></li></ul><ul><li><p>predicted probability: <code>[ p&#8317;&#8319;&#8318; = &#963;(z&#8317;&#8319;&#8318;) ]</code></p></li><li><p>evidence score: <code>[ z&#8317;&#8319;&#8318; = b + w&#7488;x&#8317;&#8319;&#8318; ]</code></p></li></ul><p>Now: how should a model be rewarded or punished? Intuitively:</p><ul><li><p>If the true label is <code>1</code> and the model says <code>(p = 0.99)</code>, that&#8217;s great.</p></li><li><p>If the true label is <code>1</code> and the model says <code>(p = 0.51)</code>, that&#8217;s only mildly good.</p></li><li><p>If the true label is <code>1</code> and the model says <code>(p = 0.01)</code>, that&#8217;s disastrously overconfident and should hurt a lot.</p></li></ul><p>This &#8220;<em>confidently wrong should hurt more</em>&#8221; idea leads to <strong>log loss</strong> (also called cross-entropy for binary classification). Here&#8217;s the clean derivation from probability, not from tradition.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!y-ZE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff98970c5-8a65-4871-b278-5c3ca990a4f5_960x540.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!y-ZE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff98970c5-8a65-4871-b278-5c3ca990a4f5_960x540.gif 424w, https://substackcdn.com/image/fetch/$s_!y-ZE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff98970c5-8a65-4871-b278-5c3ca990a4f5_960x540.gif 848w, https://substackcdn.com/image/fetch/$s_!y-ZE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff98970c5-8a65-4871-b278-5c3ca990a4f5_960x540.gif 1272w, https://substackcdn.com/image/fetch/$s_!y-ZE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff98970c5-8a65-4871-b278-5c3ca990a4f5_960x540.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!y-ZE!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff98970c5-8a65-4871-b278-5c3ca990a4f5_960x540.gif" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f98970c5-8a65-4871-b278-5c3ca990a4f5_960x540.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:540,&quot;width&quot;:960,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:314274,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/187836360?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff98970c5-8a65-4871-b278-5c3ca990a4f5_960x540.gif&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_!y-ZE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff98970c5-8a65-4871-b278-5c3ca990a4f5_960x540.gif 424w, https://substackcdn.com/image/fetch/$s_!y-ZE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff98970c5-8a65-4871-b278-5c3ca990a4f5_960x540.gif 848w, https://substackcdn.com/image/fetch/$s_!y-ZE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff98970c5-8a65-4871-b278-5c3ca990a4f5_960x540.gif 1272w, https://substackcdn.com/image/fetch/$s_!y-ZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff98970c5-8a65-4871-b278-5c3ca990a4f5_960x540.gif 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>Step-by-step derivation from a Bernoulli likelihood</h2><p>For a binary outcome <code>(y)</code>, if the model predicts probability <code>(p)</code> of <code>(y = 1)</code>, then the probability of observing <code>(y)</code> is:</p><ul><li><p>If <code>(y = 1):</code> probability is <code>(p)</code></p></li><li><p>If <code>(y = 0):</code> probability is <code>(1 - p)</code></p></li></ul><p>We can write both cases in one expression:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;P(y|p) = p^y (1-p)^{1-y}&quot;,&quot;id&quot;:&quot;RXKGUJUSHE&quot;}" data-component-name="LatexBlockToDOM"></div><p>Now training wants to choose parameters that make observed labels likely. That means maximizing the likelihood over all data points, or equivalently maximizing the log-likelihood (log is monotonic and turns products into sums).</p><p>For one example:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\log P(y|p) = \\log \\big( p^y (1-p)^{1-y} \\big) &quot;,&quot;id&quot;:&quot;GRHLUNINQQ&quot;}" data-component-name="LatexBlockToDOM"></div><p>Use <code>log</code> rules:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;= y \\log(p) \\cdot (1-y)\\log(1-p)&quot;,&quot;id&quot;:&quot;MIYHNWONPU&quot;}" data-component-name="LatexBlockToDOM"></div><p>To turn this into a loss we <em>minimize</em>, we negate it:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\ell(y,p) = -y \\log(p) -(1-y)\\log(1-p)&quot;,&quot;id&quot;:&quot;AOYCYEGBPQ&quot;}" data-component-name="LatexBlockToDOM"></div><p>That is the binary log loss.</p><p>Notice what it does: if <code>(y = 1)</code>, the loss is <code>(-log(p))</code>, which goes to infinity as <code>(p &#10141; 0)</code>. That&#8217;s the &#8220;<em>confidently wrong hurts a lot</em>&#8221; property, mathematically enforced.</p><h2>Worked example: log loss for two predictions</h2><p><strong>Problem.</strong> Compare losses for a correct-but-uncertain prediction and a correct-and-confident one.</p><p><strong>Given.</strong> True label <code>(y = 1)</code>. Two predictions: <code>(p&#8321; = 0.6)</code>, <code>(p&#8322; = 0.95)</code>.</p><p>When <code>(y = 1)</code>, loss is:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\ell = -\\log(p)&quot;,&quot;id&quot;:&quot;IPXRCWXNMT&quot;}" data-component-name="LatexBlockToDOM"></div><p>For <code>(p&#8321; = 0.6)</code>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\ell_1 = -\\log(0.6) \\approx 0.5108&quot;,&quot;id&quot;:&quot;YBWYBWJLIL&quot;}" data-component-name="LatexBlockToDOM"></div><p>For <code>(p&#8322; = 0.95)</code>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\ell_2 = -\\log(0.95) \\approx 0.0513&quot;,&quot;id&quot;:&quot;LHOSUALPDN&quot;}" data-component-name="LatexBlockToDOM"></div><p><strong>Meaning.</strong> Both are &#8220;<em>correct</em>&#8221; under a 0.5 threshold, but the confident correct prediction is rewarded much more (smaller loss). Training with this loss nudges the model to be not just correct, but <em>calibrated and decisive when the evidence supports it</em>.</p><h1>Reading the model: coefficients as directional pushes (with caveats)</h1><p>One of logistic regression&#8217;s most attractive traits is that it&#8217;s interpretable in a very practical sense. If you look at the learned coefficients:</p><ul><li><p>A positive <code>(w&#7522;)</code> means feature <code>(x&#7522;)</code> pushes probability toward &#8220;<em>yes</em>&#8221;.</p></li><li><p>A negative <code>(w&#7522;)</code> means it pushes toward &#8220;<em>no</em>&#8221;.</p></li><li><p>Larger magnitude generally means a stronger push.</p></li></ul><p>There&#8217;s also a deeper and very useful interpretation that connects directly to the evidence score. Logistic regression makes the evidence score linear:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;z = b + \\sum_i w_i x_i&quot;,&quot;id&quot;:&quot;CRMSTQPJAL&quot;}" data-component-name="LatexBlockToDOM"></div><p>And then:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p = \\sigma(z)&quot;,&quot;id&quot;:&quot;OUMLUHNBDO&quot;}" data-component-name="LatexBlockToDOM"></div><p>Because the sigmoid is monotonic, increasing <code>(z)</code> always increases <code>(p)</code>. So coefficients literally control the direction of probability movement.</p><p>You will also hear a statement like: &#8220;<em>coefficients correspond to log-odds</em>&#8221;. That can be made explicit by solving the sigmoid for <code>(z)</code>.</p><p>Start from:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p = \\frac{1}{1+e^{-z}}&quot;,&quot;id&quot;:&quot;NPOJWOBMHX&quot;}" data-component-name="LatexBlockToDOM"></div><p>Invert both sides:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{1}{p} = 1 + e^{-z}&quot;,&quot;id&quot;:&quot;RPFZROHONV&quot;}" data-component-name="LatexBlockToDOM"></div><p>Subtract <code>1</code>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{1}{p} - 1 = e^{-z}&quot;,&quot;id&quot;:&quot;DXYRZBOOQN&quot;}" data-component-name="LatexBlockToDOM"></div><p>Combine terms:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{1-p}{p} = e^{-z}&quot;,&quot;id&quot;:&quot;BMOUVYWUKM&quot;}" data-component-name="LatexBlockToDOM"></div><p>Take log:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\log\\Big(\\frac{1-p}{p}\\Big) = -z&quot;,&quot;id&quot;:&quot;UTEDZIBCAN&quot;}" data-component-name="LatexBlockToDOM"></div><p>Multiply by <code>(-1)</code>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\log\\Big(\\frac{p}{1-p}\\Big) = z &quot;,&quot;id&quot;:&quot;GXCHKVDQNX&quot;}" data-component-name="LatexBlockToDOM"></div><p>So the evidence score <code>(z)</code> is the <strong>log-odds</strong>. Each feature contribution <code>(w&#7522; x&#7522;)</code> is a contribution to log-odds.</p><p>Now, the two gotchas that surprise beginners:</p><p><strong>Gotcha 1: feature scale matters.</strong> If one feature is measured in days <code>(0&#8211;365)</code> and another is a binary flag <code>(0/1)</code>, the day feature can have a small coefficient and still dominate the sum. Comparing raw coefficient magnitudes without standardizing features can be misleading.</p><p><strong>Gotcha 2: correlated features share credit.</strong> If two features are strongly correlated (say &#8220;<em>days since last login</em>&#8221; and &#8220;<em>number of sessions last month</em>&#8221;), the model can distribute weight between them in many equivalent ways. Individual coefficients may look unstable across retraining runs, even when predictions remain stable. Interpret coefficients with care: they are not always &#8220;<em>causal importance</em>&#8221;.</p><h1>What logistic regression can and cannot represent</h1><p>Logistic regression draws a boundary that is linear in feature space (or, said differently, it separates examples using a hyperplane in the original feature space). The probability changes smoothly as you move across that boundary.</p><p>That structure is a strength. It means:</p><ul><li><p><strong>Fast training.</strong> You can fit large datasets efficiently.</p></li><li><p><strong>Robustness.</strong> With regularization, it tends not to overfit wildly.</p></li><li><p><strong>A strong baseline.</strong> If you can&#8217;t beat logistic regression, you often don&#8217;t yet understand the problem or the features.</p></li><li><p><strong>Reasonable extrapolation.</strong> Linear evidence behaves predictably outside the training set (though not always correctly).</p></li></ul><p>But it is also a limitation. If the true relationship between features and outcome is highly non-linear - say, churn risk spikes only for a specific combination of behaviors, or fraud looks like a complicated &#8220;<em>island</em>&#8221; in feature space - then a single linear boundary may not capture it well.</p><p>There&#8217;s an important nuance: logistic regression is linear in the features you give it, not necessarily linear in the raw world. You can engineer features that expose non-linear structure. For example:</p><ul><li><p>Add polynomial terms like <code>(x&#178;)</code></p></li><li><p>Add interactions like <code>(x&#8321; x&#8322;)</code></p></li><li><p>Use splines or bucketed features</p></li></ul><p>Once you add those engineered features, logistic regression can represent more complex boundaries - but you paid for that complexity by choosing the right transformations.</p><p>In practice, this is why logistic regression often shines in domains with well-designed features (ads, ranking, many tabular problems). It&#8217;s not glamorous. It&#8217;s just honest about what it can represent: a smooth ramp from &#8220;<em>no</em>&#8221; to &#8220;<em>yes</em>&#8221; driven by additive evidence.</p><h1>When it fails in real systems: rare events, noisy labels, drifting populations</h1><p>Most textbook explanations end when the model is trained. Real problems start after deployment.</p><p><strong>Rare events.</strong> In fraud, serious adverse events, or rare diseases, positives may be 0.1% of the data. A logistic regression model can still output probabilities, but those probabilities can be poorly grounded if you evaluate incorrectly or if the model is pushed into regions with little positive data. You may see confident-looking numbers that are driven more by class imbalance and feature quirks than by true signal. Handling this often requires careful sampling strategies, appropriate metrics (like precision-recall curves), and explicit attention to calibration.</p><p><strong>Noisy labels.</strong> Suppose &#8220;<em>churn</em>&#8221; is defined as &#8220;<em>no activity for 30 days</em>&#8221;, but many users are seasonal and return later. Now your labels encode ambiguity. Logistic regression will respond by pushing probabilities toward the middle because the same feature patterns sometimes map to <code>1</code> and sometimes to <code>0</code>. That is not the model being &#8220;<em>weak</em>&#8221;; it is the model reflecting inconsistency in the target definition. If your organization expects crisp predictions from fuzzy labels, you will fight endlessly.</p><p><strong>Drifting populations.</strong> The most insidious failure is distribution shift. The model is trained on last year&#8217;s user behavior, but this year you introduced a new onboarding flow, a new pricing tier, or a competitor launched something disruptive. Now the relationship between features and churn changes. Your &#8220;<em>0.8</em>&#8221; probabilities may stop meaning &#8220;<em>80%</em>&#8221;. They might behave like &#8220;<em>60%</em>&#8221; in today&#8217;s world. That&#8217;s a calibration break caused by a moving target.</p><p>Operationally, this is why monitoring matters: not just accuracy, but calibration, feature distributions, and decision outcomes. Logistic regression is simple, but the system around it is not. The model&#8217;s humility - expressed as a probability - can be lost if you treat it like a deterministic oracle.</p><h1>Misunderstandings worth clearing up early</h1><p>A few confusions come up so often that it&#8217;s worth clearing them early, before they calcify into mental bugs.</p><p><strong>&#8220;Why is it called regression if it&#8217;s classification?&#8221;</strong> Historically, &#8220;<em>regression</em>&#8221; refers to modeling a numerical quantity. Logistic regression models a numerical quantity: the probability <code>(P(y=1|x))</code>. The <em>output</em> is continuous. We then optionally threshold it to get a class label. So the name is less wrong than it sounds; it&#8217;s just anchored in the probability-first view rather than the label-first view.</p><p><strong>&#8220;If the model says 0.9, does it know the answer?&#8221;</strong> No. A probability is not a promise about this individual case. It&#8217;s a statement about frequency among similar cases. If the model is well-calibrated, then among all cases where it predicts 0.9, about 90% should be positive. That&#8217;s a population-level claim, not a metaphysical certainty about one example.</p><p><strong>&#8220;Is 0.5 the default threshold?&#8221;</strong> It&#8217;s a default in the sense that it&#8217;s a convenient midpoint, but it&#8217;s rarely the <em>right</em> choice automatically. If false positives and false negatives have different costs (they almost always do), the optimal threshold can be far from <code>0.5</code>.</p><p><strong>&#8220;Does logistic regression output &#8216;</strong><em><strong>real probabilities</strong></em><strong>&#8217;?&#8221;</strong> It outputs numbers between 0 and 1 that are trained using a probability-based loss. Whether those numbers behave like real-world probabilities depends on calibration, data quality, and whether the deployment distribution matches training. Logistic regression is often reasonably calibrated compared to some more flexible models, but there are plenty of ways it can be miscalibrated in practice.</p><p>If you keep the probability-first mental model, these misunderstandings mostly dissolve. Confusion tends to enter when we treat the label as truth and the probability as a decoration, rather than the other way around.</p><div><hr></div><p><strong>&#128640; </strong><em><strong>Know someone who still finds logistic regression confusing?</strong></em></p><p>A simple mental model often helps more than another textbook explanation.</p><p>&#128257; <strong>Share this post</strong> with someone learning machine learning.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/logistic-regression-when-machines?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/logistic-regression-when-machines?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h1>Closing reflection: a small model with a big lesson</h1><p>Logistic regression is often introduced as a &#8220;<em>baseline classifier</em>&#8221;, which is technically true and psychologically unfair. It&#8217;s more than a baseline. It&#8217;s one of the cleanest examples of a principle that scales all the way up to modern machine learning systems:</p><p><strong>Separate belief from action.</strong></p><p>The model&#8217;s job is belief: given evidence, output an honest probability. Your system&#8217;s job is action: choose what to do given that belief, your costs, your risks, and your values. When you blur these two roles - when you treat a probability as a label, or a threshold as truth - you lose control. You also lose the ability to have clear conversations with stakeholders about trade-offs, because everything collapses into &#8220;<em>the model said so</em>&#8221;.</p><p>Logistic regression also teaches a quieter lesson about mathematics in ML: the formulas are not arbitrary. The sigmoid is not a random squashing function; it is a convenient, interpretable bridge between additive evidence (log-odds) and probabilities. Log loss is not a punishment invented to scare you; it falls directly out of maximum likelihood for a Bernoulli outcome, and it encodes a very human idea: confident wrongness should hurt.</p><p>If you later move to gradient-boosted trees, neural nets, or large language models, this framing still helps. You can ask: what is the model&#8217;s belief representation, how is it trained to be honest, and where do we convert beliefs into decisions? Logistic regression is small enough to fit in your head, but big enough to teach that habit well.</p><div><hr></div><ol><li><p><strong><a href="https://a.co/d/0dK0vp8Y">The Elements of Statistical Learning: Data Mining, Inference, and Prediction, Second Edition</a></strong> <em>by Trevor Hastie, Robert Tibshirani, Jerome Friedman</em></p></li><li><p><strong><a href="https://a.co/d/00FoV7Fo">Machine Learning: A Probabilistic Perspective</a> </strong>(Adaptive Computation and Machine Learning series)<strong> </strong><em>by Kevin P. Murphy</em></p></li><li><p><strong><a href="https://a.co/d/02kYNKuh">Pattern Recognition and Machine Learning (Information Science and Statistics)</a></strong></p><p><em>by Christopher M. Bishop</em></p></li><li><p><strong><a href="https://a.co/d/0ihHFLok">Information Theory, Inference and Learning Algorithms</a> </strong><em>by David J. C. MacKay</em></p></li><li><p><strong><a href="https://cs229.stanford.edu/main_notes.pdf">Stanford CS229 notes (linear regression + optimization)</a> </strong><em>by Andrew Ng and Tengyu Ma</em></p></li><li><p><strong><a href="https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html#logisticregression">scikit-learn documentation for </a></strong><code>LogisticRegression</code></p></li></ol>]]></content:encoded></item><item><title><![CDATA[Thinking clearly in interviews — Add Two Numbers]]></title><description><![CDATA[Understanding the why before the code (Rust &#183; Python &#183; Scala)]]></description><link>https://iam.slys.dev/p/thinking-clearly-in-interviews-add</link><guid isPermaLink="false">https://iam.slys.dev/p/thinking-clearly-in-interviews-add</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 09 Mar 2026 21:34:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!qdoY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qdoY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qdoY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!qdoY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!qdoY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!qdoY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qdoY!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3052385,&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://iam.slys.dev/i/187834298?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qdoY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!qdoY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!qdoY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!qdoY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b59c917-a9c8-4d45-9a35-74c829301c67_1536x1024.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><strong>&#9888;&#65039; Before we begin &#8212; a leadership lens worth following</strong></p><div class="embedded-publication-wrap" data-attrs="{&quot;id&quot;:4640380,&quot;embedding_publication_id&quot;:null,&quot;name&quot;:&quot;Leadership in Change&quot;,&quot;logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!nt6C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93a7f29-8673-4dd8-a435-838ade7e7337_560x560.png&quot;,&quot;base_url&quot;:&quot;https://leadershipinchange.com&quot;,&quot;hero_text&quot;:&quot;Don&#8217;t just read about AI, lead it. Get the weekly playbook for leaders who want to supercharge their expertise and multiply their impact.&quot;,&quot;author_name&quot;:&quot;Joel Salinas&quot;,&quot;show_subscribe&quot;:true,&quot;logo_bg_color&quot;:&quot;#fafafa&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="EmbeddedPublicationToDOMWithSubscribe"><div class="embedded-publication show-subscribe"><a class="embedded-publication-link-part" native="true" href="https://leadershipinchange.com?utm_source=substack&amp;utm_campaign=publication_embed&amp;utm_medium=web"><img class="embedded-publication-logo" src="https://substackcdn.com/image/fetch/$s_!nt6C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93a7f29-8673-4dd8-a435-838ade7e7337_560x560.png" width="56" height="56" style="background-color: rgb(250, 250, 250);"><span class="embedded-publication-name">Leadership in Change</span><div class="embedded-publication-hero-text">Don&#8217;t just read about AI, lead it. Get the weekly playbook for leaders who want to supercharge their expertise and multiply their impact.</div><div class="embedded-publication-author-name">By Joel Salinas</div></a><form class="embedded-publication-subscribe" method="GET" action="https://leadershipinchange.com/subscribe?"><input type="hidden" name="source" value="publication-embed"><input type="hidden" name="autoSubmit" value="true"><input type="email" class="email-input" name="email" placeholder="Type your email..."><input type="submit" class="button primary" value="Subscribe"></form></div></div><p><em><strong>Leadership in Change</strong> </em>by<em> </em><span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Joel Salinas&quot;,&quot;id&quot;:198127390,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!Uip2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed5e6c5-5af1-4813-959c-4a1c14354fd2_500x500.png&quot;,&quot;uuid&quot;:&quot;d463d800-5ffa-4342-86b7-1ed222f9fbec&quot;}" data-component-name="MentionToDOM"></span> explores what it really means to lead through uncertainty, transformation, and complexity. It&#8217;s not motivational fluff - it&#8217;s grounded thinking about how leaders <em>adapt</em>, <em>influence</em>, and <em>evolve</em> when the environment shifts.</p><div><hr></div><p>You&#8217;re standing at a small bakery counter with a friend. You each grab a few items, and the cashier reads out totals in a way that feels slightly&#8230; backward. Instead of &#8220;<em>three dollars and forty-two cents</em>&#8221;, you hear &#8220;<em>two cents, then four dimes, then three dollars</em>&#8221;. Your brain stalls for a beat. You know it&#8217;s the same amount, but it&#8217;s being presented from the &#8220;<em>smallest</em>&#8221; part first.</p><p>Nothing is broken. It&#8217;s just an order you don&#8217;t usually use.</p><blockquote><p><strong>Problem</strong></p><p>You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.</p><p>You may assume the two numbers do not contain any leading zero, except the number 0 itself.</p><p><strong>Examples</strong></p><p>Example 1:</p><p>Input: <code>l1 = [2,4,3]</code>, <code>l2 = [5,6,4]</code> Output: <code>[7,0,8]</code> Explanation: <code>342 + 465 = 807</code>.</p><p>Example 2:</p><p>Input: <code>l1 = [0]</code>, <code>l2 = [0]</code> Output: <code>[0]</code></p><p>Example 3:</p><p>Input: <code>l1 = [9,9,9,9,9,9,9]</code>, <code>l2 = [9,9,9,9]</code> Output: <code>[8,9,9,9,0,0,0,1]</code></p><p><strong>Constraints</strong></p><ul><li><p>The number of nodes in each linked list is in the range <code>[1, 100].</code></p></li><li><p>0 &lt;= node value &lt;= 9</p></li><li><p>It is guaranteed that the list represents a number that does not have leading zeros.</p></li></ul></blockquote><p>If you&#8217;ve ever counted out change from coins to bills, you&#8217;ve actually done this: you start with the smallest units, deal with carry (ten pennies make a dime), and only then move up. The process is natural. The representation is what&#8217;s unusual.</p><p>That tiny moment - &#8220;<em>wait, what order am I seeing this in</em>?&#8221; - is the entire heart of a famous coding interview problem. You&#8217;re given two numbers, but you don&#8217;t get them as normal integers. You get them as linked lists whose digits arrive least-significant first. Your job is to add them and return the result in the same &#8220;<em>backward</em>&#8221; form.</p><p>Interviewers like this because it&#8217;s not about advanced math. It&#8217;s about whether you can stay calm when the data format is unfamiliar, translate it into a process you already understand (grade-school addition), and implement it cleanly without tripping over edge cases. Under time pressure, the winner isn&#8217;t the person who&#8217;s seen the trick - it&#8217;s the person who can recognize that there isn&#8217;t a trick.</p><h1>&#127897;&#65039; Why interviewers like this problem</h1><p>From an interviewer&#8217;s perspective, <em>Add Two Numbers</em> is a compact test of a lot of real engineering skills.</p><p>First, it tests whether you can map a concept to a representation. Adding numbers is easy; adding numbers when the digits are stored in a linked list - reversed - is where candidates reveal how quickly they can normalize a problem. Strong candidates don&#8217;t complain about the format; they convert it into a mental model (&#8220;<em>this is just digit-by-digit addition with carry</em>&#8221;).</p><p>Second, it exposes your relationship with pointers/references and incremental construction. Linked lists are intentionally a little awkward: you can&#8217;t index, you can&#8217;t easily go backward, and you need to build results node by node. The problem forces you into iterative thinking, careful state management, and clean handling of &#8220;<em>end of list</em>&#8221; conditions.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QEuU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dcf4b7-d23a-4e49-9423-2c672f93e460_2330x1273.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QEuU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dcf4b7-d23a-4e49-9423-2c672f93e460_2330x1273.png 424w, https://substackcdn.com/image/fetch/$s_!QEuU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dcf4b7-d23a-4e49-9423-2c672f93e460_2330x1273.png 848w, https://substackcdn.com/image/fetch/$s_!QEuU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dcf4b7-d23a-4e49-9423-2c672f93e460_2330x1273.png 1272w, https://substackcdn.com/image/fetch/$s_!QEuU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dcf4b7-d23a-4e49-9423-2c672f93e460_2330x1273.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QEuU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dcf4b7-d23a-4e49-9423-2c672f93e460_2330x1273.png" width="1456" height="795" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/18dcf4b7-d23a-4e49-9423-2c672f93e460_2330x1273.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:795,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:177357,&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://iam.slys.dev/i/187834298?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dcf4b7-d23a-4e49-9423-2c672f93e460_2330x1273.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_!QEuU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dcf4b7-d23a-4e49-9423-2c672f93e460_2330x1273.png 424w, https://substackcdn.com/image/fetch/$s_!QEuU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dcf4b7-d23a-4e49-9423-2c672f93e460_2330x1273.png 848w, https://substackcdn.com/image/fetch/$s_!QEuU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dcf4b7-d23a-4e49-9423-2c672f93e460_2330x1273.png 1272w, https://substackcdn.com/image/fetch/$s_!QEuU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dcf4b7-d23a-4e49-9423-2c672f93e460_2330x1273.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>Third, it&#8217;s a communication problem disguised as an algorithm problem. In a live interview, you&#8217;re expected to narrate your reasoning: what variables you&#8217;re tracking, why carry matters, what happens when one list is longer, and what you do at the end if there&#8217;s still carry left.</p><p>Finally, it&#8217;s a safe place to evaluate code quality. There&#8217;s no need for fancy optimizations, but there&#8217;s plenty of room for messy code: off-by-one mistakes, null handling bugs, unnecessary conversions, and overly clever approaches that hide correctness issues. Interviewers want to see whether you can write code that is both correct and readable when you&#8217;re being watched.</p><h1>&#128214; Understanding the problem in plain language</h1><p>You&#8217;re given two singly linked lists. Each node holds one digit <code>(0&#8211;9)</code>. If you read the list from head to tail, you&#8217;re reading the number from least significant digit to most significant digit.</p><p>So the list <code>[2, 4, 3]</code> represents the number 342, because:</p><ul><li><p>head <code>2</code> is the ones place</p></li><li><p>then <code>4</code> is the tens place</p></li><li><p>then <code>3</code> is the hundreds place</p></li></ul><p>You need to add the two numbers represented by the two lists and return a new linked list representing their sum in the same reversed-digit format.</p><p>A few important clarifications that are easy to miss when you&#8217;re nervous:</p><ul><li><p>The lists are <strong>non-empty</strong>. You&#8217;ll always have at least one node in each.</p></li><li><p>Digits are <strong>non-negative single digits</strong>. No node contains <code>12</code> or <code>-1</code>.</p></li><li><p>The numbers have <strong>no leading zeros</strong> in the normal representation, but that&#8217;s not the main challenge here. (And <code>0</code> itself is allowed.)</p></li><li><p>The result can be longer than either input list, because addition can introduce a new most-significant digit (like <code>999 + 1 = 1000</code>).</p></li></ul><p>The most important mental shift is this: you do <em>not</em> need to convert the lists to integers. In fact, you usually shouldn&#8217;t. The natural solution is to simulate grade-school addition as you walk the lists.</p><h1>&#128099; Reasoning through examples</h1><p>Let&#8217;s walk through the classic example slowly, the way you&#8217;d narrate it in an interview.</p><p><code>l1 = [2, 4, 3]</code> represents <code>342</code><br><code>l2 = [5, 6, 4]</code> represents <code>465</code></p><p>We add digit by digit, starting from the head (ones place), carrying when needed.</p><ul><li><p>Start with <code>carry = 0</code>.</p></li><li><p>Ones place: <code>2 + 5 + 0 = 7</code> &#8594; write down 7, carry 0.</p></li><li><p>Tens place: <code>4 + 6 + 0 = 10</code> &#8594; write down 0, carry 1.</p></li><li><p>Hundreds place: <code>3 + 4 + 1 = 8</code> &#8594; write down 8, carry 0.</p></li><li><p>Done, <code>carry is 0</code>, so stop.</p></li></ul><p>Result: <code>[7, 0, 8]</code> which represents 807. That matches what we expect.</p><p>Now look at an example where carry survives past the last digits:</p><p><code>l1 = [9, 9, 9, 9, 9, 9, 9]</code><br><code>l2 = [9, 9, 9, 9]</code></p><p>Here&#8217;s how you should describe your thinking out loud:</p><ul><li><p>&#8220;<em>I&#8217;m going to traverse both lists until both are exhausted and there&#8217;s no carry.</em>&#8221;</p></li><li><p>&#8220;<em>At each position, if a list is exhausted, I treat its digit as 0.</em>&#8221;</p></li><li><p>&#8220;<em>I compute sum = digit1 + digit2 + carry, output sum % 10, and update carry = sum // 10.</em>&#8221;</p></li></ul><p>If you narrate it like that, you&#8217;ll naturally handle unequal lengths and the final carry without special-case hacks.</p><p>Where intuition often fails: candidates sometimes stop when both pointers are <code>null</code> and forget about a remaining carry. Or they try to handle carry with complicated branching instead of one consistent formula. The formula is your anchor under pressure.</p><h1>&#128207; Constraints and what they tell us</h1><p>The lists have between 1 and 100 nodes each. That&#8217;s small, but don&#8217;t let it trick you into sloppy thinking.</p><p>In interviews, constraints are less about &#8220;<em>do we need an </em><code>O(n log n)</code><em> trick?</em>&#8221; and more about &#8220;<em>what approaches are appropriate and safe?</em>&#8221;</p><p>Here&#8217;s what the constraints imply:</p><ul><li><p><strong>Time complexity</strong>: a single pass over both lists is obviously fine. The natural solution is <code>O(max(m, n))</code>.</p></li><li><p><strong>Space complexity</strong>: you need a result list, so <code>O(max(m, n))</code> additional nodes is unavoidable. Beyond that, you should be <code>O(1)</code> extra space.</p></li><li><p><strong>Avoid integer conversion</strong>: even though 100 digits could fit in big integers in some languages, converting is usually considered a red flag in interviews. It dodges the linked-list aspect and can introduce overflow issues in languages without arbitrary precision.</p></li><li><p><strong>Input size isn&#8217;t huge, but correctness still matters</strong>: 100 nodes is enough to expose bugs in carry logic, termination conditions, and list construction.</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_!eTdq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89df6594-ce18-4408-9d12-35ff0151b976_2700x2484.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eTdq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89df6594-ce18-4408-9d12-35ff0151b976_2700x2484.png 424w, https://substackcdn.com/image/fetch/$s_!eTdq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89df6594-ce18-4408-9d12-35ff0151b976_2700x2484.png 848w, https://substackcdn.com/image/fetch/$s_!eTdq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89df6594-ce18-4408-9d12-35ff0151b976_2700x2484.png 1272w, https://substackcdn.com/image/fetch/$s_!eTdq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89df6594-ce18-4408-9d12-35ff0151b976_2700x2484.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eTdq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89df6594-ce18-4408-9d12-35ff0151b976_2700x2484.png" width="1456" height="1340" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/89df6594-ce18-4408-9d12-35ff0151b976_2700x2484.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1340,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:447341,&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://iam.slys.dev/i/187834298?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89df6594-ce18-4408-9d12-35ff0151b976_2700x2484.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_!eTdq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89df6594-ce18-4408-9d12-35ff0151b976_2700x2484.png 424w, https://substackcdn.com/image/fetch/$s_!eTdq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89df6594-ce18-4408-9d12-35ff0151b976_2700x2484.png 848w, https://substackcdn.com/image/fetch/$s_!eTdq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89df6594-ce18-4408-9d12-35ff0151b976_2700x2484.png 1272w, https://substackcdn.com/image/fetch/$s_!eTdq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89df6594-ce18-4408-9d12-35ff0151b976_2700x2484.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>Interviewers also use these constraints as a gentle prompt: &#8220;<em>This is meant to be straightforward</em>&#8221;. If you find yourself designing stacks, recursion, or complex data structures, pause and ask whether you&#8217;re solving a different problem than the one you were given.</p><h1>&#129300; The obvious first idea &#8212; and why it&#8217;s not enough</h1><p>The most common first idea is: &#8220;<em>Convert each linked list into an integer, add them, then convert back</em>&#8221;.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DLCB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a4d2505-fa67-47f9-8d05-c4bfde0fc494_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DLCB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a4d2505-fa67-47f9-8d05-c4bfde0fc494_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!DLCB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a4d2505-fa67-47f9-8d05-c4bfde0fc494_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!DLCB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a4d2505-fa67-47f9-8d05-c4bfde0fc494_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!DLCB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a4d2505-fa67-47f9-8d05-c4bfde0fc494_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DLCB!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a4d2505-fa67-47f9-8d05-c4bfde0fc494_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a4d2505-fa67-47f9-8d05-c4bfde0fc494_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:763023,&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://iam.slys.dev/i/187834298?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a4d2505-fa67-47f9-8d05-c4bfde0fc494_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DLCB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a4d2505-fa67-47f9-8d05-c4bfde0fc494_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!DLCB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a4d2505-fa67-47f9-8d05-c4bfde0fc494_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!DLCB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a4d2505-fa67-47f9-8d05-c4bfde0fc494_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!DLCB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a4d2505-fa67-47f9-8d05-c4bfde0fc494_1536x1024.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>It feels natural because that&#8217;s how humans add numbers: we think of whole numbers, not lists of digits. And in a scripting language with big integers, it might even work for these constraints.</p><p>But in interviews, this approach is usually not enough for three reasons:</p><p>First, it ignores what&#8217;s being tested. The problem is deliberately built around linked list traversal, incremental output construction, and carry handling. If you convert to integers, you&#8217;re side-stepping the core skill signal.</p><p>Second, it can be unsafe or awkward across languages. In many environments (or in lower-level languages), integers have fixed size. A 100-digit number will overflow any standard 64-bit type. You could use big integer libraries, but now your &#8220;<em>simple</em>&#8221; solution depends on library availability and adds noise to the interview.</p><p>Third, conversion introduces its own pitfalls: building strings, reversing, parsing, and then reconstructing the list. You end up with more code, more failure modes, and less clarity.</p><p>A strong candidate will often acknowledge the naive approach quickly - &#8220;<em>We could convert, but that risks overflow and misses the point</em>&#8221; - and then pivot to the real approach: simulate addition directly on the lists.</p><p>That pivot is an interview moment. It shows you can evaluate trade-offs rather than blindly implementing the first thought.</p><h1>&#128161; The key insight</h1><p>The key insight is that the &#8220;<em>reverse order</em>&#8221; representation is not a complication - it&#8217;s a gift.</p><p>If digits were stored most-significant first, you&#8217;d have a real problem: addition starts from the least-significant digit, so you&#8217;d need to reverse lists, use stacks, or recurse to reach the tail first.</p><p>But this problem hands you the digits starting from the least-significant place. That means you can add as you traverse, exactly like counting change from coins upward. No lookahead, no backtracking.</p><p>Once you accept that, the algorithm becomes almost mechanical:</p><ul><li><p>Maintain a <code>carry</code>.</p></li><li><p>Walk both lists in lockstep.</p></li><li><p>At each position, read the current digit from each list (or 0 if that list is finished).</p></li><li><p>Compute <code>sum = d1 + d2 + carry</code>.</p></li><li><p>Output <code>sum % 10</code>.</p></li><li><p>Update <code>carry = sum // 10</code>.</p></li><li><p>Continue until both lists are finished and <code>carry == 0</code>.</p></li></ul><p>This insight also tells you how to structure your code cleanly: one loop, one consistent update rule, and a dummy head node to simplify list construction.</p><p>In interviews, &#8220;<code>dummy head</code>&#8221; is often a differentiator. It&#8217;s not advanced, but it signals you&#8217;ve built lists before and you know how to avoid special-casing the first node.</p><div><hr></div><h3>&#128161; Did this mental shift click for you?</h3><p>What was your &#8220;<em>aha</em>&#8221; moment while reading this?</p><p>&#128172; Leave a comment - I&#8217;d love to know how you think about Add Two Numbers.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/thinking-clearly-in-interviews-add/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/thinking-clearly-in-interviews-add/comments"><span>Leave a comment</span></a></p><div><hr></div><h1>&#9881;&#65039; The final approach</h1><p>Narratively, here&#8217;s the approach you want to be able to explain smoothly:</p><p>You create a new linked list for the result. You keep a pointer (<code>tail</code>) to the last node in the result so you can append efficiently. You traverse <code>l1</code> and <code>l2</code> with pointers <code>p</code> and <code>q</code>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!D7pa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1ca18b0-dc37-48ec-a054-a18b9f9b433e_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!D7pa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1ca18b0-dc37-48ec-a054-a18b9f9b433e_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!D7pa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1ca18b0-dc37-48ec-a054-a18b9f9b433e_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!D7pa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1ca18b0-dc37-48ec-a054-a18b9f9b433e_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!D7pa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1ca18b0-dc37-48ec-a054-a18b9f9b433e_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!D7pa!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1ca18b0-dc37-48ec-a054-a18b9f9b433e_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b1ca18b0-dc37-48ec-a054-a18b9f9b433e_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1312780,&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://iam.slys.dev/i/187834298?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1ca18b0-dc37-48ec-a054-a18b9f9b433e_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!D7pa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1ca18b0-dc37-48ec-a054-a18b9f9b433e_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!D7pa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1ca18b0-dc37-48ec-a054-a18b9f9b433e_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!D7pa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1ca18b0-dc37-48ec-a054-a18b9f9b433e_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!D7pa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1ca18b0-dc37-48ec-a054-a18b9f9b433e_1536x1024.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>At each iteration:</p><ul><li><p>Let <code>x</code> be <code>p.val</code> if <code>p</code> exists, otherwise 0.</p></li><li><p>Let <code>y</code> be <code>q.val</code> if <code>q</code> exists, otherwise 0.</p></li><li><p>Let <code>s = x + y + carry</code>.</p></li><li><p>The digit to store is <code>s % 10</code>.</p></li><li><p>The next carry is <code>s / 10</code> (integer division).</p></li></ul><p>Append the digit to the result, move <code>p</code> and <code>q</code> forward if they exist, and repeat.</p><p>Correctness argument (the kind interviewers like):</p><ul><li><p>At each step you compute the correct digit for that place value because you include the carry from the previous place.</p></li><li><p>Carry is always 0 or 1 (since max digit sum is <code>9 + 9 + 1 = 19</code>), so it&#8217;s safe and bounded.</p></li><li><p>You terminate only when there are no digits left in either input and no carry left to propagate, ensuring the most-significant carry is not lost.</p></li></ul><p>Edge cases are naturally handled:</p><ul><li><p>One list longer than the other &#8594; missing digits treated as 0.</p></li><li><p>Final carry after both lists end &#8594; loop continues and appends it.</p></li><li><p>Inputs are <code>[0]</code> and <code>[0]</code> &#8594; result becomes <code>[0]</code>.</p></li></ul><p>This is the kind of solution that stays stable under pressure because it has one moving part: the carry and the loop.</p><h2>&#9201;&#65039; Performance under interview constraints</h2><p>Time complexity is <code>O(max(m, n))</code>. You touch each node in each list once, and do O(1) work per node.</p><p>Space complexity is <code>O(max(m, n))</code> for the output list. Extra working space (not counting the output) is <code>O(1)</code>: a few pointers and integers.</p><p>In a live interview, you should justify complexity in plain language:</p><ul><li><p>&#8220;<em>We traverse both lists once, so runtime is linear in the length of the longer list.</em>&#8221;</p></li><li><p>&#8220;<em>We allocate one node per digit in the result, which is at most one more than the longer input due to carry.</em>&#8221;</p></li></ul><p>Trade-offs are minimal here, but you can still show maturity by stating what you&#8217;re <em>not</em> doing:</p><ul><li><p>&#8220;<em>I&#8217;m not converting to integers to avoid overflow and unnecessary work.</em>&#8221;</p></li><li><p>&#8220;<em>I&#8217;m not using recursion because iteration is simpler and avoids stack depth concerns.</em>&#8221;</p></li></ul><p>That&#8217;s often enough to signal strong judgment without over-explaining.</p><h1>&#128187; Implementation</h1><p>Implementation is secondary to clarity of thought, but in interviews, correctness and readability are non-negotiable. The easiest way to write clean code here is:</p><ul><li><p>Use a dummy head node.</p></li><li><p>Keep a <code>tail</code> pointer for appending.</p></li><li><p>Use a loop condition that includes carry: <code>while p != null or q != null or carry != 0</code>.</p></li></ul><p>Also, keep your variable names boring. &#8220;<em>carry</em>&#8221;, &#8220;<em>dummy</em>&#8221;, &#8220;<em>tail</em>&#8221;, &#8220;<em>p</em>&#8221;, &#8220;<em>q</em>&#8221; are boring in a good way: they reduce cognitive load for both you and the interviewer.</p><div><hr></div><h3>&#9888;&#65039; Know someone preparing for interviews?</h3><p>Send this to them - it might save them from an avoidable mistake.</p><p>&#128257; Share this post with a friend grinding interview prep.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/thinking-clearly-in-interviews-add?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/thinking-clearly-in-interviews-add?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h2>&#128013; Python implementation</h2><p>In Python, the implementation reads very close to the algorithm. The main detail is defining a <code>ListNode</code> class and carefully moving pointers.</p><pre><code>from __future__ import annotations
from typing import Optional

class ListNode:
    def __init__(self, val: int = 0, next: Optional[&#8221;ListNode&#8221;] = None):
        self.val = val
        self.next = next

def addTwoNumbers(l1: ListNode, l2: ListNode) -&gt; ListNode:
    dummy = ListNode(0)
    tail = dummy

    carry = 0
    p, q = l1, l2

    while p is not None or q is not None or carry != 0:
        x = p.val if p is not None else 0
        y = q.val if q is not None else 0

        s = x + y + carry
        digit = s % 10
        carry = s // 10

        tail.next = ListNode(digit)
        tail = tail.next

        if p is not None:
            p = p.next
        if q is not None:
            q = q.next

    return dummy.next</code></pre><p>A couple of interview-friendly notes you can say while writing:</p><ul><li><p>&#8220;<em>The dummy node avoids a special case for the head of the result list.</em>&#8221;</p></li><li><p>&#8220;<em>The loop condition includes carry so we don&#8217;t drop a final carry digit.</em>&#8221;</p></li><li><p>&#8220;<em>When one list ends, I treat its digit as 0.</em>&#8221;</p></li></ul><p>If you want to be extra careful, you can mention that the algorithm supports <code>[0] + [0]</code> naturally, and that digits are guaranteed 0&#8211;9 so carry stays bounded.</p><h2>&#129408; Rust implementation</h2><p>Rust adds two kinds of pressure: ownership and the <code>Option&lt;Box&lt;ListNode&gt;&gt;</code> shape. The trick is not to fight it. Use references to traverse (<code>as_ref()</code>), and build the output list with a mutable tail pointer.</p><p>Below is a common, readable pattern: we keep <code>p</code> and <code>q</code> as <code>Option&lt;&amp;Box&lt;ListNode&gt;&gt;</code> (borrowed traversal), while we build the result list as owned nodes.</p><pre><code>#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ListNode {
    pub val: i32,
    pub next: Option&lt;Box&lt;ListNode&gt;&gt;,
}

impl ListNode {
    #[inline]
    pub fn new(val: i32) -&gt; Self {
        ListNode { val, next: None }
    }
}

pub fn add_two_numbers(
    l1: Option&lt;Box&lt;ListNode&gt;&gt;,
    l2: Option&lt;Box&lt;ListNode&gt;&gt;,
) -&gt; Option&lt;Box&lt;ListNode&gt;&gt; {
    let mut carry: i32 = 0;

    // Borrowed pointers for traversal (we don&#8217;t need to mutate inputs).
    let mut p = l1.as_ref();
    let mut q = l2.as_ref();

    // Dummy head for output.
    let mut dummy = Box::new(ListNode::new(0));
    let mut tail = &amp;mut dummy;

    while p.is_some() || q.is_some() || carry != 0 {
        let x = p.map(|n| n.val).unwrap_or(0);
        let y = q.map(|n| n.val).unwrap_or(0);

        let s = x + y + carry;
        let digit = s % 10;
        carry = s / 10;

        tail.next = Some(Box::new(ListNode::new(digit)));
        tail = tail.next.as_mut().unwrap();

        p = p.and_then(|n| n.next.as_ref());
        q = q.and_then(|n| n.next.as_ref());
    }

    dummy.next
}</code></pre><p>What to point out in an interview:</p><ul><li><p>We traverse by borrowing (<code>as_ref()</code>), which avoids moving ownership out of <code>l1</code>/<code>l2</code> during the loop.</p></li><li><p>We use a dummy head and a mutable reference <code>tail</code> to append in <code>O(1)</code>.</p></li><li><p><code>tail.next.as_mut().unwrap()</code> is safe here because we just set <code>tail.next</code> to <code>Some(...)</code>.</p></li></ul><p>If the interviewer asks about safety: integer operations are safe here because values are tiny (<code>0..9</code> plus carry), so no overflow concerns with <code>i32</code>.</p><h2>&#128995; Scala implementation</h2><p>In Scala, you typically model a linked list node with a class containing <code>var next</code> so you can build incrementally. The core logic remains the same: traverse, compute digit and carry, append.</p><pre><code>final class ListNode(var value: Int, var next: ListNode = null)

object Solution {
  def addTwoNumbers(l1: ListNode, l2: ListNode): ListNode = {
    val dummy = new ListNode(0)
    var tail = dummy

    var carry = 0
    var p = l1
    var q = l2

    while (p != null || q != null || carry != 0) {
      val x = if (p != null) p.value else 0
      val y = if (q != null) q.value else 0

      val s = x + y + carry
      val digit = s % 10
      carry = s / 10

      tail.next = new ListNode(digit)
      tail = tail.next

      if (p != null) p = p.next
      if (q != null) q = q.next
    }

    dummy.next
  }
}</code></pre><p>Design choices worth mentioning:</p><ul><li><p>This is intentionally iterative: it&#8217;s simpler and avoids recursion depth concerns.</p></li><li><p><code>dummy</code> and <code>tail</code> keep the append logic clean.</p></li><li><p>Using <code>null</code> for list termination is common when mirroring interview-style list node definitions; in production Scala you might prefer <code>Option</code>, but that adds verbosity that often isn&#8217;t helpful in a timed setting.</p></li></ul><h1>&#9888;&#65039; Common interview mistakes</h1><p>People rarely fail this problem because they don&#8217;t know how to add numbers. They fail because they lose control of the small moving parts while talking and coding at the same time.</p><p>Forgetting the final carry is the classic one. You get a correct-looking list for many inputs, but you break on things like <code>[5] + [5]</code> (should produce <code>[0, 1]</code>). The cure is structural: bake carry into the loop condition. Don&#8217;t try to &#8220;remember&#8221; to handle it afterward.</p><p>Mismanaging list traversal is another. Candidates sometimes advance pointers too early, or they read <code>p.val</code> without checking <code>p != null</code>. Again, the cure is to adopt a stable pattern: read digits with conditional expressions, then advance pointers at the end of the loop.</p><p>Building the result list with special cases is also common: &#8220;<em>If head is null set head, else append&#8230;</em>&#8221;. Under pressure, that&#8217;s where bugs breed. Dummy head is the antidote because it makes every append identical.</p><p>Finally, over-complicating the solution can be its own failure mode. If you find yourself reversing lists, creating stacks, or converting to strings, pause. Ask: &#8220;<em>Am I still doing digit-by-digit addition?</em>&#8221; If not, you&#8217;re probably leaving the clean path.</p><h1>&#128269; Edge cases interviewers love to ask about</h1><p>Interviewers often probe edge cases not to be tricky, but to see whether your solution is robust and whether you can reason about it calmly.</p><p>One list is much longer than the other is a favorite. For example: <code>[1] + [9,9,9]</code>. The correct behavior is to keep going after the shorter list ends and treat its digits as 0. Your loop condition and digit extraction should already handle this.</p><p>A final carry that extends the result is another standard check: <code>[9,9] + [1]</code> should yield <code>[0,0,1]</code>. When asked, you want to respond with confidence: &#8220;My loop continues while carry is non-zero, so it appends an extra node when needed.&#8221;</p><p>Inputs containing zeros are also asked, especially <code>[0] + [0]</code>. Some buggy solutions accidentally return an empty list or skip node creation. With the dummy-head approach, this case is naturally fine.</p><p>Occasionally, an interviewer asks about mutating inputs: &#8220;<em>Can you reuse nodes?</em>&#8221; In most interview settings, returning a new list is the expected approach. If asked, you can say: &#8220;<em>We could reuse nodes to reduce allocations, but it complicates the code and risks modifying inputs unexpectedly; I&#8217;d stick with a fresh list unless explicitly asked.</em>&#8221;</p><p>And sometimes they ask the &#8220;<em>what if</em>&#8221; variant: digits stored in forward order. The correct response is not to panic - it&#8217;s to identify the difference: forward order prevents left-to-right addition, so you&#8217;d need to reverse lists, use stacks, or use recursion. The important part is that you can articulate the dependency: <em>addition wants least-significant digits first</em>.</p><h1>&#127891; What this problem trains you to do</h1><p>This problem trains a very transferable interview skill: <strong>staying oriented when the representation is unfamiliar</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UfgY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1a7e23-91f7-400c-8b41-e332c92ed7b6_2174x1049.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UfgY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1a7e23-91f7-400c-8b41-e332c92ed7b6_2174x1049.png 424w, https://substackcdn.com/image/fetch/$s_!UfgY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1a7e23-91f7-400c-8b41-e332c92ed7b6_2174x1049.png 848w, https://substackcdn.com/image/fetch/$s_!UfgY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1a7e23-91f7-400c-8b41-e332c92ed7b6_2174x1049.png 1272w, https://substackcdn.com/image/fetch/$s_!UfgY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1a7e23-91f7-400c-8b41-e332c92ed7b6_2174x1049.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UfgY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1a7e23-91f7-400c-8b41-e332c92ed7b6_2174x1049.png" width="1456" height="703" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ba1a7e23-91f7-400c-8b41-e332c92ed7b6_2174x1049.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:703,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:136003,&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://iam.slys.dev/i/187834298?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1a7e23-91f7-400c-8b41-e332c92ed7b6_2174x1049.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_!UfgY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1a7e23-91f7-400c-8b41-e332c92ed7b6_2174x1049.png 424w, https://substackcdn.com/image/fetch/$s_!UfgY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1a7e23-91f7-400c-8b41-e332c92ed7b6_2174x1049.png 848w, https://substackcdn.com/image/fetch/$s_!UfgY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1a7e23-91f7-400c-8b41-e332c92ed7b6_2174x1049.png 1272w, https://substackcdn.com/image/fetch/$s_!UfgY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1a7e23-91f7-400c-8b41-e332c92ed7b6_2174x1049.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 real engineering, data is rarely handed to you in the ideal shape. It&#8217;s serialized, chunked, streamed, reversed, sparse, or wrapped in an API that forces a certain access pattern. The winning move is often to stop thinking &#8220;<em>this is weird</em>&#8221; and start thinking &#8220;<em>what process does this representation make natural?</em>&#8221;</p><p>Here, reverse-order digits make carry-based addition natural. You don&#8217;t need to fight the structure. You lean into it.</p><p>It also trains incremental construction and pointer discipline - skills that show up in many classic tasks: merging sorted lists, removing nodes, partitioning lists, building trees iteratively, and streaming algorithms where you produce output as you consume input.</p><p>Most importantly, it trains a style of reasoning that interviewers trust: a stable loop invariant (&#8220;<em>I&#8217;ve computed all digits up to this position correctly, carry represents overflow to the next position</em>&#8221;), a simple termination condition, and small, repeatable operations.</p><p>When you can explain that clearly, you&#8217;re not just &#8220;<em>solving the problem</em>&#8221;. You&#8217;re demonstrating that you can think like an engineer under observation.</p><h1>&#127793; Closing thoughts</h1><p>There&#8217;s a quiet confidence that comes from problems like this - not because they&#8217;re hard, but because they reward steadiness. The representation is a little unusual, and that&#8217;s enough to trigger doubt in the moment. The way through is to recognize the familiar process hiding underneath: add digits, carry, repeat.</p><p>If you practice explaining this solution out loud - carry logic, loop condition, why dummy head simplifies construction - you&#8217;ll find that the code almost writes itself. And that&#8217;s the real goal for live interviews: reduce the number of decisions you have to make while someone is watching.</p><div><hr></div><h3>&#129504; If you want to build real interview intuition&#8230;</h3><p>I write deep, calm breakdowns of classic problems - without tricks or memorization.</p><p>&#128236; Subscribe to get the next one directly.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[How Load Balancing really works — and why it is not just about splitting traffic]]></title><description><![CDATA[System design fundamentals]]></description><link>https://iam.slys.dev/p/how-load-balancing-really-works-and</link><guid isPermaLink="false">https://iam.slys.dev/p/how-load-balancing-really-works-and</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Tue, 03 Mar 2026 21:43:24 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!t6fh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t6fh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t6fh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!t6fh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!t6fh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!t6fh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t6fh!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2449540,&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://iam.slys.dev/i/176401638?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!t6fh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!t6fh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!t6fh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!t6fh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5441de9a-4a85-4939-9186-7106fbaeffae_1536x1024.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>&#9888;&#65039; <strong>Before we dive in &#8212; highly worth your attention</strong></p><div class="embedded-publication-wrap" data-attrs="{&quot;id&quot;:4097137,&quot;embedding_publication_id&quot;:null,&quot;name&quot;:&quot;Product with Attitude&quot;,&quot;logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!KJxv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f411cce-3771-42d9-965e-1c01efe464eb_986x986.png&quot;,&quot;base_url&quot;:&quot;https://karozieminski.substack.com&quot;,&quot;hero_text&quot;:&quot;AI Product Manager turning everyone into AI builders. I help you design, build and test your product, and feature it on StackShelf.app. I connect you with a supportive 10K+ community building and learning in public.&quot;,&quot;author_name&quot;:&quot;Karo (Product with Attitude)&quot;,&quot;show_subscribe&quot;:true,&quot;logo_bg_color&quot;:&quot;#ffffff&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="EmbeddedPublicationToDOMWithSubscribe"><div class="embedded-publication show-subscribe"><a class="embedded-publication-link-part" native="true" href="https://karozieminski.substack.com?utm_source=substack&amp;utm_campaign=publication_embed&amp;utm_medium=web"><img class="embedded-publication-logo" src="https://substackcdn.com/image/fetch/$s_!KJxv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f411cce-3771-42d9-965e-1c01efe464eb_986x986.png" width="56" height="56" style="background-color: rgb(255, 255, 255);"><span class="embedded-publication-name">Product with Attitude</span><div class="embedded-publication-hero-text">AI Product Manager turning everyone into AI builders. I help you design, build and test your product, and feature it on StackShelf.app. I connect you with a supportive 10K+ community building and learning in public.</div><div class="embedded-publication-author-name">By Karo (Product with Attitude)</div></a><form class="embedded-publication-subscribe" method="GET" action="https://karozieminski.substack.com/subscribe?"><input type="hidden" name="source" value="publication-embed"><input type="hidden" name="autoSubmit" value="true"><input type="email" class="email-input" name="email" placeholder="Type your email..."><input type="submit" class="button primary" value="Subscribe"></form></div></div><p>This week I want to highlight <em><strong>Product with Attitude</strong></em> by <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Karo (Product with Attitude)&quot;,&quot;id&quot;:27968736,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!aG8-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F599e664e-d6b8-4249-814a-4feadc68d706_1096x1096.png&quot;,&quot;uuid&quot;:&quot;9a794cfe-75a9-4c05-b721-aa3c2806071c&quot;}" data-component-name="MentionToDOM"></span> - a newsletter rooted in practical AI product thinking. Karo writes about how to <em>design, build, and test with clear purpose</em>, not just hype. If you want less noise and more frameworks that actually improve how you build with AI, this is one of the feeds I trust most.</p><div class="pullquote"><p>Imagine you run a restaurant with three chefs.<br>Customers keep coming in, and the waiter (your load balancer) has to assign orders.<br>In a perfect world, each chef gets an equal share.<br>But in reality? One chef is faster, another is stuck with a complicated dish, and the third just went out for coffee.</p><p>The waiter has to make quick, smart choices to keep everyone happy.</p></div><p>In a computer system, a <strong>load balancer</strong> plays the role of that attentive waiter. It&#8217;s not just blindly splitting work evenly - it&#8217;s making <em>intelligent decisions</em> about where each &#8220;<em>order</em>&#8221; (network request) should go. The goal is to keep the service running smoothly, just like our waiter keeps the restaurant flowing. This light metaphor hints at a key idea: <strong>load balancing is about intelligent flow management, not just fairness.</strong> Before diving into tech details, keep that intuitive image in mind - smart coordination to prevent overload and ensure everyone (and every server) stays productive.</p><h1>&#129513; What Load Balancing actually is</h1><p>Let&#8217;s start simple. <strong>Load balancing means distributing incoming work across multiple servers or instances</strong>.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> Instead of one server handling all requests (and risking overload), a group of servers share the job. The goal isn&#8217;t just to &#8220;<em>spread the load</em>&#8221; evenly for fairness - it&#8217;s to ensure a few critical benefits.</p><p><strong>Performance.</strong> No single server gets overwhelmed and slow. By sharing requests, the system can respond faster. In fact, a load balancer ensures no one server bears too many requests, which directly improves application speed and responsiveness.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a></p><p><strong>Reliability (High availability).</strong> If one server fails or needs maintenance, others can take over. With a good load balancer, one node can fail without taking down the whole system. The site stays up because traffic is redirected to healthy servers (this failover mechanism is often automatic).</p><p><strong>Scalability.</strong> You can add more servers as demand grows, and the load balancer will include them in the rotation. This horizontal scaling means you can serve more users just by adding new instances, with minimal disruption.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vnez!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f658b3-f598-4b73-9fee-ed1ce3c2047f_2395x850.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vnez!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f658b3-f598-4b73-9fee-ed1ce3c2047f_2395x850.png 424w, https://substackcdn.com/image/fetch/$s_!vnez!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f658b3-f598-4b73-9fee-ed1ce3c2047f_2395x850.png 848w, https://substackcdn.com/image/fetch/$s_!vnez!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f658b3-f598-4b73-9fee-ed1ce3c2047f_2395x850.png 1272w, https://substackcdn.com/image/fetch/$s_!vnez!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f658b3-f598-4b73-9fee-ed1ce3c2047f_2395x850.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vnez!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f658b3-f598-4b73-9fee-ed1ce3c2047f_2395x850.png" width="1200" height="426.0989010989011" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/67f658b3-f598-4b73-9fee-ed1ce3c2047f_2395x850.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:517,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:164856,&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://iam.slys.dev/i/176401638?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f658b3-f598-4b73-9fee-ed1ce3c2047f_2395x850.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_!vnez!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f658b3-f598-4b73-9fee-ed1ce3c2047f_2395x850.png 424w, https://substackcdn.com/image/fetch/$s_!vnez!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f658b3-f598-4b73-9fee-ed1ce3c2047f_2395x850.png 848w, https://substackcdn.com/image/fetch/$s_!vnez!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f658b3-f598-4b73-9fee-ed1ce3c2047f_2395x850.png 1272w, https://substackcdn.com/image/fetch/$s_!vnez!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f658b3-f598-4b73-9fee-ed1ce3c2047f_2395x850.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 short, load balancing is the technique that makes a cluster of machines behave like one reliable, fast <strong>service</strong>. It&#8217;s like an orchestra conductor - they don&#8217;t play an instrument themselves, but by directing each musician at the right time, they ensure the performance (your application) is harmonious and can handle a big audience. The conductor (load balancer) doesn&#8217;t strum a violin, yet without them coordinating, the music would fall apart.</p><div class="pullquote"><p><strong>Key point:</strong> Load balancing isn&#8217;t a luxury; it&#8217;s a cornerstone of modern system design. Without it, a busy service can suffer single points of failure, slowdowns, or total crashes if one server can&#8217;t handle a spike in traffic. With it, we get a system that&#8217;s <em>steady under pressure</em> - as our restaurant story showed, it&#8217;s about managing the flow intelligently so no single chef (or server) is in the weeds.</p></div><h1>&#129504; Where Load Balancing happens &#8212; layers and levels</h1><p>Load balancing doesn&#8217;t only happen in one place or one way. It can occur at different layers of the network/application stack, each with a different scope and method.</p><div><hr></div><p><strong>Enjoying the metaphors so far? I write posts just like this - breaking down tech concepts with plain language and real-world comparisons.</strong></p><p>&#128161; Want more like this delivered to your inbox?</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p><strong>Layer 4 (Transport layer) Load Balancing.</strong> This operates at the TCP/UDP level. A Layer 4 load balancer routes traffic based on low-level info like IP address and port, without understanding the content of the request.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> It&#8217;s essentially doing packet forwarding. Think of it as <em>fast and dumb</em> &#8211; it just knows &#8220;<em>this packet goes to Server 1, next to Server 2</em>&#8221;, etc., and doesn&#8217;t peek inside the HTTP request. Because it&#8217;s simple, it&#8217;s very high-performance (blazingly fast). Classic network appliances and some cloud load balancers (like AWS Classic ELB or Network LB) work at L4, as do tools like HAProxy in TCP mode. An L4 balancer can&#8217;t, for example, route traffic based on an HTTP URL &#8211; it just sees addresses and ports.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a> But it&#8217;s great for raw speed and is often used for lower-level balancing of non-HTTP protocols or as a first-line balancer for lots of connections.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YrXa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94158c12-6ff2-4739-8edc-1da24ee38d8a_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YrXa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94158c12-6ff2-4739-8edc-1da24ee38d8a_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!YrXa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94158c12-6ff2-4739-8edc-1da24ee38d8a_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!YrXa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94158c12-6ff2-4739-8edc-1da24ee38d8a_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!YrXa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94158c12-6ff2-4739-8edc-1da24ee38d8a_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YrXa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94158c12-6ff2-4739-8edc-1da24ee38d8a_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/94158c12-6ff2-4739-8edc-1da24ee38d8a_1536x1024.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;:1915308,&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://iam.slys.dev/i/176401638?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94158c12-6ff2-4739-8edc-1da24ee38d8a_1536x1024.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_!YrXa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94158c12-6ff2-4739-8edc-1da24ee38d8a_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!YrXa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94158c12-6ff2-4739-8edc-1da24ee38d8a_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!YrXa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94158c12-6ff2-4739-8edc-1da24ee38d8a_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!YrXa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94158c12-6ff2-4739-8edc-1da24ee38d8a_1536x1024.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><strong>Layer 7 (Application layer) Load Balancing.</strong> This works at the HTTP/HTTPS (or gRPC, etc.) layer. A Layer 7 load balancer actually looks at the content of the requests &#8211; URLs, headers, cookies, etc. &#8211; and can make smarter routing decisions. For example, it could send all image requests to a dedicated image server pool, or route <code>/api/*</code> paths to a different cluster than <code>/web/*</code>. Because it operates with full knowledge of HTTP, a Layer 7 balancer can do things like TLS termination (decrypting HTTPS), cookie-based &#8220;<em>sticky sessions</em>&#8221;, and content-based routing. Nginx, Traefik, Envoy, or cloud Application Load Balancers (ALB) fall in this category. The trade-off: <strong>L7 balancing adds some overhead</strong> &#8211; it&#8217;s doing more work (maintaining two TCP connections, parsing requests) which can add a bit of latency. But in modern systems, this overhead is usually small (milliseconds or less<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a> for each request) compared to the flexibility gained. In practice, most web applications use L7 load balancing because it allows intelligent control at the HTTP level. </p><div class="pullquote"><p>In our restaurant analogy, this is like a head waiter who knows not just how many orders each chef has, but also what type of dish each order is &#8211; allowing them to assign by complexity, not just count.</p></div><p><strong>DNS Load Balancing. </strong>This is a load balancing at the <strong>DNS level</strong>, often called <em>round-robin DNS</em>. It&#8217;s actually the simplest form of load distribution. Here, multiple IP addresses are registered for one domain name. Each DNS query for the name may get a different IP from the list, thereby spreading traffic across servers geographically or by simple rotation.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a> For example, <em>www.example.com</em> might resolve to <strong>IP1</strong> on one lookup and <strong>IP2</strong> on the next. Big CDNs and global services use DNS load balancing to direct users to a server cluster nearest to them. <strong>Pros:</strong> It&#8217;s simple and doesn&#8217;t require an explicit proxy in the middle. <strong>Cons:</strong> It&#8217;s very <em>coarse</em>. DNS caching can cause uneven distribution (some clients will keep hitting the same IP). And if one server goes down, DNS might still hand out its IP unless additional measures (health-check aware DNS or short TTLs) are in place. So, round-robin DNS helps with simple global load spreading, but by itself it doesn&#8217;t account for server load or even guarantee a down server is avoided. Many services combine DNS load balancing with other layers (for instance, DNS directs you to a regional load balancer, which then does L7 balancing).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2G6-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda6aa6f1-cd2d-4961-b2fb-a93077ff2856_2592x2001.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2G6-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda6aa6f1-cd2d-4961-b2fb-a93077ff2856_2592x2001.png 424w, https://substackcdn.com/image/fetch/$s_!2G6-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda6aa6f1-cd2d-4961-b2fb-a93077ff2856_2592x2001.png 848w, https://substackcdn.com/image/fetch/$s_!2G6-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda6aa6f1-cd2d-4961-b2fb-a93077ff2856_2592x2001.png 1272w, https://substackcdn.com/image/fetch/$s_!2G6-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda6aa6f1-cd2d-4961-b2fb-a93077ff2856_2592x2001.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2G6-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda6aa6f1-cd2d-4961-b2fb-a93077ff2856_2592x2001.png" width="1456" height="1124" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/da6aa6f1-cd2d-4961-b2fb-a93077ff2856_2592x2001.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1124,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:224933,&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://iam.slys.dev/i/176401638?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda6aa6f1-cd2d-4961-b2fb-a93077ff2856_2592x2001.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_!2G6-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda6aa6f1-cd2d-4961-b2fb-a93077ff2856_2592x2001.png 424w, https://substackcdn.com/image/fetch/$s_!2G6-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda6aa6f1-cd2d-4961-b2fb-a93077ff2856_2592x2001.png 848w, https://substackcdn.com/image/fetch/$s_!2G6-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda6aa6f1-cd2d-4961-b2fb-a93077ff2856_2592x2001.png 1272w, https://substackcdn.com/image/fetch/$s_!2G6-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda6aa6f1-cd2d-4961-b2fb-a93077ff2856_2592x2001.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><strong>Client-Side Load Balancing.</strong> This flips the scenario: instead of a central load balancer dispatching requests, the <em>client</em> (or a client-side library) decides which server to talk to. In microservices architectures, it&#8217;s common to use <strong>service discovery</strong> where each service instance registers itself (with a service registry like Eureka, Consul, etc.). When Service A wants to call Service B, it asks the registry for all available endpoints of B. Then <em>Service A itself</em> picks one of B&#8217;s instances (often at random or round-robin) to send the request to.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a> This is client-side load balancing. <strong>Advantages:</strong> It removes the extra network hop - a dedicated load balancer in between services isn&#8217;t needed, which can improve performance. Each service call goes direct to a chosen instance. <strong>Disadvantages:</strong> The logic to choose an instance now lives in the client or a library. That means complexity in each service (or language-specific libraries), and if the list of servers changes, clients need to update their info. Also, each client must implement health checking or retry logic on failures. Systems like Netflix OSS (Ribbon, Eureka) or gRPC&#8217;s built-in load balancing use this approach. <em>Think of it like removing the waiter entirely and handing each customer a list of chefs to choose from; it can work well if customers are smart about picking an available chef.</em> In practice, client-side LB is often used inside clusters (microservice-to-microservice calls), while external traffic from users is handled by centralized L7 or L4 load balancers.</p><p>To visualize a simple case of load balancing in action:</p><pre><code><code>User &#8594; Load Balancer &#8594; Servers (S1, S2, S3)</code></code></pre><p>Here, the user hits one address (the load balancer), and the balancer then forwards the request to one of the servers S1, S2, or S3. That could be happening at L4 or L7 as described. In complex systems, you might even see <em>multiple</em> layers of load balancing: e.g., DNS directs you to a region, a layer-4 balancer spreads load among data centers, and a layer-7 balancer routes within a cluster. Each layer has its role in keeping traffic flowing efficiently.</p><h1>&#9881;&#65039; Common Load Balancing strategies</h1><p>How does a load balancer actually decide where to send each request? There are many <strong>load balancing algorithms</strong> in use, each with different trade-offs. The choice can be static or dynamic. Let&#8217;s break down some real strategies and when they&#8217;re used.</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/DSuOB/2/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8152dd88-c88d-4f36-b020-0101be81a5a3_1220x3106.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5b0d6209-6824-4572-a8c8-eff15b1dee51_1220x3176.png&quot;,&quot;height&quot;:1021,&quot;title&quot;:&quot;Load Balancing strategies&quot;,&quot;description&quot;:&quot;&quot;,&quot;belowTheFold&quot;:true}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/DSuOB/2/" width="730" height="1021" frameborder="0" scrolling="no" loading="lazy"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><blockquote><p>&#10145;&#65039; <strong>Insight:</strong> Load balancing isn&#8217;t just about picking the next server in line. It&#8217;s about <strong>context-aware decision-making.</strong> A simple round-robin might assume every request is equal, but as we know, in reality one &#8220;<em>order</em>&#8221; might be a quick salad and another a complex entr&#233;e. Modern load balancers can be surprisingly sophisticated: some cloud load balancers use algorithms that consider things like the fewest <em>outstanding</em> requests (to account for slow vs fast requests)<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a>, or combine multiple factors. The right strategy depends on your scenario &#8211; simple algorithms for simple needs, and adaptive ones when you need that extra &#8220;<em>brainpower</em>&#8221; in the balancer to keep things flowing smoothly.</p></blockquote><h1>&#127754; Load Balancing and state</h1><p>One tricky aspect in load balanced environments is <strong>user session state</strong>. Imagine a user logs into your website &#8211; their session data (e.g. login status, shopping cart) might be stored in memory on the server that handled the login. Now, what if their next request goes to a <em>different</em> server via the load balancer? That second server might not know about the user&#8217;s session, and suddenly the user appears logged out or their cart is empty. This can be a big problem if not addressed.</p><p>There are a few ways to handle state in a load-balanced system.</p><p><strong>Sticky Sessions (Session affinity).</strong> This is like telling the load balancer, &#8220;<em>once a user goes to a server, keep them on that server for all subsequent requests</em>&#8221;. The load balancer will identify the user (often via a cookie or the client&#8217;s IP) and always route them to the same backend server &#8211; effectively <strong>binding the session to that server</strong>.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-9" href="#footnote-9" target="_self">9</a> It&#8217;s like the waiter remembering you and always taking you to the same table with the same chef because that chef knows your ongoing order. Many load balancers support this: they might set a special cookie on the first response that tags the server, and read it on future requests to enforce the stickiness. <strong>Pro:</strong> It solves the immediate consistency problem &#8211; that user&#8217;s session data stays warm on one server. <strong>Cons:</strong> It can lead to uneven load (some servers get lots of &#8220;<em>sticky</em>&#8221; users, others get few) and <strong>reduces fault tolerance</strong> (if that one server crashes, those users&#8217; sessions are lost).<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-10" href="#footnote-10" target="_self">10</a> Essentially, sticky sessions trade off some of the benefits of load balancing for the sake of state. It&#8217;s often considered a quick fix or a smaller-scale solution. For example, in a pinch, you might enable sticky sessions on an AWS ELB so that users don&#8217;t bounce between instances during a short session.</p><p><strong>External Session stores.</strong> A more scalable approach is to <strong>decouple the session data from any single web server</strong>. Instead of storing session info in each server&#8217;s memory, store it in a shared place that all servers can access. This could be a database, a distributed cache like Redis or Memcached, or another dedicated session service. Then any server can handle any request because they all consult the central session store. For instance, in a PHP app, you might configure sessions to go to a Redis cluster. Or in Java, use a shared database or in-memory data grid. This way, it doesn&#8217;t matter which server the load balancer picks &#8211; the user session is the same. <strong>Pro:</strong> You regain full load balancing freedom; truly any request can go anywhere, and losing one server doesn&#8217;t drop sessions (they&#8217;re in the central store). <strong>Con:</strong> The central session store can become a new bottleneck or point of failure if not managed (though these systems are usually made robust). Also, there&#8217;s a performance cost to reading/writing session data over the network. But in practice, many large systems use this approach because it keeps the web tier stateless. In our restaurant analogy, this is like <em>writing down the order and customer preferences in a central book</em> that any chef can read &#8211; so even if the customer moves to a different table, the new chef can pick up where the last left off.</p><p><strong>Client-Side Sessions (Tokens).</strong> Another modern solution is to avoid server-held session state altogether by using something like <strong>JWTs (JSON Web Tokens)</strong> or other signed tokens. In this model, when a user logs in, the server gives the client a token (often encoded with user info and an expiration, and cryptographically signed). On subsequent requests, the client (browser/app) sends this token, and any server can validate it and know who the user is (and maybe some basic info) <em>without</em> needing to look up anything in memory or database. This is essentially a stateless session &#8211; the state is carried by the client. It&#8217;s popular in microservice and API designs. <strong>Pros:</strong> Truly stateless on the server side &#8211; any server can serve any request, no dependency on shared caches or sticky routing. <strong>Cons:</strong> You can&#8217;t easily <em>invalidate</em> a JWT (except by expiration) unless you keep a blacklist, and if you store too much in it, the token can get large. Also, for very sensitive data, you might still prefer a server-side store. Nonetheless, this approach complements load balancing by eliminating the whole issue of &#8220;<em>which server has the session</em>&#8221;.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ms7Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd2e0d0-7502-4615-87a2-adeabfae16b5_1511x1508.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ms7Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd2e0d0-7502-4615-87a2-adeabfae16b5_1511x1508.png 424w, https://substackcdn.com/image/fetch/$s_!ms7Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd2e0d0-7502-4615-87a2-adeabfae16b5_1511x1508.png 848w, https://substackcdn.com/image/fetch/$s_!ms7Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd2e0d0-7502-4615-87a2-adeabfae16b5_1511x1508.png 1272w, https://substackcdn.com/image/fetch/$s_!ms7Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd2e0d0-7502-4615-87a2-adeabfae16b5_1511x1508.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ms7Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd2e0d0-7502-4615-87a2-adeabfae16b5_1511x1508.png" width="1456" height="1453" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dfd2e0d0-7502-4615-87a2-adeabfae16b5_1511x1508.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1453,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:161946,&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://iam.slys.dev/i/176401638?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd2e0d0-7502-4615-87a2-adeabfae16b5_1511x1508.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_!ms7Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd2e0d0-7502-4615-87a2-adeabfae16b5_1511x1508.png 424w, https://substackcdn.com/image/fetch/$s_!ms7Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd2e0d0-7502-4615-87a2-adeabfae16b5_1511x1508.png 848w, https://substackcdn.com/image/fetch/$s_!ms7Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd2e0d0-7502-4615-87a2-adeabfae16b5_1511x1508.png 1272w, https://substackcdn.com/image/fetch/$s_!ms7Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd2e0d0-7502-4615-87a2-adeabfae16b5_1511x1508.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>Now back to <strong>sticky sessions</strong>, since that&#8217;s a direct load-balancing feature: It&#8217;s worth noting sticky sessions are often considered a <strong>necessary evil</strong> or a quick workaround. If used, one should be aware that it breaks pure load distribution. For example, if 100 users all happen to get &#8220;<em>stuck</em>&#8221; to Server A because they all hit it around the same time, Server A will carry all their load, while Server B sits idle &#8211; thus defeating the purpose of balancing. Additionally, if that server A goes down, those 100 users&#8217; sessions vanish (they were never saved elsewhere). Real-world story: an e-commerce site once had a sticky-session setup; when one server &#8220;<em>zombied</em>&#8221; (stopped actually working but didn&#8217;t fully crash), the load balancer&#8217;s health check was naive and kept sending that user&#8217;s traffic to it. Those users got a bad experience (lost carts, errors) because the LB <em>insisted</em> they go to the same dead server.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-11" href="#footnote-11" target="_self">11</a> The lesson: if you do sticky, <strong>make sure to externalize critical state</strong> or have a backup plan.</p><p><strong>Alternatives Recap:</strong> The more robust solution is to design apps to be stateless or use shared state. For instance, one can store user session data in a fast key-value store (like Redis) so that any server can retrieve it. That way, the load balancer is free to truly balance every request independently. Many frameworks have easy hooks for this (e.g., Django can use cache-based sessions, PHP can use memcached for session storage<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-12" href="#footnote-12" target="_self">12</a>, etc.). The use of JWTs or tokens has also become very common in RESTful API scenarios &#8211; it sidesteps the whole sticky session issue elegantly.</p><p>To use the analogy given: <strong>sticky sessions</strong> are like the waiter remembering <em>you specifically</em> and always seating you at the same table (helpful if your food was left there). A <strong>central session store</strong> is like the whole restaurant having a shared knowledge base (&#8220;<em>Kitchen Display System</em>&#8221;) of what each regular customer is doing &#8211; so any waiter or chef can serve you seamlessly. The latter is more scalable in a busy restaurant chain!</p><p>For beginners: the main takeaway is that load balancers by default don&#8217;t remember anything about users &#8211; which is good for stateless scaling, but you must architect your app accordingly. If you have user-specific state that can&#8217;t be lost, plan for it. Either enable session affinity on the LB (with the caveats noted) or better, keep the app stateless across servers by not tying user data to one server&#8217;s memory.</p><h1>&#128678; Load Balancing in the real world &#8212; from Nginx to Envoy</h1><p>Thus far, we&#8217;ve talked conceptually. Let&#8217;s connect this to real tools and technologies you might encounter.</p><p><strong>Traditional Software Load Balancers (L4/L7):</strong> Examples include <strong>Nginx</strong>, <strong>HAProxy</strong>, and <strong>Apache Traffic Server</strong>, among others. These are software applications you deploy on servers (or use as a service) that implement load balancing. Nginx and HAProxy are extremely popular open-source solutions. Nginx often acts as a reverse proxy and load balancer (mostly L7, but can do L4) &#8211; in fact, as of a few years ago, Nginx was estimated to handle around 25% of the traffic of the top million websites<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-13" href="#footnote-13" target="_self">13</a>, showing how widely it&#8217;s used in the wild. HAProxy is another battle-tested load balancer (the name stands for High Availability Proxy). These solutions can be deployed on commodity hardware, which historically was a big shift from the older model of <strong>hardware load balancers</strong>. A decade or two ago, companies commonly used hardware appliances (like F5 Big-IP or Citrix NetScaler) &#8211; specialized devices in data centers &#8211; to do load balancing. Now, software like Nginx/HAProxy on standard servers (or containers) does the job, which is more flexible and cost-effective.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-14" href="#footnote-14" target="_self">14</a> You can even run multiple instances for redundancy. For example, you might have two HAProxy servers in active-passive mode &#8211; if one goes down, the other takes over, to avoid the LB itself being a single point of failure.</p><p><strong>Modern Reverse Proxies and Edge Balancers:</strong> <strong>Envoy</strong> is a newer proxy developed at Lyft, now widely used, especially in cloud-native environments. Envoy supports advanced L7 features and is designed for microservices architecture. It&#8217;s often used in what&#8217;s called a <strong>service mesh</strong> (more on that in a second). <strong>Traefik</strong> is another modern L7 balancer that integrates well with container environments (like Docker/Kubernetes), auto-discovering services. These modern proxies not only do load balancing but also things like <em>circuit breaking, retries, and detailed metrics</em>. They blur the line between &#8220;<em>just a load balancer</em>&#8221; and an &#8220;<em>application traffic management layer</em>&#8221;. For example, <strong>NGINX Plus</strong> (the paid version of Nginx) and Envoy both can do adaptive health checks, content-based routing, and even act as API gateways.</p><p><strong>Service Mesh (Envoy/Istio):</strong> In cloud-native microservices, a pattern emerged: instead of each service doing client-side load balancing or having one big gateway, you can run a small &#8220;<em>sidecar</em>&#8221; proxy next to each service instance. Istio is a popular service mesh, and it uses Envoy proxies as sidecars. In an Istio service mesh, all traffic from Service A to Service B goes through Envoy, which can load balance among B&#8217;s instances, handle retries, and enforce policies. Essentially, Envoy in this context is doing client-side load balancing on behalf of the service, but it&#8217;s centrally configured. Envoy supports many algorithms (round robin, least request, etc., even &#8220;<em>ring hash</em>&#8221; for consistent hashing).<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-15" href="#footnote-15" target="_self">15</a> It also brings features like mutual TLS, traffic shadowing, etc., showing how load balancing is now part of a larger <strong>traffic control fabric</strong>. The key takeaway: tools like Envoy and Istio move load balancing into software that is deeply integrated with your application environment (Kubernetes, for example). They treat load balancing not as a separate appliance but as a built-in capability of your platform. This gives a lot of power: Istio/Envoy can, say, detect if one instance is slow and steer traffic away, or do A/B testing by routing 5% of users to a new service version. It&#8217;s beyond simple load splitting - it&#8217;s <em>intentional routing</em> as part of architecture.</p><p><strong>Cloud Provider Load Balancers:</strong> All major cloud providers offer managed load balancing services. For example, AWS has the Elastic Load Balancer (ELB) family: Classic LB, Application LB (ALB), Network LB (NLB), and newer Gateway LB. Google Cloud has Cloud Load Balancing (with global anycast capability), Azure has Azure Load Balancer and Application Gateway, etc. These services are essentially &#8220;<em>load balancers as a service</em>&#8221;. You don&#8217;t manage the software or VMs directly; the cloud handles it and usually provides high availability by default. One strong advantage is integration: e.g., an AWS ALB can integrate with auto-scaling groups and target groups so that new EC2 instances register themselves automatically. Some cloud load balancers can do things that are hard to replicate on-prem, like Google&#8217;s Cloud Load Balancing uses a <strong>single anycast IP</strong> that fronts servers in multiple regions around the world<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-16" href="#footnote-16" target="_self">16</a> - meaning a user is automatically routed to the nearest healthy instance globally, with seamless failover if an entire region goes down. Cloud LBs also often come with built-in health checks and monitoring. They might cost money per hour and per GB of data, but they save ops work.</p><p><strong>Hardware Load Balancers (Legacy and Niche):</strong> It&#8217;s worth noting that hardware load balancers are still around in some enterprises (for very high throughput needs or legacy reasons). These are physical devices with custom chips (ASICs) that can do extremely fast L4 routing, and often L7 too. They often come with features like SSL offloading at huge scale. Companies like F5 Networks dominated this space. However, the trend has been moving away from these because of cost and flexibility. Commodity servers and cloud LBs have eaten a lot of that lunch. Still, if you ever hear &#8220;<em>ADC</em>&#8221; (Application Delivery Controller), that often refers to these advanced hardware (or virtual appliance) load balancers that do a ton (load balancing, compression, security, etc.). In our analogy, this is like having a super-expensive robot waiter; it can serve really fast but might be hard to change its behavior without buying a new one.</p><p><strong>Global Traffic Managers:</strong> These are a layer above - often using DNS - to distribute traffic across geographies. For instance, if you have one deployment in US and one in Europe, a global load balancer can send European users to the EU servers and US users to US servers (reducing latency). Cloudflare, AWS Route 53 with latency-based routing, or Azure Traffic Manager serve this role. It&#8217;s load balancing at the <em>internet</em> level.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O6Nm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb8d33dd-0e88-4cba-a487-8801ce1118d2_2586x1147.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O6Nm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb8d33dd-0e88-4cba-a487-8801ce1118d2_2586x1147.png 424w, https://substackcdn.com/image/fetch/$s_!O6Nm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb8d33dd-0e88-4cba-a487-8801ce1118d2_2586x1147.png 848w, https://substackcdn.com/image/fetch/$s_!O6Nm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb8d33dd-0e88-4cba-a487-8801ce1118d2_2586x1147.png 1272w, https://substackcdn.com/image/fetch/$s_!O6Nm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb8d33dd-0e88-4cba-a487-8801ce1118d2_2586x1147.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!O6Nm!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb8d33dd-0e88-4cba-a487-8801ce1118d2_2586x1147.png" width="1200" height="532.4175824175824" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/db8d33dd-0e88-4cba-a487-8801ce1118d2_2586x1147.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:646,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:260247,&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://iam.slys.dev/i/176401638?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb8d33dd-0e88-4cba-a487-8801ce1118d2_2586x1147.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_!O6Nm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb8d33dd-0e88-4cba-a487-8801ce1118d2_2586x1147.png 424w, https://substackcdn.com/image/fetch/$s_!O6Nm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb8d33dd-0e88-4cba-a487-8801ce1118d2_2586x1147.png 848w, https://substackcdn.com/image/fetch/$s_!O6Nm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb8d33dd-0e88-4cba-a487-8801ce1118d2_2586x1147.png 1272w, https://substackcdn.com/image/fetch/$s_!O6Nm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb8d33dd-0e88-4cba-a487-8801ce1118d2_2586x1147.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>A quick reflection on evolution: <strong>Load balancers used to be boxes in a rack</strong>, specialized and static. Now, they&#8217;re everywhere in software. In Kubernetes, every Service is like a little load balancer for pods. In front-end applications, CDNs act as load balancers for edge servers. We even have load balancing algorithms in libraries. The concept has permeated every layer because modern apps demand flexibility and resilience. Load balancing is no longer just an appliance or a single point in architecture - it&#8217;s a distributed, software-defined function that can happen at multiple points in the data flow.</p><p>To put it another way, <strong>the load balancer has become part of our &#8220;</strong><em><strong>service fabric</strong></em><strong>&#8221;.</strong> It&#8217;s not just forwarding packets; it&#8217;s often making decisions based on application behavior (URL patterns, user identity, etc.). For example, an AWS ALB can route by HTTP path to different target groups (like an internal API vs. a web front-end), essentially acting on application logic. This means as an engineer, you now think of the load balancer almost as an extension of your application - another component you configure with rules, much like you would your application code. The positive side is powerful traffic control; the downside is more complexity to manage.</p><div><hr></div><p><strong>Which tools are </strong><em><strong>you</strong></em><strong> using?</strong></p><p>Have you tried Nginx, Envoy, or maybe you&#8217;re wrestling with AWS&#8217;s ELB quirks? I&#8217;d love to hear your thoughts, questions, or stories from the trenches.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/how-load-balancing-really-works-and/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/how-load-balancing-really-works-and/comments"><span>Leave a comment</span></a></p><div><hr></div><p>In summary, <strong>open-source tools</strong> (Nginx, HAProxy, Envoy, Traefik), <strong>cloud-managed services</strong> (ELB/ALB, etc.), and <strong>software proxies in meshes</strong> are all real-world manifestations of load balancing. Often, a production system uses a combination: e.g., DNS to geo-distribute, a cloud LB at each region&#8217;s entry, and Envoy/sidecars for internal service-to-service balancing. The principles remain the same, but the implementation might be layered.</p><p>One more real-world example: <strong>Stripe</strong> (the payment company) has mentioned using L7 load balancing to route API versions and for safe deployments. Netflix famously had their Zuul (now they use Envoy) front door doing a lot of smart routing and combined that with mid-tier load balancers. These show that beyond just spreading load, companies leverage load balancers as strategic control points in the system.</p><h1>&#129518; Load Balancing and scaling</h1><p>Load balancing and horizontal <strong>scaling</strong> go hand-in-hand. If you want to scale out (add more servers to handle increased traffic), you almost always need a load balancer to distribute work to those new servers. Conversely, a load balancer without multiple servers isn&#8217;t scaling out much. They are two sides of building a scalable system:</p><ul><li><p><strong>Entry Point for Scale:</strong> The load balancer is typically the single entry point that clients talk to. Behind it, you can have N servers (where N can grow). If you get more traffic, you deploy more servers, and the load balancer starts sending traffic to those servers as well. For example, if your web service is getting 1000 requests/second on one machine at 80% CPU, you might bring up another machine. The load balancer, once it knows about the new machine, will send roughly half the traffic to each. Now each handles ~500 req/s at a comfortable 40% CPU. <strong>Crucially</strong>, from the outside, clients still just hit one address (the balancer) and don&#8217;t need to know about the change. This is how you scale transparently.</p></li><li><p><strong>Auto-Scaling Integration:</strong> In cloud environments, load balancers are tightly integrated with auto-scaling. Take AWS: you have an Auto Scaling Group (ASG) for your EC2 instances and attach it to an ALB. When the ASG launches new instances (say CPU is high, so it adds two more), those instances register with the ALB&#8217;s target group automatically. The ALB health-checks them, and as soon as they&#8217;re healthy, it starts including them in the rotation.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-17" href="#footnote-17" target="_self">17</a> Similarly, if traffic dies down and the ASG terminates instances, the load balancer stops sending traffic to those. This dynamic dance means your system can <strong>grow or shrink on demand</strong>, and the load balancer is the traffic cop that always points to the currently available servers. In Kubernetes, the equivalent is a Deployment&#8217;s pods being scaled by a Horizontal Pod Autoscaler (HPA) - the Service (which load balances to pods) notices new pods and will send traffic to them too.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-18" href="#footnote-18" target="_self">18</a> The user still hits the same Service IP or DNS name.</p></li><li><p><strong>Zero-Downtime Updates:</strong> Load balancers also help in rolling out new code or instances without downtime. For instance, you spin up new servers with a new version of your app, register them with the balancer, and deregister the old ones. If done correctly (ensuring health checks pass on new ones before removing old ones), clients won&#8217;t notice anything except maybe a slight change in response times during transitions. The LB gracefully moves traffic around. This is how <em>rolling deployments</em> or <em>blue-green deployments</em> achieve zero downtime: the load balancer switches traffic from blue to green environment.</p></li><li><p><strong>Brain Analogy:</strong> A nice way to view it: The load balancer is <em>the brain that decides where the current flows</em>. If scaling is adding more &#8220;<em>limbs</em>&#8221; or &#8220;<em>muscle</em>&#8221; to handle work, the load balancer is the nervous system routing tasks to those muscles. Without it, adding more servers wouldn&#8217;t automatically help (how would users know to go to the new server?). With it, you have a central intelligence distributing tasks so that all your capacity is utilized.</p></li></ul><p>Consider <strong>Kubernetes</strong>: by default, a Service in Kubernetes does round-robin across pod IPs (via iptables/IPVS). When HPA doubles the number of pods (say from 3 to 6 pods because CPU got high), the Service&#8217;s endpoints list updates with the new pod IPs. Instantly, the next incoming requests get spread across 6 pods instead of 3. From the client&#8217;s perspective, it&#8217;s still hitting the same service address. From the app owner&#8217;s perspective, you just scaled out seamlessly. The load balancing is what made that scaling effective, otherwise only the original 3 pods would get traffic and the other 3 sit idle.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!k8Se!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3c610c-3ac1-4048-9084-5e25e4ec3bc2_2037x1651.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!k8Se!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3c610c-3ac1-4048-9084-5e25e4ec3bc2_2037x1651.png 424w, https://substackcdn.com/image/fetch/$s_!k8Se!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3c610c-3ac1-4048-9084-5e25e4ec3bc2_2037x1651.png 848w, https://substackcdn.com/image/fetch/$s_!k8Se!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3c610c-3ac1-4048-9084-5e25e4ec3bc2_2037x1651.png 1272w, https://substackcdn.com/image/fetch/$s_!k8Se!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3c610c-3ac1-4048-9084-5e25e4ec3bc2_2037x1651.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!k8Se!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3c610c-3ac1-4048-9084-5e25e4ec3bc2_2037x1651.png" width="1456" height="1180" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ce3c610c-3ac1-4048-9084-5e25e4ec3bc2_2037x1651.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1180,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:254575,&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://iam.slys.dev/i/176401638?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3c610c-3ac1-4048-9084-5e25e4ec3bc2_2037x1651.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_!k8Se!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3c610c-3ac1-4048-9084-5e25e4ec3bc2_2037x1651.png 424w, https://substackcdn.com/image/fetch/$s_!k8Se!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3c610c-3ac1-4048-9084-5e25e4ec3bc2_2037x1651.png 848w, https://substackcdn.com/image/fetch/$s_!k8Se!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3c610c-3ac1-4048-9084-5e25e4ec3bc2_2037x1651.png 1272w, https://substackcdn.com/image/fetch/$s_!k8Se!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3c610c-3ac1-4048-9084-5e25e4ec3bc2_2037x1651.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>Similarly, on cloud VMs: without a load balancer, you might scale out by putting a new VM behind a DNS entry manually or something - a very clunky process. With a load balancer, it&#8217;s often plug-and-play: new server comes, registers, boom, it&#8217;s serving live traffic in seconds. This also aids <strong>fault tolerance</strong> - if one instance in an auto-scaled group dies unexpectedly, auto-scaling replaces it and the LB switches traffic over (plus the LB would have detected it as unhealthy and stopped sending to it within seconds or whatever health check interval).</p><p>A key term here is <strong>elasticity</strong>. Load balancers enable elasticity by serving as the abstraction layer between clients and servers. They are constantly monitoring (via health checks) and adjusting who gets traffic. Some advanced setups even let load balancers trigger scaling - e.g., if queues are building up, the LB might signal to add capacity.</p><p>In sum, <strong>scaling out = add more workers; load balancer = assign tasks to all workers</strong>. Can&#8217;t have one part without the other, if your goal is handling more load than a single machine can manage.</p><p>Finally, think about the user experience: if 100x traffic comes in (say your product goes viral), auto-scaling might bring up dozens of new instances. The users still go to one URL. Behind that URL, your load balancer fans it out to maybe 50 instances now. The alternative (without LB) would require some DNS trickery or manual traffic splitting by issuing different URLs - not practical. So load balancers <em>are the enablers of smooth scaling</em>.</p><h1>&#9888;&#65039; Pitfalls and trade-offs</h1><p>Load balancing isn&#8217;t magic - it introduces its own considerations and potential downsides. It&#8217;s important to be aware of these pitfalls.</p><p><strong>Single Point of Failure (SPOF).</strong> Ironically, the load balancer itself can become a new single point of failure. If you have one load balancer and it crashes, nobody can reach your service (even if your back-end servers are all fine). It&#8217;d be like having one waiter in the restaurant - if that waiter steps out, no orders get to any chef. <strong>Mitigation:</strong> Always have redundancy for your load balancer. This could mean running two instances in an active-passive setup (with a heartbeat and failover IP), or an active-active cluster with both sharing traffic (and clients can fail over to the surviving one). Cloud load balancers are usually designed to be redundant across zones by the provider. For on-prem, techniques like keepalived with VRRP can give two load balancer VMs a virtual IP for failover. In essence, treat your LB layer with the same high-availability approach as any critical component. Also, use health checks on the client side or DNS to detect if an LB is down and route to a backup. For example, some DNS setups will switch to an alternate IP if the primary doesn&#8217;t respond.</p><p><strong>Added Latency.</strong> Each layer of load balancing can introduce a bit of delay. For L4 it&#8217;s minimal (just another network hop). For L7, there&#8217;s the overhead of terminating TCP/SSL and processing HTTP. If you cascade load balancers (like a CDN to an L7 proxy to a service mesh sidecar), you&#8217;ve added multiple hops of processing. This <strong>latency amplification</strong> can be small individually, but it stacks up. As noted, Layer 7 gives you more control but adds latency and complexity. If misconfigured, a load balancer could also become a choke point (e.g., if it doesn&#8217;t have enough resources, it might queue requests). <strong>Mitigation:</strong> Keep the load balancer layer as slim as needed for the job. Use keep-alive connections to reduce overhead where possible. Monitor the latency your LB adds (many provide metrics for response time). In practice, a well-tuned LB adds only a few milliseconds, but it&#8217;s still overhead. Another angle: if your load balancer is geographically far from some clients, that can add latency - sometimes a DNS/global LB solution can direct users to a nearer entry point.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8Hie!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5cc773e5-390a-4683-b8aa-826b13b93bac_2048x1553.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8Hie!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5cc773e5-390a-4683-b8aa-826b13b93bac_2048x1553.png 424w, https://substackcdn.com/image/fetch/$s_!8Hie!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5cc773e5-390a-4683-b8aa-826b13b93bac_2048x1553.png 848w, https://substackcdn.com/image/fetch/$s_!8Hie!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5cc773e5-390a-4683-b8aa-826b13b93bac_2048x1553.png 1272w, https://substackcdn.com/image/fetch/$s_!8Hie!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5cc773e5-390a-4683-b8aa-826b13b93bac_2048x1553.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8Hie!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5cc773e5-390a-4683-b8aa-826b13b93bac_2048x1553.png" width="1456" height="1104" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5cc773e5-390a-4683-b8aa-826b13b93bac_2048x1553.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1104,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:225502,&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://iam.slys.dev/i/176401638?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5cc773e5-390a-4683-b8aa-826b13b93bac_2048x1553.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_!8Hie!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5cc773e5-390a-4683-b8aa-826b13b93bac_2048x1553.png 424w, https://substackcdn.com/image/fetch/$s_!8Hie!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5cc773e5-390a-4683-b8aa-826b13b93bac_2048x1553.png 848w, https://substackcdn.com/image/fetch/$s_!8Hie!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5cc773e5-390a-4683-b8aa-826b13b93bac_2048x1553.png 1272w, https://substackcdn.com/image/fetch/$s_!8Hie!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5cc773e5-390a-4683-b8aa-826b13b93bac_2048x1553.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><strong>Complexity and bugs.</strong> A load balancer is yet another moving piece. Misconfigurations can lead to subtle issues. For instance, choosing the wrong algorithm for your traffic pattern might cause uneven load (e.g., naive round robin when you have very long-lived connections could overload one server). Or enabling sticky sessions without realizing it (some have it default on) could undermine your scaling. Also, if you have multiple layers of LB, debugging can be hard (&#8220;<em>Is the user hitting the CDN or not? Did the mesh route this or was it the gateway?</em>&#8221;). Modern LBs have lots of features (path rewriting, header insertion, etc.) - powerful but with power comes the possibility of mistakes.</p><p><strong>Health Check Pitfalls.</strong> Health checks are how load balancers know if a server is alive and well. A common pitfall is <em>misconfigured health checks</em>. For example, a health check that only pings &#8220;<em>is port 80 open?</em>&#8221; or fetches &#8220;<em>/index.html</em>&#8221; might be too superficial. A server could be up but suffering (e.g., cannot connect to database, or in a crash-loop for certain features) and a shallow health check returns OK so the LB keeps sending traffic to it. Users hitting that server get errors, but the LB doesn&#8217;t kick it out of rotation because the health check didn&#8217;t catch the issue. <strong>Mitigation:</strong> Make health checks as robust as practical. E.g., check a simple but meaningful endpoint (like <code>/health</code> that tests connectivity to dependencies). Also set appropriate timeouts &#8211; e.g., mark a server unhealthy if it doesn&#8217;t respond within, say, 2 seconds <em>multiple</em> times, to catch hangs. We saw earlier an example: a server was out of memory and not actually processing, but still returned a basic &#8220;<em>200 OK</em>&#8221; to the LB&#8217;s trivial check, so the LB kept sending users there, essentially into a black hole. The fix was to implement deeper health checks (and alerts) to detect such failures. Another one: if health check intervals or thresholds are wrong, you might either remove servers too aggressively or keep a dead server around too long. It&#8217;s a tuning game: you want the LB to yank a bad node quickly, but also avoid flapping (e.g., a server that fails one check shouldn&#8217;t be kicked out if it was a momentary glitch).</p><p><strong>Overhead and cost.</strong> Running load balancers (especially robust ones) means you need resources for them. If self-hosting, that&#8217;s extra CPU/RAM to handle potentially tens of thousands of connections. In cloud, you often pay per hour and data - at scale, that can be significant. It&#8217;s often worth it for the benefits, but it&#8217;s a cost to account for. Also, if you encrypt traffic end-to-end, an L7 LB must decrypt and possibly re-encrypt traffic - this offloading is helpful to backend servers, but the LB itself must handle the crypto which could be CPU-heavy (though many LBs can use optimized libraries or even hardware for TLS).</p><p><strong>Layer mixing and double handling.</strong> Sometimes an application accidentally ends up with multiple load balancing decisions on the same request. For instance, DNS might send user to Region A, but that user&#8217;s request might have to go to Region B for some data, causing a trombone effect. Or if you have an L7 LB and the application then calls another service via another LB, you have to trace issues across those boundaries. Each hop is a potential failure point (DNS lookup could fail, LB could be misrouting, etc.). <strong>Observability is key</strong> - ensuring you have logs or tracing at the LB level helps.</p><p><strong>Geo-consistency and caching.</strong> In some scenarios (like caching), load balancing could reduce effectiveness if not done right. E.g., if each request of a user goes to a different cache node, they might miss out on cached results each time (cache misses). That&#8217;s why for caches or CDNs, consistent hashing or stickiness is used to improve cache hit rates by sending a user to the same node. So one must consider such trade-offs: pure load spread vs. locality.</p><p><strong>The &#8220;It&#8217;s always DNS&#8221; problem.</strong> If you rely on DNS load balancing, you might hit issues with DNS caching or TTL. Also, changes (like removing a bad server&#8217;s IP) might not propagate instantly, leading to some users still hitting a down server until their DNS cache expires. People say &#8220;<em>DNS is not a real-time load balancer</em>&#8221; for this reason. It&#8217;s best combined with other methods or with very low TTLs (which itself can increase DNS query load and sometimes be ignored by resolvers).</p><p><strong>Security Considerations.</strong> A load balancer can also be a focal point for security. If an LB is compromised or misconfigured (say, doesn&#8217;t pass along client IPs correctly, or is open to a certain attack), it can affect all traffic. Also, load balancers often terminate SSL &#8211; you need to ensure they are as secure as your app servers (patched for vulnerabilities, etc.). On the flip side, they can also help security by absorbing DDoS or hiding your internal topology.</p><p>To summarize, <strong>load balancing introduces an extra layer</strong>, and with any abstraction, you have to handle it carefully. Ensure redundancy to avoid new single points of failure, understand that it&#8217;s not set-and-forget (monitor the LB&#8217;s health and performance too), and configure things like health checks and algorithms with your app&#8217;s behavior in mind. When set up properly, a load balancer <em>greatly increases</em> reliability; if set up poorly, it can &#8220;<em>silently kill</em>&#8221; as one article put it - e.g., by routing users to a bad instance without you realizing because dashboards &#8220;<em>look green</em>&#8221;. Awareness of these pitfalls is the first step to avoiding them.</p><h1>&#128161; Load Balancing as a philosophy</h1><p>Load balancing isn&#8217;t just an engineering technique - it can be seen as a <strong>philosophy of balance</strong> that extends beyond computers. Think about it: it&#8217;s about distributing pressure intelligently so that no single component breaks, and the overall system stays healthy.</p><p>In life and in teams, the same concept applies. A good team leader is like a human load balancer: they notice when one team member is overloaded and others have capacity, and then redistribute tasks to keep the team effective (and prevent burnout). It&#8217;s about <em>balance</em>. If you give all the work to one star performer, they&#8217;ll burn out (like a server overloaded) while others stay idle (like underutilized servers). Instead, a wise leader spreads tasks according to each person&#8217;s current load and strengths&#8212;just as a load balancer routes traffic considering each server&#8217;s capacity and current usage.</p><p>We can even extend the restaurant analogy: a restaurant manager might move chefs or waiters around on a busy night to balance the workload in the kitchen and on the floor. They might notice the dessert station is backed up while the grill station is idle and shift resources accordingly. That&#8217;s load balancing in human terms.</p><p>The <strong>philosophy of load balancing</strong> is essentially <em>resilience through distribution</em>. By not putting all eggs in one basket, by not overwhelming one component, we achieve stability. This is seen in many systems: electricity grids balance load across power plants, shipping companies load balance packages across multiple routes, etc. In each case, the goal is to prevent any single point from being the bottleneck or point of failure.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6R-W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54972c8c-9927-46d8-b434-a9bdfd3d705c_2515x1336.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6R-W!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54972c8c-9927-46d8-b434-a9bdfd3d705c_2515x1336.png 424w, https://substackcdn.com/image/fetch/$s_!6R-W!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54972c8c-9927-46d8-b434-a9bdfd3d705c_2515x1336.png 848w, https://substackcdn.com/image/fetch/$s_!6R-W!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54972c8c-9927-46d8-b434-a9bdfd3d705c_2515x1336.png 1272w, https://substackcdn.com/image/fetch/$s_!6R-W!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54972c8c-9927-46d8-b434-a9bdfd3d705c_2515x1336.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6R-W!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54972c8c-9927-46d8-b434-a9bdfd3d705c_2515x1336.png" width="1200" height="637.0879120879121" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/54972c8c-9927-46d8-b434-a9bdfd3d705c_2515x1336.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:773,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:253988,&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://iam.slys.dev/i/176401638?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54972c8c-9927-46d8-b434-a9bdfd3d705c_2515x1336.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_!6R-W!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54972c8c-9927-46d8-b434-a9bdfd3d705c_2515x1336.png 424w, https://substackcdn.com/image/fetch/$s_!6R-W!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54972c8c-9927-46d8-b434-a9bdfd3d705c_2515x1336.png 848w, https://substackcdn.com/image/fetch/$s_!6R-W!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54972c8c-9927-46d8-b434-a9bdfd3d705c_2515x1336.png 1272w, https://substackcdn.com/image/fetch/$s_!6R-W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54972c8c-9927-46d8-b434-a9bdfd3d705c_2515x1336.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>For us tech folks, maybe the poetic way to put it is: <strong>load balancers turn chaos into flow, and systems into symphonies.</strong> They take the chaos of millions of requests and orchestrate them to where they need to go, preventing cacophony (server overload or crashes). When you watch a well-balanced system under heavy load, it&#8217;s quite elegant - everything hums along, users are served, and behind the scenes dozens or thousands of machines dance in unison to handle the load.</p><p>In the end, thinking about load balancing reminds us of the importance of <em>balance in design</em>. Too often, systems fail not because they can&#8217;t handle something in general, but because one part was overstressed. By balancing, we ensure <strong>grace under pressure</strong>. It&#8217;s a mindset: anticipate where the load will be and distribute it proactively.</p><p>So, beyond just the hardware and software, load balancing teaches an almost zen lesson: don&#8217;t let any part of the system (or team) carry more than it should - spread out the work, and everything (and everyone) will last longer and perform better.</p><h1>&#9997;&#65039; Recap</h1><p><strong>Load balancing</strong> means distributing work (requests, connections) across multiple servers. It&#8217;s fundamental for building high-performance, reliable, scalable systems. Instead of one server handling everything (risking overload or downtime), you have a fleet of servers sharing the load.</p><p>It&#8217;s <em>not just about fairness</em>. The goal is <strong>resilience and efficiency</strong>. A good load balancing strategy ensures no single server becomes a bottleneck (improving throughput and response times) and that the system can tolerate failures (if one server drops, others pick up the slack). It also enables you to grow your service easily by adding more servers (horizontal scaling).</p><p>Load balancing happens at multiple layers:</p><ul><li><p><strong>L4</strong> (transport) for low-level, fast routing based on IPs/ports.</p></li><li><p><strong>L7</strong> (application) for smart routing based on HTTP content, cookies, etc., albeit with slight overhead.</p></li><li><p><strong>DNS-based</strong> for simple global distribution using multiple IPs.</p></li><li><p><strong>Client-side</strong> within apps, where services call other services directly choosing from known instances. Each layer has its use-cases, often used in combination.</p></li></ul><p>Common <strong>algorithms</strong> include Round Robin (simple rotation), Least Connections (to spread based on current load), hashing (for stickiness), weighted schemes, and more adaptive methods that account for server response times and other metrics. The choice of algorithm should match your workload (e.g., if all requests are equal, round-robin is fine; if not, consider least-connections or adaptive).</p><p><strong>State and sessions</strong> pose challenges in a distributed environment. If users bounce between servers, you must manage session data. Solutions include sticky sessions (simplest, but can undermine balancing), or better, external session stores / stateless tokens so any server can serve any user without issues. Designing stateless services as much as possible makes load balancing most effective.</p><p>In the real world, we have both <strong>open-source and commercial tools</strong>:</p><ul><li><p><em>Software like Nginx, HAProxy, Traefik, Envoy</em> are deployed by many to do L4/L7 balancing in data centers and clouds (often on standard VMs/containers, replacing the need for big hardware boxes).</p></li><li><p><em>Cloud-managed load balancers</em> (AWS ELB/ALB, Google Cloud Load Balancing, etc.) offer turnkey high availability and scalability, integrating with auto-scaling and eliminating a lot of ops overhead.</p></li><li><p><em>Service Mesh proxies</em> (Envoy via Istio/Linkerd) bring load balancing into each service instance for microservice architectures, enabling very fine-grained control and reliability techniques.<br>Historically, specialized hardware appliances were used, but today software-defined load balancers dominate due to flexibility and cost &#8211; the industry trend is moving from proprietary hardware to commodity hardware + OSS software solutions.</p></li></ul><p><strong>Auto-scaling synergy.</strong> Load balancers work hand-in-hand with scaling mechanisms. As you add instances or pods, the load balancer updates its pool and starts routing traffic to them. This allows your system to handle growth and spikes seamlessly. Without a load balancer, auto-scaling would have no coordinated way to use new instances. With it, you achieve elastic scaling &#8211; users just see a stable service, not the scaling events.</p><p><strong>Pitfalls.</strong> Be mindful of making the load balancer a new single point of failure (always have a fallback or redundancy). Recognize that each extra layer (especially L7) adds some latency and complexity, so use it judiciously. Configure health checks properly - they are your balancer&#8217;s eyes to detect bad servers, so they must actually detect real failures (deep health checks &gt; shallow ones). And monitor your load balancer like any critical component; misconfigurations there can impact the whole system (e.g., all traffic could be misrouted or throttled).</p><p>Load balancing is about turning a collection of servers into a <strong>single reliable service</strong>. It&#8217;s the reason we can build sites that serve millions of users - not on one supercomputer, but on thousands of ordinary servers working in unison. It takes what would be chaos (random, spiky traffic) and <strong>turns it into an organized flow</strong>. In doing so, it also gives us flexibility: we can maintain, deploy, and scale parts of the system without users noticing interruptions.</p><div><hr></div><p><strong>Know someone who&#8217;s just getting into backend development or cloud architecture</strong></p><p>If this post helped you, pass it along - help them level up too. &#128071;</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/leaderboard?&amp;utm_source=post&quot;,&quot;text&quot;:&quot;Refer a friend&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/leaderboard?&amp;utm_source=post"><span>Refer a friend</span></a></p><div><hr></div><p>In summary, load balancing is a foundational concept that goes beyond just splitting traffic 50/50 or 33/33/33. It&#8217;s about <strong>smart distribution of workload</strong>, with awareness of performance and reliability. When done right, it makes systems behave elegantly under load - like a well-run kitchen or a well-conducted orchestra, where no single part is overwhelmed and the output (be it dishes, music, or web pages) keeps coming smoothly. By understanding how load balancing works and its caveats, you&#8217;re better equipped to design systems that are both <strong>strong and flexible</strong>, capable of handling the rush hour of the internet without breaking a sweat.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p><a href="https://en.wikipedia.org/wiki/Load_balancing_(computing)">Load balancing (computing)</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p><a href="https://www.geeksforgeeks.org/system-design/what-is-load-balancer-system-design/">What is Load Balancer &amp; How Load Balancing works?</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p><a href="https://softbuilds.medium.com/load-balancers-decoded-l4-vs-l7-sticky-sessions-failovers-aa510382efae">Load Balancers Decoded: L4 vs L7, Sticky Sessions &amp; Failovers</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p><a href="https://www.a10networks.com/glossary/how-do-layer-4-and-layer-7-load-balancing-differ/">How do Layer 4 Load Balancing and Layer 7 Load Balancing Differ?</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p><a href="https://www.haproxy.com/blog/layer-4-vs-layer-7-load-balancing">Layer 4 vs Layer 7 Load Balancing (Differences Explained)</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p><a href="https://www.cloudflare.com/learning/dns/glossary/round-robin-dns/">What is round-robin DNS?</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p><a href="https://www.geeksforgeeks.org/java/client-side-service-discovery-in-microservices/">Client Side Service Discovery in Microservices</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p><a href="https://medium.com/@sohail_saifi/the-load-balancer-algorithm-that-adapts-to-your-applications-behavior-44718c958685">The Load Balancer Algorithm That Adapts to Your Application&#8217;s Behavior</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-9" href="#footnote-anchor-9" class="footnote-number" contenteditable="false" target="_self">9</a><div class="footnote-content"><p><a href="https://traefik.io/glossary/what-are-sticky-sessions">What Are Sticky Sessions &#8212; How They Work and When to Use Them</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-10" href="#footnote-anchor-10" class="footnote-number" contenteditable="false" target="_self">10</a><div class="footnote-content"><p><a href="https://www.designgurus.io/answers/detail/what-are-sticky-sessions-and-when-to-avoid-them">What Are Sticky Sessions and When to Avoid Them?</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-11" href="#footnote-anchor-11" class="footnote-number" contenteditable="false" target="_self">11</a><div class="footnote-content"><p><a href="https://vishnu-g.medium.com/when-your-load-balancer-becomes-the-silent-killer-and-how-to-fix-it-cac4f69c83bd">When Your Load Balancer Becomes the Silent Killer (and How to Fix It)</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-12" href="#footnote-anchor-12" class="footnote-number" contenteditable="false" target="_self">12</a><div class="footnote-content"><p><a href="https://www.linode.com/community/questions/6459/nodebalancers-sticky-sessions-or-central-session-storage">NodeBalancers - Sticky sessions or central session storage?</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-13" href="#footnote-anchor-13" class="footnote-number" contenteditable="false" target="_self">13</a><div class="footnote-content"><p><a href="https://www.loggly.com/blog/benchmarking-5-popular-load-balancers-nginx-haproxy-envoy-traefik-and-alb/">Benchmarking 5 Popular Load Balancers: Nginx, HAProxy, Envoy, Traefik, and ALB</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-14" href="#footnote-anchor-14" class="footnote-number" contenteditable="false" target="_self">14</a><div class="footnote-content"><p><a href="https://blog.envoyproxy.io/introduction-to-modern-network-load-balancing-and-proxying-a57f6ff80236">Introduction to modern network load balancing and proxying</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-15" href="#footnote-anchor-15" class="footnote-number" contenteditable="false" target="_self">15</a><div class="footnote-content"><p><a href="https://tetrate.io/what-is-envoy-proxy">What Is Envoy Proxy and Why Do You Need It?</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-16" href="#footnote-anchor-16" class="footnote-number" contenteditable="false" target="_self">16</a><div class="footnote-content"><p><a href="https://cloudsolutions.academy/solution/what-is-anycast-ip-address-and-how-does-google-cloud-load-balancer-works/">What is Anycast IP address and how does Google Cloud Load Balancer works</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-17" href="#footnote-anchor-17" class="footnote-number" contenteditable="false" target="_self">17</a><div class="footnote-content"><p><a href="https://www.geeksforgeeks.org/devops/understanding-auto-scaling-and-load-balancing-integration-in-aws/">Understanding Auto Scaling And Load Balancing Integration In AWS</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-18" href="#footnote-anchor-18" class="footnote-number" contenteditable="false" target="_self">18</a><div class="footnote-content"><p><a href="https://medium.com/garantibbva-teknoloji/load-balancing-in-kubernetes-and-how-to-use-grpc-protocol-7735ae7faabb">Load Balancing in Kubernetes and how to use gRPC protocol</a></p><p></p></div></div>]]></content:encoded></item><item><title><![CDATA[Linear Regression — the heartbeat of Machine Learning]]></title><description><![CDATA[AI Literacy]]></description><link>https://iam.slys.dev/p/linear-regression-the-heartbeat-of</link><guid isPermaLink="false">https://iam.slys.dev/p/linear-regression-the-heartbeat-of</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 23 Feb 2026 23:20:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!i4IZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!i4IZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!i4IZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!i4IZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!i4IZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!i4IZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!i4IZ!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2957718,&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://iam.slys.dev/i/187834422?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!i4IZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!i4IZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!i4IZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!i4IZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F465a6bf7-5b95-473f-a7dd-f8e1dbf3c4c6_1536x1024.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>Linear regression looks almost trivial on the surface: you draw a straight line through a cloud of points and use it to predict <code>(y)</code> from <code>(x)</code>. But that &#8220;<em>almost</em>&#8221; hides why it has survived for so long. Linear regression is the first model where you can see - clearly, concretely - the full learning loop that powers much of machine learning: propose a parameterized rule, measure its failures, and change the parameters to reduce those failures.</p><p>In other words, linear regression isn&#8217;t memorable because lines are special. It&#8217;s memorable because it&#8217;s the simplest place where <em>optimization</em> becomes a way of thinking. You&#8217;re not trying to &#8220;<em>be right</em>&#8221;. You&#8217;re trying to be <em>less wrong</em> according to a score you chose on purpose. That&#8217;s an idea you will reuse everywhere: logistic regression, matrix factorization, gradient-boosted trees, neural networks, language models. The shapes change, the data changes, the number of parameters explodes - but the heartbeat is recognizable.</p><p>This post is deliberately slow. I&#8217;m going to spend time on the emotional problem (&#8220;<em>the world is messy and I still want a simple story</em>&#8221;), then the modeling decision (&#8220;<em>a line is a constraint we choose</em>&#8221;), and only then the mechanics (&#8220;<em>how do we adjust the knobs?</em>&#8221;). When we do introduce math, we&#8217;ll treat it like a guided walkthrough, not a magic incantation. The goal is that you could sit down with a notebook and re-derive the essentials yourself - and, more importantly, that you&#8217;ll know what to look for when the line inevitably fails.</p><h2>The comforting lie of a clean relationship</h2><p>Most of us begin modeling with a quiet hope: <em>more of X means more of Y</em>. It&#8217;s a reasonable hope because it&#8217;s often directionally true. More practice tends to improve performance. More advertising tends to increase sales. More weight on a spring tends to stretch it further. These are comforting because they offer a simple story you can tell yourself and others.</p><p>Then you actually plot the data.</p><p>Instead of a neat diagonal trend, you get dots that wobble. They clump. They contradict each other. You might see points with high <code>(x)</code> and low <code>(y)</code>, and low <code>(x)</code> and high <code>(y)</code>, all in the same dataset. If you were expecting a clean relationship, the scatter plot feels like a betrayal: &#8220;<em>Was my intuition wrong? Is there no relationship at all?</em>&#8221;</p><p>This is the emotional problem linear regression solves. Not &#8220;<em>how do I find the truth</em>&#8221;, but: <strong>how do I tell a simple story about a messy world in a way that can be defended?</strong> The defensible part is crucial. Anyone can eyeball a plot and draw a line they like. But two people will draw two different lines. If your story depends on taste, you haven&#8217;t learned anything stable.</p><p>Linear regression offers a truce. It says: we will accept that points do not line up perfectly, and we will still insist on a simple summary - but we will choose that summary according to a clear rule. Not because the rule is perfect, but because it is explicit, repeatable, and critique-able. That is what turns a comforting lie into an engineering tool.</p><h2>Drawing a line is really choosing a kind of explanation</h2><p>A straight line is not &#8220;<em>the default</em>&#8221; because reality is linear. It&#8217;s the default because it is the simplest explanation that can still be wrong in a useful way. That sounds almost philosophical, but it&#8217;s practical: if you allow yourself to fit anything - curves, kinks, weird oscillations - you can always build a model that matches the data you already saw. The hard part is building something that will behave sensibly on data you haven&#8217;t seen.</p><p>A line is a bias toward simplicity. It says: &#8220;<em>I will explain the relationship using only two degrees of freedom: an overall vertical shift and a tilt</em>&#8221;. That constraint is what makes a line interpretable. You can point at it and say something like: &#8220;<em>For every additional unit of </em><code>(x)</code><em>, the predicted </em><code>(y)</code><em> changes by a constant amount</em>&#8221;. That is a statement a human can reason about.</p><p>This is a general pattern in machine learning: we choose a <em>family</em> of explanations, then search within that family. Linear regression chooses a very small family. Its strength is not that it captures everything; its strength is that it captures one kind of structure - an overall trend - while refusing to chase the noise.</p><p>And that refusal is not a moral virtue. It&#8217;s a trade-off. You give up expressive power, but you gain stability, readability, and often surprisingly strong baseline performance. If you later move to more flexible models, this early habit matters: always ask, &#8220;<em>What kind of explanations am I allowing the model to use?</em>&#8221; You don&#8217;t just fit data. You choose the vocabulary of explanations - and a line is one of the smallest vocabularies that still lets you say something meaningful.</p><h2>Before &#8220;<em>training</em>&#8221;, there is only &#8220;<em>guessing with rules</em>&#8221;</h2><p>Before we talk about training, it helps to strip away the ML vocabulary. A model is just a rule: it takes an input and returns an output. If the input is <code>(x)</code> and the output is <code>(y)</code>, then the model is a function that maps <code>(x)</code> to a prediction <code>(&#375;)</code>.</p><p>A line is a particularly simple rule with <strong>two knobs</strong>. One knob controls where the line crosses the vertical axis (the baseline when <code>(x = 0))</code>. The other knob controls how steeply the line rises or falls (how much <code>(&#375;)</code> changes when (x) changes).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!T1zh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f6c601f-b968-4a64-8472-b1430504bf38_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!T1zh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f6c601f-b968-4a64-8472-b1430504bf38_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!T1zh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f6c601f-b968-4a64-8472-b1430504bf38_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!T1zh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f6c601f-b968-4a64-8472-b1430504bf38_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!T1zh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f6c601f-b968-4a64-8472-b1430504bf38_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!T1zh!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f6c601f-b968-4a64-8472-b1430504bf38_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f6c601f-b968-4a64-8472-b1430504bf38_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3235874,&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://iam.slys.dev/i/187834422?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f6c601f-b968-4a64-8472-b1430504bf38_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!T1zh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f6c601f-b968-4a64-8472-b1430504bf38_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!T1zh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f6c601f-b968-4a64-8472-b1430504bf38_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!T1zh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f6c601f-b968-4a64-8472-b1430504bf38_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!T1zh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f6c601f-b968-4a64-8472-b1430504bf38_1536x1024.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>Only after you can see those knobs clearly is it useful to name them. We call the baseline the <strong>intercept</strong> and the steepness the <strong>slope</strong>. We call both knobs <strong>parameters</strong>, because they are values inside the model that we can set.</p><p>The rule is written as:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat{y} = b_0 + b_1 x&quot;,&quot;id&quot;:&quot;TKXCALSVIH&quot;}" data-component-name="LatexBlockToDOM"></div><p>Here <code>(b&#8320;)</code> is the intercept and <code>(b&#8321;)</code> is the slope.</p><p><strong>Worked example (making predictions).</strong></p><p>Suppose we pick <code>(b&#8320; = 1)</code> and <code>(b&#8321; = 2)</code>. Then:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat{y} = 1 + 2x&quot;,&quot;id&quot;:&quot;ANKPKJECRS&quot;}" data-component-name="LatexBlockToDOM"></div><p>If <code>(x = 3)</code>, substitute directly:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat{y} = 1 + 2\\cdot 3&quot;,&quot;id&quot;:&quot;ZMFWSPKZFW&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat{y} = 1 + 6 = 7&quot;,&quot;id&quot;:&quot;REZOBZNUYH&quot;}" data-component-name="LatexBlockToDOM"></div><p>If <code>(x = 0)</code>, the prediction is just the intercept:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat{y} = 1 + 2\\cdot 0 = 1&quot;,&quot;id&quot;:&quot;CWAOAYAOZV&quot;}" data-component-name="LatexBlockToDOM"></div><p>Nothing mysterious is happening yet. Training will simply mean: choose <code>(b&#8320;)</code> and <code>(b&#8321;)</code> so that these predictions align well with reality.</p><h2>Noise: the reason the line will never please everyone</h2><p>Even if there is a true underlying relationship, your observed points almost never sit perfectly on a line. This is not because your data is &#8220;<em>bad</em>&#8221;. It&#8217;s because the world contains more variation than your measurement captures.</p><p>Sometimes the noise is literal measurement error: a sensor has jitter, a scale is miscalibrated, a human labeler is inconsistent, a number gets rounded. Sometimes it&#8217;s behavioral variability: two people can receive the same input and respond differently for reasons you didn&#8217;t measure. And often it&#8217;s missing variables: you&#8217;re modeling <code>(y)</code> with one feature <code>(x)</code>, but the real system depends on many factors.</p><p>A useful mental model is:</p><ul><li><p>there may be a clean trend,</p></li><li><p>but each observed point is the trend plus &#8220;<em>everything else</em>&#8221;.</p></li></ul><p>Linear regression doesn&#8217;t deny &#8220;<em>everything else</em>&#8221;. It just refuses to model it explicitly. It says: I will capture the main direction with a line, and I will treat the leftovers as noise.</p><p>That makes the phrase &#8220;<em>the line doesn&#8217;t hit all points</em>&#8221; feel normal rather than suspicious. In fact, if your line hits every point exactly, you should be cautious: either the problem is artificially clean, or you&#8217;ve accidentally let the model become too flexible (overfitting in disguise), or you&#8217;re evaluating on the same data you trained on in a way that hides generalization error.</p><p>Noise is also why humility matters in ML. If the data-generating process is noisy, then a perfect predictor might not exist even in principle. The best you can do is reduce uncertainty and make useful probabilistic or average-case predictions. Linear regression is the first model that gently forces you to accept that &#8220;<em>some error</em>&#8221; is not failure - it&#8217;s reality.</p><h2>The first key idea: error is not embarrassment, it&#8217;s feedback</h2><p>Once you accept that a line cannot satisfy every point, you need a different relationship with &#8220;<em>being wrong</em>&#8221;. In machine learning, error is not embarrassment. Error is the mechanism by which learning happens.</p><p>Take a point <code>((x&#7522;, y&#7522;))</code>. Your model predicts <code>(&#375;&#7522;)</code>. The difference between <code>(y&#7522;)</code> and <code>(&#375;&#7522;)</code> is not abstract - it is a visible vertical gap on the scatter plot. If the point lies above the line, the model predicted too low. If it lies below the line, the model predicted too high.</p><p>This matters because it turns learning into a loop:</p><ol><li><p>make a prediction using your current parameters,</p></li><li><p>compare the prediction to the truth,</p></li><li><p>use the comparison to decide how to adjust.</p></li></ol><p>Without step <code>(2)</code>, you have no guidance. You can &#8220;<em>guess</em>&#8221; different lines, but you cannot say which direction is improvement. So the core requirement for training is not cleverness; it is <strong>a consistent way to measure misses</strong>.</p><p>A subtle point: the error must be defined in a way the model can respond to smoothly. If tiny parameter changes cause unpredictable swings in the error measure, learning becomes chaotic. Linear regression with standard error definitions behaves nicely, which is another reason it&#8217;s such a good first model: the feedback is stable enough that you can build intuition about iterative improvement.</p><p>So in this section, the main idea is psychological as much as technical: errors are not shameful artifacts to hide. They are the signal that drives parameter adjustment.</p><div><hr></div><p>&#128161; <em>What if being wrong is the whole point?</em></p><p>In machine learning, mistakes aren&#8217;t something to hide - they&#8217;re the mechanism that makes improvement possible.</p><p>&#128073; <strong>Subscribe</strong> to explore more ideas where math quietly reshapes how we think.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2>Residuals as a diagnostic, not just a number</h2><p>After you&#8217;ve stared at those vertical gaps for a while, it&#8217;s helpful to name them: they are <strong>residuals</strong>. For each point <code>(i)</code>, we define the residual as observed minus predicted:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;e_i = y_i - \\hat{y}_i&quot;,&quot;id&quot;:&quot;ADBSQLHCPW&quot;}" data-component-name="LatexBlockToDOM"></div><p>And since:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat{y}_i = b_0 + b_1 x_i&quot;,&quot;id&quot;:&quot;PPMRRVSFCQ&quot;}" data-component-name="LatexBlockToDOM"></div><p>we can write:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;e_i = y_i - (b_0 + b_1 x_i)&quot;,&quot;id&quot;:&quot;ARJBJQFDZI&quot;}" data-component-name="LatexBlockToDOM"></div><p>The sign matters. If <code>(e&#7522; &gt; 0)</code>, you underpredicted. If <code>(e&#7522;&lt; 0)</code>, you overpredicted.</p><p><strong>Worked example (computing residuals).</strong><br>Suppose our data points are:</p><ul><li><p><code>((1, 2))</code></p></li><li><p><code>((2, 3))</code></p></li><li><p><code>((3, 5))</code></p></li></ul><p>and our current line is:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat{y} = 1 + 1x&quot;,&quot;id&quot;:&quot;TNCWZCXQUI&quot;}" data-component-name="LatexBlockToDOM"></div><p>Compute predictions:</p><ul><li><p>at <code>(x = 1): (&#375; = 1 + 1 = 2)</code></p></li><li><p>at <code>(x = 2): (&#375; = 1 + 2 = 3)</code></p></li><li><p>at <code>(x = 3): (&#375; = 1 + 3 = 4)</code></p></li></ul><p>Now residuals <code>(e&#7522; = y&#7522; - &#375;&#7522;)</code>:</p><ul><li><p><code>(e&#8321; = 2 - 2 = 0)</code></p></li><li><p><code>(e&#8322; = 3 - 3 = 0)</code></p></li><li><p><code>(e&#8323; = 5 - 4 = 1)</code></p></li></ul><p>So far, residuals look &#8220;<em>fine</em>&#8221;. But the real power is diagnostic: residuals tell stories in aggregate. If residuals are mostly positive for large <code>(x)</code>, you&#8217;re systematically underpredicting in that region. If they cluster negative for small <code>(x)</code>, you&#8217;re systematically overpredicting there. Residuals aren&#8217;t just numbers - they&#8217;re a microscope for model mismatch.</p><h2>The second key idea: to optimize, you need one score to minimize</h2><p>Residuals give you many pieces of feedback - one per point. But to choose between two candidate lines, you need a single scoreboard number. Otherwise, you&#8217;re stuck arguing about trade-offs point by point: &#8220;<em>This line is better here, but worse there</em>&#8221;.</p><p>This is why loss functions exist. A <strong>loss function</strong> compresses the whole set of residuals into one number that the computer can minimize.</p><p>A common choice is mean squared error <code>(MSE)</code>. If you have <code>(n)</code> points, define:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\text{MSE} = \\frac{1}{n}\\sum e_i^2&quot;,&quot;id&quot;:&quot;LUVFRDVGEE&quot;}" data-component-name="LatexBlockToDOM"></div><p>This does two things at once. First, it makes all contributions nonnegative by squaring. Second, it makes &#8220;<em>big mistakes</em>&#8221; count more than &#8220;<em>small mistakes</em>&#8221;.</p><p><strong>Worked example (aggregating to one score).</strong></p><p>Using the residuals from the previous section <code>([0, 0, 1])</code>, compute <code>MSE</code>:</p><p>Square each residual:</p><ul><li><p><code>(0&#178; = 0)</code></p></li><li><p><code>(0&#178; = 0)</code></p></li><li><p><code>(1&#178; = 1)</code></p></li></ul><p>Sum them:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\sum e_i^2 = 0+0+1 = 1&quot;,&quot;id&quot;:&quot;RRDHFEPMFP&quot;}" data-component-name="LatexBlockToDOM"></div><p>Divide by <code>(n = 3)</code>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\text{MSE} = \\frac{1}{3} \\approx 0.333&quot;,&quot;id&quot;:&quot;LMWRWUNXAW&quot;}" data-component-name="LatexBlockToDOM"></div><p>Now we can compare lines. A line that produces <code>MSE (0.2)</code> is &#8220;<em>better</em>&#8221; than one that produces <code>MSE (0.3)</code>, <em>under this definition of better</em>. That last clause is important: optimization doesn&#8217;t discover goodness; it operationalizes the goodness you defined.</p><h2>Why squared error feels harsh &#8212; and why that harshness is useful</h2><p>Squared error is opinionated. It says: large errors are disproportionately bad. That can feel harsh, especially if you&#8217;re thinking in human terms where a miss is a miss. But squared error is a deliberate choice about priorities.</p><p>To see the effect, compare two residual sets:</p><ul><li><p><code>Set A: ([2, 2, 2])</code></p></li><li><p><code>Set B: ([0, 0, 4])</code></p></li></ul><p>Under absolute error, both have total error (6). Squared error distinguishes them.</p><p>Compute squared sums:</p><p>For A:</p><ul><li><p><code>(2&#178; = 4)</code> three times</p></li><li><p>sum (<code>= 4 + 4 + 4 = 12</code>)</p></li></ul><p>For B:</p><ul><li><p><code>(0&#178; = 0)</code></p></li><li><p><code>(0&#178; = 0)</code></p></li><li><p><code>(4&#178; = 16)</code></p></li><li><p>sum <code>(= 16)</code></p></li></ul><p>So squared error prefers A (many small misses) over B (one big miss). That&#8217;s the harshness.</p><p>Why is that useful? Sometimes it matches reality. In many systems, a few catastrophic predictions cause outsized damage: a medical dose that&#8217;s wildly off, a delivery ETA that&#8217;s wrong by hours, a credit decision that misprices risk severely. Squared error encodes &#8220;<em>avoid disasters even if it means tolerating mild imperfection elsewhere</em>&#8221;.</p><p>There&#8217;s also a computational reason: squaring creates a smooth, bowl-shaped objective for linear regression. Smoothness means that small parameter changes lead to predictable loss changes, which makes optimization stable. If your loss surface has sharp corners, learning algorithms can jitter or stall. So squared error is not only a value judgment; it&#8217;s an engineering choice that makes the search landscape easier to navigate.</p><div><hr></div><p>&#9878;&#65039; <em>Would you rather tolerate many small misses or prevent one big failure?</em></p><p>Every loss function encodes a value judgment - even when it looks purely mathematical.</p><p>&#128172; <strong>Comment</strong> - would you design the objective differently?</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/linear-regression-the-heartbeat-of/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/linear-regression-the-heartbeat-of/comments"><span>Leave a comment</span></a></p><div><hr></div><h2>Optimization as a story of two knobs and repeated correction</h2><p>Now we return to the two knobs: intercept <code>(b&#8320;)</code> and slope <code>(b&#8321;)</code>. Optimization is the process of adjusting these knobs to reduce the loss - usually MSE.</p><p>At a human level, you can already anticipate the direction of many corrections. If your line sits too high across the plot - meaning most points fall below it - then most residuals are negative, and lowering the intercept should help. If your line is too low, raising the intercept should help.</p><p>Slope is about imbalance across <code>(x)</code>. If you underpredict at high <code>(x)</code> and overpredict at low <code>(x)</code>, your line is too flat and should tilt upward. If the opposite happens, it&#8217;s too steep and should flatten.</p><p>The key is that training is rarely a one-shot act. It&#8217;s a loop:</p><ul><li><p>pick initial <code>(b&#8320;, b&#8321;)</code>,</p></li><li><p>compute predictions <code>(&#375;&#7522;)</code>,</p></li><li><p>compute residuals <code>(e&#7522;)</code>,</p></li><li><p>compute loss,</p></li><li><p>adjust <code>(b&#8320;, b&#8321;)</code> to reduce loss,</p></li><li><p>repeat.</p></li></ul><p>This &#8220;<em>tiny edits</em>&#8221; framing matters because it generalizes. Even though linear regression can be solved directly with a closed-form formula, most modern ML training feels like this loop. Once you&#8217;re comfortable with &#8220;<em>we improve by repeated correction</em>&#8221;, later ideas - learning rates, convergence, early stopping, optimizer choices - feel like natural refinements rather than arcane rituals.</p><p>The goal isn&#8217;t to make optimization sound glamorous. It&#8217;s closer to careful woodworking: measure, shave a little, measure again. And the more clearly you can connect each shave to the measurement, the more trustworthy the process becomes.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4dTC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F671fec52-4c63-45ac-a358-d01d4fb12f5f_960x540.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4dTC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F671fec52-4c63-45ac-a358-d01d4fb12f5f_960x540.gif 424w, https://substackcdn.com/image/fetch/$s_!4dTC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F671fec52-4c63-45ac-a358-d01d4fb12f5f_960x540.gif 848w, https://substackcdn.com/image/fetch/$s_!4dTC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F671fec52-4c63-45ac-a358-d01d4fb12f5f_960x540.gif 1272w, https://substackcdn.com/image/fetch/$s_!4dTC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F671fec52-4c63-45ac-a358-d01d4fb12f5f_960x540.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4dTC!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F671fec52-4c63-45ac-a358-d01d4fb12f5f_960x540.gif" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/671fec52-4c63-45ac-a358-d01d4fb12f5f_960x540.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:540,&quot;width&quot;:960,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:211682,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/187834422?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F671fec52-4c63-45ac-a358-d01d4fb12f5f_960x540.gif&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_!4dTC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F671fec52-4c63-45ac-a358-d01d4fb12f5f_960x540.gif 424w, https://substackcdn.com/image/fetch/$s_!4dTC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F671fec52-4c63-45ac-a358-d01d4fb12f5f_960x540.gif 848w, https://substackcdn.com/image/fetch/$s_!4dTC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F671fec52-4c63-45ac-a358-d01d4fb12f5f_960x540.gif 1272w, https://substackcdn.com/image/fetch/$s_!4dTC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F671fec52-4c63-45ac-a358-d01d4fb12f5f_960x540.gif 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>The hill-and-valley picture: turning model fitting into navigation</h2><p>The optimization loop becomes much easier to reason about once you adopt the hill-and-valley picture.</p><p>Imagine every possible pair <code>((b&#8320;, b&#8321;))</code> as a location on a map. For each location, you can compute the loss (say, <code>MSE</code>) using your dataset. Now imagine the loss as a height. High loss is a mountain. Low loss is a valley. Training means finding the lowest valley.</p><p>This turns a vague task (&#8220;<em>fit the model</em>&#8221;) into navigation (&#8220;<em>walk downhill</em>&#8221;). It also clarifies why the loss function is so central. Without a scalar loss, you can&#8217;t define height; without height, you can&#8217;t define downhill.</p><p>For linear regression with squared error, this landscape has a particularly friendly shape: it&#8217;s a smooth bowl. Intuitively, if you start from a bad line and move a little in a helpful direction, the loss tends to decrease smoothly; you don&#8217;t hit unpredictable cliffs. That&#8217;s why the metaphor is not just motivational - it reflects a real mathematical property (convexity) that makes training reliable.</p><p>But even if you don&#8217;t use that word, the practical takeaway is simple: in this setting, there aren&#8217;t many &#8220;<em>competing explanations</em>&#8221; with similar loss. There is a single best region. So if your training is unstable, it&#8217;s often because of the step size, data scaling, or implementation - problems you can fix - rather than because the landscape itself is treacherous.</p><p>This mental picture is also a bridge to gradient descent: if you can estimate the local slope of the terrain, you can decide where to step next.</p><h2>Gradient descent: learning by taking steps you can justify</h2><p>Gradient descent can be introduced without derivatives by asking a plain question: <strong>if I nudge a knob slightly, does the loss go up or down?</strong> If nudging <code>(b&#8321;)</code> upward makes the loss smaller, you should increase <code>(b&#8321;)</code>. If it makes the loss bigger, you should decrease <code>(b&#8321;)</code>. Do the same for <code>(b&#8320;)</code>. Repeat.</p><p>Derivatives enter as the clean, efficient way to compute those &#8220;<em>nudges</em>&#8221; without trial-and-error probing. They tell you how sensitive the loss is to each parameter.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Z1ic!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84462ec-35eb-4bcb-9379-6957373061f1_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Z1ic!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84462ec-35eb-4bcb-9379-6957373061f1_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!Z1ic!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84462ec-35eb-4bcb-9379-6957373061f1_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!Z1ic!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84462ec-35eb-4bcb-9379-6957373061f1_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!Z1ic!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84462ec-35eb-4bcb-9379-6957373061f1_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Z1ic!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84462ec-35eb-4bcb-9379-6957373061f1_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f84462ec-35eb-4bcb-9379-6957373061f1_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3252345,&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://iam.slys.dev/i/187834422?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84462ec-35eb-4bcb-9379-6957373061f1_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Z1ic!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84462ec-35eb-4bcb-9379-6957373061f1_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!Z1ic!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84462ec-35eb-4bcb-9379-6957373061f1_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!Z1ic!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84462ec-35eb-4bcb-9379-6957373061f1_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!Z1ic!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84462ec-35eb-4bcb-9379-6957373061f1_1536x1024.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>Let&#8217;s derive the gradients for <code>MSE</code> step by step, carefully.</p><p>Start with:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;L = \\frac{1}{n}\\sum e_i^2&quot;,&quot;id&quot;:&quot;XDOKPKYJCM&quot;}" data-component-name="LatexBlockToDOM"></div><p>Residual is:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;e_i = y_i - \\hat{y}_i&quot;,&quot;id&quot;:&quot;GVBGYETXIP&quot;}" data-component-name="LatexBlockToDOM"></div><p>And prediction is:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat{y}_i = b_0 + b_1 x_i&quot;,&quot;id&quot;:&quot;GNNXHMENKZ&quot;}" data-component-name="LatexBlockToDOM"></div><p>So:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;e_i = y_i - (b_0 + b_1 x_i)&quot;,&quot;id&quot;:&quot;JYBOUDDYMX&quot;}" data-component-name="LatexBlockToDOM"></div><p>Differentiate <code>(L)</code> with respect to <code>(b&#8320;)</code>. Use the chain rule:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{\\partial L}{\\partial b_0} = \\frac{1}{n}\\sum 2e_i \\frac{\\partial e_i}{\\partial b_0}&quot;,&quot;id&quot;:&quot;SXXOUBTOHS&quot;}" data-component-name="LatexBlockToDOM"></div><p>Since <code>(e&#7522; = y&#7522; - b&#8320; - b&#8321; x&#7522;)</code>,</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{\\partial e_i}{\\partial b_0} = -1&quot;,&quot;id&quot;:&quot;CVKMMBMVVJ&quot;}" data-component-name="LatexBlockToDOM"></div><p>So:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{\\partial L}{\\partial b_0} = -\\frac{2}{n}\\sum e_i&quot;,&quot;id&quot;:&quot;JOONBKMQGA&quot;}" data-component-name="LatexBlockToDOM"></div><p>Similarly for <code>(b&#8321;)</code>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{\\partial L}{\\partial b_1} = \\frac{1}{n}\\sum 2e_i \\frac{\\partial e_i}{\\partial b_1}&quot;,&quot;id&quot;:&quot;NSCCXRRXPB&quot;}" data-component-name="LatexBlockToDOM"></div><p>But:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{\\partial e_i}{\\partial b_1} = -x_i&quot;,&quot;id&quot;:&quot;AFLAILOLDB&quot;}" data-component-name="LatexBlockToDOM"></div><p>So:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{\\partial L}{\\partial b_1} = -\\frac{2}{n}\\sum e_i x_i&quot;,&quot;id&quot;:&quot;DRMDNBAETT&quot;}" data-component-name="LatexBlockToDOM"></div><p>Gradient descent updates:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;b_0 := b_0 - \\alpha \\frac{\\partial L}{\\partial b_0}&quot;,&quot;id&quot;:&quot;STCKSRFJBM&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;b_1 := b_1 - \\alpha \\frac{\\partial L}{\\partial b_1}&quot;,&quot;id&quot;:&quot;RQLLEZRALY&quot;}" data-component-name="LatexBlockToDOM"></div><p>Here <code>(&#120572;)</code> is the learning rate: too large and you overshoot; too small and you crawl.</p><p><strong>Worked example (one gradient step).</strong><br>Data: <code>((1, 2))</code>, <code>((2, 3))</code>. Start <code>(b&#8320; = 0)</code>, <code>(b&#8321; = 0)</code>.</p><p>Predictions: <code>(&#375; = [0, 0])</code>. Residuals: <code>(e = [2, 3])</code>.</p><p>Initial <code>MSE</code>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;L = \\frac{1}{2}(2^2+3^2)&quot;,&quot;id&quot;:&quot;JGLCEITUYJ&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;L = \\frac{1}{2}(4+9)=6.5&quot;,&quot;id&quot;:&quot;CUQRJZGYZR&quot;}" data-component-name="LatexBlockToDOM"></div><p>Gradients:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{\\partial L}{\\partial b_0} = -\\frac{2}{2}(2+3)=-5&quot;,&quot;id&quot;:&quot;CDDZRRVFEY&quot;}" data-component-name="LatexBlockToDOM"></div><p></p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{\\partial L}{\\partial b_1} = -\\frac{2}{2}(2\\cdot1+3\\cdot2)= -(2+6)=-8&quot;,&quot;id&quot;:&quot;NKTPIVWIUJ&quot;}" data-component-name="LatexBlockToDOM"></div><p></p><p>Choose <code>(&#120572; = 0.1)</code>. Update:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;b_0 = 0 - 0.1(-5)=0.5&quot;,&quot;id&quot;:&quot;VPWQGOLOKU&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;b_1 = 0 - 0.1(-8)=0.8&quot;,&quot;id&quot;:&quot;OSFKYTJFFP&quot;}" data-component-name="LatexBlockToDOM"></div><p>New predictions: at <code>(x = 1)</code>, <code>(&#375; = 1.3)</code>; at <code>(x = 2)</code>, <code>(&#375; = 2.1)</code>. Residuals: <code>(0.7, 0.9)</code>.</p><p>New <code>MSE</code>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;L = \\frac{1}{2}(0.7^2+0.9^2)&quot;,&quot;id&quot;:&quot;FNNPDIYKPD&quot;}" data-component-name="LatexBlockToDOM"></div><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;L = \\frac{1}{2}(0.49+0.81)=0.65&quot;,&quot;id&quot;:&quot;JCITTMWRYU&quot;}" data-component-name="LatexBlockToDOM"></div><p>One justified step took loss from <code>(6.5)</code> to <code>(0.65)</code>. That&#8217;s the learning loop in miniature.</p><h2>A concrete walkthrough: hours studied vs. exam score</h2><p>Now let&#8217;s step away from arithmetic and return to the lived feel of training.</p><p>Imagine a dataset of students: hours studied <code>(x-axis)</code> and exam score <code>(y-axis)</code>. You plot it and see the usual mess. Some students studied a lot but scored poorly - sleep deprivation, stress, test anxiety, maybe they focused on the wrong topics. Some studied little but scored well - prior knowledge, strong intuition, lucky question coverage. But despite the chaos, the cloud leans upward: more studying tends to help.</p><p>Suppose your initial line is too flat. Conceptually, that means it doesn&#8217;t &#8220;<em>respect</em>&#8221; the upward lean of the data. What happens to residuals? On the right side (high study hours), the points tend to sit above your line. Those are positive residuals: you underpredicted the high-study students. On the left side (low study hours), points tend to sit below your line. Those are negative residuals: you overpredicted the low-study students.</p><p>That residual pattern is not random noise; it&#8217;s a coherent critique. It&#8217;s the dataset saying: &#8220;<em>Your slope is too small</em>&#8221;.</p><p>So the optimizer increases the slope a little. As the slope increases, the right end of the line rises (helping underpredictions on high study hours) and the left end drops relative to before (helping overpredictions on low study hours). The loss decreases.</p><p>Then you might notice something else: perhaps the line is now consistently too low overall. That would show up as many positive residuals across the board, suggesting the intercept should increase. Training becomes a repeated, almost conversational adjustment: tilt, shift, re-check. Not until it&#8217;s perfect - until further adjustments stop making meaningful improvements.</p><h2>What &#8220;<em>best fit</em>&#8221; really means: the line that makes the fairest trade-offs</h2><p>&#8220;<em>Best fit</em>&#8221; is a dangerously friendly phrase, because it sounds like the line should match as many points as possible. But in noisy data, matching many points exactly is not the goal - and often not even possible.</p><p>Under squared error, the &#8220;<em>best</em>&#8221; line is the one that minimizes <code>MSE</code>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\text{MSE} = \\frac{1}{n}\\sum e_i^2&quot;,&quot;id&quot;:&quot;UQXGUMRATE&quot;}" data-component-name="LatexBlockToDOM"></div><p>That definition implies a particular kind of compromise. Every data point gets a vote, and the line positions itself to make the total squared discomfort as small as possible. It will not satisfy the outliers, and it will not satisfy every region equally. Instead, it makes trade-offs according to the rules encoded in the loss function.</p><p>This is why I sometimes describe optimization as producing a notion of &#8220;<em>fairness</em>&#8221;, but a very specific, mechanical fairness: fairness <strong>as defined by the objective</strong>. Squared error gives more influence to points with large residuals, because shrinking a large residual yields a big loss reduction. If you used absolute error, you would get a different &#8220;<em>fair</em>&#8221; compromise. If you weighted certain points more heavily (for example, high-value customers), you&#8217;d get yet another.</p><p>This framing helps you avoid a common confusion: if the fitted line doesn&#8217;t look like what you expected, it may not be &#8220;<em>wrong</em>.&#8221; It may be doing exactly what you asked under your chosen loss - especially if the dataset contains outliers, nonlinearity, or heteroskedastic noise (changing variance). &#8220;<em>Best fit</em>&#8221; is not a platonic ideal. It&#8217;s the best answer to a question you defined.</p><h2>The model&#8217;s humility: what linear regression refuses to claim</h2><p>A straight line can&#8217;t express a threshold, a curve, a plateau, or an interaction - at least not without additional feature engineering. If the real relationship is &#8220;<em>benefit increases quickly at first, then levels off</em>&#8221;, a line will insist on one constant rate of change. If the real relationship is &#8220;<em>nothing happens until </em><code>(x)</code><em> crosses a boundary</em>&#8221;, a line will smear that boundary into gradual change. If two variables interact (say, studying helps only if you also sleep enough), a single-feature linear regression can&#8217;t see that at all.</p><p>This is not a bug. It is the consequence of choosing a simple explanation on purpose.</p><p>There&#8217;s a kind of integrity in that refusal. Linear regression will not pretend to have discovered a complex mechanism. It will tell you only what fits inside its vocabulary: an overall trend plus a baseline. That&#8217;s why it remains a foundational learning tool - because it makes the boundary between &#8220;<em>what the model can say</em>&#8221; and &#8220;<em>what the world might be doing</em>&#8221; very clear.</p><p>In practice, this humility makes linear regression useful even when it&#8217;s not &#8220;<em>the best predictor</em>&#8221;. It&#8217;s often an excellent baseline and a strong diagnostic. If a complex model claims huge gains over linear regression, you can ask: is the relationship truly nonlinear, or did the complex model pick up leakage, spurious correlations, or noise? If linear regression performs surprisingly well, that&#8217;s also information: perhaps your problem is dominated by a simple trend, and you should focus on data quality, measurement, and feature design rather than architectural complexity.</p><p>Learning to appreciate what a model refuses to claim is a deep ML habit. It keeps you honest, and it keeps your systems more stable.</p><h2>When optimization can&#8217;t save you: the failure modes that look like &#8220;<em>bad training</em>&#8221;</h2><p>There are times when you can do everything &#8220;<em>right</em>&#8221; in training - correct code, stable optimization, convergence - and still feel disappointed. This is not always a training problem. Often it&#8217;s a problem of mismatch between the model family, the features, and the world.</p><p>If the true relationship is curved, no amount of optimizing a line will remove structure from the residuals. You can minimize MSE perfectly <em>within the space of lines</em> and still see a systematic pattern: residuals positive in the middle and negative at the ends, or vice versa. That pattern is a clue that your explanation vocabulary is too small.</p><p>If an important variable is missing, the model will treat its influence as noise. Imagine predicting exam score from hours studied but ignoring sleep. The model will &#8220;<em>see</em>&#8221; sleep as unexplained variability. Your residuals will be larger than they need to be, and you may falsely conclude the problem is inherently unpredictable.</p><p>Outliers are another classic failure mode, especially under squared error. Since squared error punishes large residuals heavily, a few extreme points can tug the line away from the majority. The optimizer is not misbehaving; it is faithfully minimizing the objective you gave it. If those outliers are measurement errors, this is painful. If they are legitimate rare cases, you have a real trade-off: do you want to serve the typical case well, or do you want to reduce catastrophic failures on rare points?</p><p>The practical lesson is: when results look like &#8220;<em>bad training</em>&#8221;, ask whether the model is actually failing to optimize - or whether it is optimizing something reasonable over a representation that cannot capture the structure you care about.</p><h2>Common misunderstandings: where beginners talk themselves into the wrong conclusion</h2><p>Linear regression is interpretable, which is wonderful, but it also invites overconfident storytelling.</p><p>A big one is causation. A fitted slope does not mean <code>(x)</code> causes <code>(y)</code>. It means that within your dataset, variation in <code>(x)</code> is associated with variation in <code>(y)</code>, after whatever other features you included (if any). Confounders can create impressive slopes that vanish when you measure the right variables. Correlation is not a technicality - it&#8217;s the default state of observational data.</p><p>Another misunderstanding is treating the slope as a law of nature. The slope depends on the population you sampled, the range of <code>(x)</code> values present, and the other variables you did or did not include. Add a new feature and the slope can change, not because the world changed, but because you changed what the model is allowed to attribute to each feature.</p><p>A third trap is trusting a single summary metric too much. <code>MSE</code> can look &#8220;<em>good</em>&#8221; while the model makes systematic errors in certain ranges of <code>(x)</code>, or for certain subgroups. You need residual plots and slice analysis to see whether the errors are randomly scattered (a sign you&#8217;ve captured the main structure) or patterned (a sign you&#8217;re missing something).</p><h2>Closing: the real heartbeat is &#8220;<em>define error, then minimize it</em>&#8221;</h2><p>If you zoom out far enough, linear regression stops being &#8220;<em>about lines</em>&#8221; and becomes &#8220;<em>about learning as disciplined correction</em>&#8221;.</p><p>You start with a parameterized rule - a rule with knobs. You don&#8217;t pretend it&#8217;s true. You simply commit to a vocabulary of explanations you&#8217;re willing to use. Then you define what it means to be wrong in a way that is explicit and computable. That definition becomes a single objective number. And once you have a number, you can improve the rule by changing the knobs to reduce it.</p><p>That is the heartbeat: <strong>define error, then minimize it</strong>.</p><p>Everything else - residuals, squared loss, gradients, learning rates - is structure built around that rhythm to make it reliable. Linear regression is the cleanest place to hear the beat without distractions. The moment you internalize it, later models feel less like magic. A neural network is not an alien artifact; it is the same loop with more knobs. A large language model is the same loop at enormous scale. The details matter, of course - but the mindset transfers.</p><p>There&#8217;s also a quiet reassurance in this. Machine learning can look like a zoo of architectures and acronyms. Linear regression reminds you that, underneath, we&#8217;re doing something modest and human: we propose a simple story, we listen to how it fails, and we revise the story according to a rule we can explain. If you can do that with a line, you&#8217;re already thinking the way the rest of machine learning asks you to think - even when the models stop looking like lines.</p><div><hr></div><p>&#10084;&#65039; <em>If you felt the heartbeat&#8230;</em></p><p>The mindset behind linear regression is the same one powering neural networks and language models - just scaled up.</p><p>&#128257; <strong>Share</strong> this with someone learning machine learning - it might save them years of confusion.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/linear-regression-the-heartbeat-of?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/linear-regression-the-heartbeat-of?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><ol><li><p><strong><a href="https://a.co/d/0jlbjexn">An Introduction to Statistical Learning: with Applications in Python (Springer Texts in Statistics) 2023rd Edition</a></strong> <em>by Gareth James, Daniela Witten, Trevor Hastie , Robert Tibshirani, Jonathan Taylor</em></p></li><li><p><strong><a href="https://a.co/d/0dK0vp8Y">The Elements of Statistical Learning: Data Mining, Inference, and Prediction, Second Edition</a></strong> <em>by Trevor Hastie, Robert Tibshirani, Jerome Friedman</em></p></li><li><p><strong><a href="https://a.co/d/03Bb6pWe">All of Statistics: A Concise Course in Statistical Inference (Springer Texts in Statistics)</a></strong> <em>by Larry Wasserman</em></p></li><li><p><strong><a href="https://a.co/d/02kYNKuh">Pattern Recognition and Machine Learning (Information Science and Statistics)</a></strong></p><p><em>by Christopher M. Bishop</em></p></li><li><p><strong><a href="https://scikit-learn.org/stable/modules/linear_model.html">scikit-learn documentation (Linear Models)</a></strong></p></li><li><p><strong><a href="https://cs229.stanford.edu/main_notes.pdf">Stanford CS229 notes (linear regression + optimization)</a> </strong><em>by Andrew Ng and Tengyu Ma</em></p></li></ol>]]></content:encoded></item><item><title><![CDATA[Thinking clearly in interviews — Two Sum]]></title><description><![CDATA[Understanding the why before the code (Rust &#183; Python &#183; Scala)]]></description><link>https://iam.slys.dev/p/thinking-clearly-in-interviews-two</link><guid isPermaLink="false">https://iam.slys.dev/p/thinking-clearly-in-interviews-two</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 16 Feb 2026 21:19:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!SkPr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SkPr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SkPr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!SkPr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!SkPr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!SkPr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SkPr!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2253121,&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://iam.slys.dev/i/187834092?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SkPr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!SkPr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!SkPr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!SkPr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60470bed-6c74-4f6e-9005-062c48e182f2_1536x1024.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>&#9888;&#65039; <strong>Before we dive in &#8212; highly worth your attention</strong></p><div class="embedded-publication-wrap" data-attrs="{&quot;id&quot;:3477571,&quot;embedding_publication_id&quot;:null,&quot;name&quot;:&quot;Lights On by Farida&quot;,&quot;logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!rtf8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0130bc80-296c-498f-8eef-8962c2088bcd_1024x1024.png&quot;,&quot;base_url&quot;:&quot;https://fafi25.substack.com&quot;,&quot;hero_text&quot;:&quot;A systems-driven newsletter from a data engineer and cybersecurity thinker, decoding complex data, technology, and business trends into actionable insight.&quot;,&quot;author_name&quot;:&quot;Farida Khalaf&quot;,&quot;show_subscribe&quot;:true,&quot;logo_bg_color&quot;:&quot;#faf5ff&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="EmbeddedPublicationToDOMWithSubscribe"><div class="embedded-publication show-subscribe"><a class="embedded-publication-link-part" native="true" href="https://fafi25.substack.com?utm_source=substack&amp;utm_campaign=publication_embed&amp;utm_medium=web"><img class="embedded-publication-logo" src="https://substackcdn.com/image/fetch/$s_!rtf8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0130bc80-296c-498f-8eef-8962c2088bcd_1024x1024.png" width="56" height="56" style="background-color: rgb(250, 245, 255);"><span class="embedded-publication-name">Lights On by Farida</span><div class="embedded-publication-hero-text">A systems-driven newsletter from a data engineer and cybersecurity thinker, decoding complex data, technology, and business trends into actionable insight.</div><div class="embedded-publication-author-name">By Farida Khalaf</div></a><form class="embedded-publication-subscribe" method="GET" action="https://fafi25.substack.com/subscribe?"><input type="hidden" name="source" value="publication-embed"><input type="hidden" name="autoSubmit" value="true"><input type="email" class="email-input" name="email" placeholder="Type your email..."><input type="submit" class="button primary" value="Subscribe"></form></div></div><p><em><strong>Lights On</strong></em> by <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Farida Khalaf&quot;,&quot;id&quot;:47192869,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!nBHI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F117d97dc-0da6-4fcf-9202-f7b5e956c047_1024x1024.png&quot;,&quot;uuid&quot;:&quot;6afb7f7c-5137-4ffa-9f95-16b77d82fe6b&quot;}" data-component-name="MentionToDOM"></span> - a publication where systems thinking meets <em>real-world AI</em> and <em>business insights</em>. Farida breaks down complexity into clarity, with frameworks and perspectives that make tech trends feel actionable rather than overwhelming.</p><div><hr></div><p>You&#8217;re standing in a small market with a basket in one hand and a short list in the other. You remember you have a coupon that takes exactly a fixed amount off - say, $9 - but it only applies if you buy two items whose prices add up to that amount. Easy, you think: <strong>just glance at the tags and find a pair</strong>.</p><p>Then the mild confusion starts. There are more items than you expected. Some prices repeat. A few are even discounted in odd ways, so the same number shows up again and again. You catch yourself circling the same shelf twice, picking up an item, putting it back, and wondering if you already considered it. The problem isn&#8217;t that anything is &#8220;<em>wrong</em>&#8221; - it&#8217;s that your brain is doing a very human thing: <strong>searching without a system</strong>.</p><p>Nothing is broken here. This is what happens when the space of possibilities grows: <strong>what felt like a quick visual match turns into a process problem</strong>. And process problems are exactly what coding interviews are designed to surface - not whether you&#8217;ve seen a trick before, but whether you can turn a fuzzy search into a clean, explainable method.</p><blockquote><p><strong>Problem</strong></p><p>Given an array of integers, return indices of the two numbers such that they add up to a specific target.</p><p><strong>Examples</strong></p><p>Given <code>nums = [2, 7, 11, 15]</code>, <code>target = 9</code>, Because <code>nums[0] + nums[1] = 2 + 7 = 9</code>, return <code>[0, 1].</code></p><p><strong>Constraints</strong></p><p>You may assume that each input would have exactly one solution, and you may not use the same element twice.</p></blockquote><p>The Two Sum problem is that market moment, translated into arrays and indices. The best solutions don&#8217;t come from moving faster; they come from deciding what you&#8217;re going to remember as you scan, and how you&#8217;ll prove to yourself (and an interviewer) that you didn&#8217;t miss anything.</p><h1>&#127897;&#65039; Why is it so interesting</h1><p>From an interviewer&#8217;s perspective, Two Sum is deceptively small. The statement fits in a couple of lines, the examples look friendly, and most candidates feel they &#8220;<em>basically get it</em>&#8221; within seconds. That&#8217;s exactly why it works: it creates a situation where it&#8217;s easy to start coding before you&#8217;ve demonstrated good engineering judgment.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MZEP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fe46a90-1ba7-4db8-9c71-e9dc02de8902_2268x1300.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MZEP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fe46a90-1ba7-4db8-9c71-e9dc02de8902_2268x1300.png 424w, https://substackcdn.com/image/fetch/$s_!MZEP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fe46a90-1ba7-4db8-9c71-e9dc02de8902_2268x1300.png 848w, https://substackcdn.com/image/fetch/$s_!MZEP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fe46a90-1ba7-4db8-9c71-e9dc02de8902_2268x1300.png 1272w, https://substackcdn.com/image/fetch/$s_!MZEP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fe46a90-1ba7-4db8-9c71-e9dc02de8902_2268x1300.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MZEP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fe46a90-1ba7-4db8-9c71-e9dc02de8902_2268x1300.png" width="724.953125" height="415.75265066964283" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1fe46a90-1ba7-4db8-9c71-e9dc02de8902_2268x1300.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:835,&quot;width&quot;:1456,&quot;resizeWidth&quot;:724.953125,&quot;bytes&quot;:203227,&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://iam.slys.dev/i/187834092?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fe46a90-1ba7-4db8-9c71-e9dc02de8902_2268x1300.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_!MZEP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fe46a90-1ba7-4db8-9c71-e9dc02de8902_2268x1300.png 424w, https://substackcdn.com/image/fetch/$s_!MZEP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fe46a90-1ba7-4db8-9c71-e9dc02de8902_2268x1300.png 848w, https://substackcdn.com/image/fetch/$s_!MZEP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fe46a90-1ba7-4db8-9c71-e9dc02de8902_2268x1300.png 1272w, https://substackcdn.com/image/fetch/$s_!MZEP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fe46a90-1ba7-4db8-9c71-e9dc02de8902_2268x1300.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>A strong interviewer isn&#8217;t looking for whether you can add two numbers. They&#8217;re watching for whether you can do a few higher-signal things under light time pressure:</p><ul><li><p><strong>You clarify what must be returned.</strong> Many candidates jump to returning the numbers themselves, not their indices. That&#8217;s not a tiny mistake; it indicates you&#8217;re not anchoring on requirements.</p></li><li><p><strong>You manage ambiguity proactively.</strong> What about duplicates? Can the same element be used twice? Is there always a solution? These questions change the shape of the code, and interviewers want to see you surface those constraints instead of stumbling into them mid-implementation.</p></li><li><p><strong>You reason about trade-offs.</strong> Two Sum has a clean naive solution and a clean optimized solution. Interviewers want to see if you can explain both, choose appropriately given constraints, and justify the complexity out loud.</p></li><li><p><strong>You implement carefully.</strong> The &#8220;<em>optimal</em>&#8221; idea is simple - a hash map lookup - but it&#8217;s also easy to get wrong with off-by-one mistakes, wrong insertion order, or duplicate handling. That&#8217;s the point: can you write correct, readable code while narrating your reasoning?</p></li></ul><p>Two Sum is also a gateway. The same mental pattern shows up everywhere: &#8220;<em>I&#8217;m scanning a list; what facts do I need to remember so I can decide quickly when I see the next element?</em>&#8221; If you can communicate that pattern well here, you&#8217;re building interview leverage for a whole family of problems.</p><h1>&#128214; Understanding the problem in plain language</h1><p>You&#8217;re given a list of integers and a target integer. You need to find <em>two different positions</em> in the list such that the values at those positions add up exactly to the target.</p><p>The output is not the values - it&#8217;s the <em>indices</em> (positions) of the two elements. The order of the returned indices usually doesn&#8217;t matter unless explicitly stated, but you should be consistent.</p><p>A few constraints matter a lot:</p><ul><li><p>There is exactly one valid answer. That means you don&#8217;t have to return all pairs, and you don&#8217;t have to deal with &#8220;<em>no solution</em>&#8221; unless the interviewer extends the problem.</p></li><li><p>You may not use the same element twice. If the target is 10 and the array contains a single 5, you can&#8217;t use that one 5 two times. But if the array contains two 5s at different indices, then you can use those two distinct elements.</p></li><li><p>Values can be negative, zero, or repeated, unless otherwise restricted. Many candidates subconsciously assume &#8220;<em>positive and distinct</em>&#8221; because the examples look that way. Don&#8217;t.</p></li></ul><p>When I restate this to a colleague, I usually say:</p><pre><code>&#8220;Scan this array and return the two positions i and j (i &#8800; j) where nums[i] + nums[j] == target.&#8221;</code></pre><p>That restatement is important because it highlights the real work: you&#8217;re not doing arithmetic, you&#8217;re doing <em>search</em>, and you&#8217;re doing it while preserving original positions.</p><h1>&#128099; Reasoning through examples</h1><p>Let&#8217;s walk slowly through the classic example:</p><ul><li><p><code>nums = [2, 7, 11, 15]</code></p></li><li><p><code>target = 9</code></p></li></ul><p>A good candidate narrates something like:</p><blockquote><p>&#8220;<em>I need two indices. If I pick 2 at index 0, I need 7 to reach 9. Is 7 present later? Yes - index 1. So the answer is [0, 1].</em>&#8221;</p></blockquote><p>That&#8217;s correct, but the interviewer wants more than correctness on the toy input. They want to hear a process that scales.</p><p>Here&#8217;s the more interview-useful narration:</p><blockquote><p>&#8220;<em>As I scan left to right, for each number x I can compute its complement </em><code>c = target - x</code><em>. If I&#8217;ve already seen c earlier, then I&#8217;ve found the pair: the earlier index of c and the current index. If not, I store x with its index for future lookups.&#8221;</em></p></blockquote><p>Now you&#8217;ve described an algorithm, not just an observation.</p><p>Let&#8217;s test the narration on something slightly trickier:</p><ul><li><p><code>nums = [3, 3]</code></p></li><li><p><code>target = 6</code></p></li></ul><p>If you store the first 3 at index 0, then when you see the second 3 at index 1, the complement is also 3, which you&#8217;ve seen. You return [0, 1]. This example is important because it proves you understand &#8220;<em>not the same element twice</em>&#8221; really means &#8220;<em>not the same index twice</em>&#8221;.</p><p>Another example that reveals common misunderstandings:</p><ul><li><p><code>nums = [1, 5, 1, 5]</code></p></li><li><p><code>target = 6</code></p></li></ul><p>There are many value matches, but the problem guarantees exactly one solution in the test setup. Still, your code should behave sensibly with duplicates. If you use a hash map of value &#8594; index, you have to decide: do you store the first index, the last index, or does it matter? It can matter if the complement equals the current value (like 3 and 3), or if the &#8220;<em>one solution</em>&#8221; guarantee is removed as a follow-up.</p><p>The key is: your reasoning needs to explicitly connect &#8220;<em>what I store</em>&#8221; with &#8220;<em>what I will look up later</em>&#8221;, and that connection should remain correct even when values repeat.</p><h1>&#128207; Constraints and what they tell us</h1><p>Interview constraints are often understated, but Two Sum is one of those problems where constraints quietly dictate the right approach.</p><p>If the array length n is small (say, a few hundred), an <code>O(n&#178;)</code> solution is perfectly fine in production and fine in many interviews. But interviews rarely choose Two Sum for <code>n=200</code>. They choose it because they want to see whether you recognize that <code>O(n&#178;)</code> becomes painful when n is large - tens of thousands, hundreds of thousands, or more.</p><p>The &#8220;<em>exactly one solution</em>&#8221; constraint is also a hint. It tells you:</p><ul><li><p>You can return as soon as you find a valid pair.</p></li><li><p>You don&#8217;t need to keep searching for alternatives.</p></li><li><p>You don&#8217;t need to worry about tie-breaking.</p></li></ul><p>The &#8220;<em>may not use the same element twice</em>&#8221; constraint is a bigger hint than it looks. It forces you to think about timing:</p><ul><li><p>If you build a map of all values first and then search, you must be careful not to pair an element with itself unless there are duplicates.</p></li><li><p>If you scan left-to-right and only allow matches with previously seen values, you automatically avoid using the same index twice. That&#8217;s a very clean correctness argument, and interviewers like correctness arguments that are easy to say out loud.</p></li></ul><p>Finally, the fact that you must return indices (not values) tells you you can&#8217;t sort the array without extra work, because sorting destroys original positions. You <em>can</em> sort if you carry indices along, but that&#8217;s already a signal: &#8220;<em>I&#8217;m choosing a more complex approach than necessary</em>&#8221;.</p><p>Constraints are the interviewer quietly telling you what kind of thinking they want to see: time complexity awareness, careful handling of duplicates, and a solution that preserves index identity.</p><h1>&#129300; The obvious first idea &#8212; and why it&#8217;s not enough</h1><p>The most natural approach is brute force:</p><ul><li><p>For each index <code>i</code></p></li><li><p>For each index <code>j &gt; i</code></p></li><li><p>Check if <code>nums[i] + nums[j] == target</code></p></li><li><p>If yes, return <code>[i, j]</code></p></li></ul><p>This is the algorithm your brain does in the market when you keep re-checking shelves: <em>try a choice, then try to match it with every other choice</em>.</p><p>Why it feels good:</p><ul><li><p>It&#8217;s simple.</p></li><li><p>It&#8217;s hard to get wrong.</p></li><li><p>It doesn&#8217;t require any extra data structure.</p></li><li><p>The code is short and readable.</p></li></ul><p>Why it&#8217;s not enough (in many interviews):</p><p>Time complexity is <code>O(n&#178;)</code>. If <code>n = 100,000</code>, that&#8217;s ~5 billion pairs in the worst case. Even if each check is fast, that&#8217;s not going to finish in a reasonable time.</p><p>More subtly, brute force doesn&#8217;t demonstrate the interview skill this problem is testing: the ability to reduce search using memory. An interviewer might accept brute force as a starting point, but they&#8217;re hoping you&#8217;ll quickly move to something better and explain <em>why</em> it&#8217;s better.</p><p>In a live setting, a strong move is:</p><ul><li><p>State the brute force approach clearly.</p></li><li><p>Give its complexity.</p></li><li><p>Then say, calmly: &#8220;<em>We can do better by trading space for time</em>.&#8221;</p></li></ul><p>That last sentence is the hinge. It signals you understand that performance improvements usually come from changing what you remember, not from writing clever arithmetic.</p><h1>&#128161; The key insight</h1><p>Two Sum becomes easy when you stop thinking of it as &#8220;<em>find two numbers</em>&#8221; and start thinking of it as:</p><blockquote><p>&#8220;<em>As I scan, what information would make the next decision instant?</em>&#8221;</p></blockquote><p>Suppose you&#8217;re currently at value <code>x</code>. You want to know if there exists a previous value <code>y</code> such that <code>y + x == target</code>. Rearranged:</p><p><code>y == target - x</code></p><p>So the decision at x reduces to a question you can answer instantly <em>if</em> you have the right memory:</p><blockquote><p><em>&#8220;Have I seen (</em><code>target - x</code><em>) before? If yes, where?&#8221;</em></p></blockquote><p>That suggests a hash map (dictionary) keyed by value, storing the index where you saw it.</p><p>The insight has two important parts:</p><ul><li><p><strong>Complement thinking:</strong> Every element defines exactly what it needs from the past (the complement).</p></li><li><p><strong>One-pass memory:</strong> If you store what you&#8217;ve seen so far, you can decide in <code>O(1)</code> average time per element.</p></li></ul><p>This is not about memorizing that &#8220;<em>Two Sum uses a hash map</em>&#8221;. It&#8217;s about noticing a pattern: <em>you don&#8217;t need to search all pairs if you can transform the question into membership queries against a set of prior observations.</em></p><p>In interviews, it&#8217;s worth saying explicitly:</p><blockquote><p><em>&#8220;I&#8217;m going to scan left to right. For each element, I&#8217;ll check whether its complement is already in the map. If it is, I return immediately. Otherwise I insert the current element with its index.&#8221;</em></p></blockquote><p>That&#8217;s the moment the interviewer relaxes a bit - because now they can evaluate execution, not direction.</p><div><hr></div><h3>&#128161; Did this mental shift click for you?</h3><p>What was your &#8220;<em>aha</em>&#8221; moment while reading this?</p><p>&#128172; Leave a comment - I&#8217;d love to know how you think about Two Sum.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/thinking-clearly-in-interviews-two/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/thinking-clearly-in-interviews-two/comments"><span>Leave a comment</span></a></p><div><hr></div><h1>&#9881;&#65039;The final approach</h1><p>Narratively, here&#8217;s the algorithm:</p><p>You keep a dictionary <code>seen</code> that maps a number value to the index where you saw it.</p><p>Then for each index <code>i</code> and value <code>x = nums[i]</code>:</p><ul><li><p>Compute <code>c = target - x</code></p></li><li><p>If <code>c</code> is in <code>seen</code>, you found the answer: <code>[seen[c], i]</code></p></li><li><p>Otherwise store <code>seen[x] = i</code> and continue</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_!qkO2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fa2e952-f284-4fd3-978f-57fd1dd7c7a7_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qkO2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fa2e952-f284-4fd3-978f-57fd1dd7c7a7_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!qkO2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fa2e952-f284-4fd3-978f-57fd1dd7c7a7_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!qkO2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fa2e952-f284-4fd3-978f-57fd1dd7c7a7_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!qkO2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fa2e952-f284-4fd3-978f-57fd1dd7c7a7_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qkO2!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fa2e952-f284-4fd3-978f-57fd1dd7c7a7_1536x1024.png" width="1200" height="800.2747252747253" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1fa2e952-f284-4fd3-978f-57fd1dd7c7a7_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1497899,&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://iam.slys.dev/i/187834092?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fa2e952-f284-4fd3-978f-57fd1dd7c7a7_1536x1024.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_!qkO2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fa2e952-f284-4fd3-978f-57fd1dd7c7a7_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!qkO2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fa2e952-f284-4fd3-978f-57fd1dd7c7a7_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!qkO2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fa2e952-f284-4fd3-978f-57fd1dd7c7a7_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!qkO2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fa2e952-f284-4fd3-978f-57fd1dd7c7a7_1536x1024.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>Correctness, in words you can say live:</p><ul><li><p>If the answer uses indices <code>(p, q)</code> with <code>p &lt; q</code>, then when we reach q in the scan, we have already inserted <code>nums[p]</code> into <code>seen</code>.</p></li><li><p>At q, we compute complement target - <code>nums[q]</code>, which equals nums[p], so the lookup succeeds and we return <code>(p, q)</code>.</p></li><li><p>We never use the same element twice because we only match the current index with a strictly earlier index from the map.</p></li></ul><p>Edge behavior:</p><ul><li><p>Duplicates are naturally handled because values can appear multiple times; when the second instance appears, it can match the first if needed.</p></li><li><p>The only nuance is whether you store the first occurrence or overwrite with the last. With the &#8220;<em>exactly one solution</em>&#8221; guarantee, either often works, but storing the first occurrence makes the behavior more stable when duplicates exist and the guarantee is removed.</p></li></ul><p>A small but important implementation detail:</p><p>Check for the complement <strong>before</strong> inserting the current value. That ordering prevents pairing an element with itself when <code>x == target - x</code>. For example, target 6 and x 3: you want to match with a <em>previous</em> 3, not the one you&#8217;re currently at.</p><p>That check-then-insert rhythm is the cleanest version to explain and defend.</p><h1>&#9201;&#65039; Performance under interview constraints</h1><p>Time complexity:</p><ul><li><p>You do a single pass through n elements.</p></li><li><p>Each pass does a hash lookup and possibly an insert.</p></li><li><p>On average, those are <code>O(1)</code>, so total is <code>O(n)</code> average time.</p></li></ul><p>Space complexity:</p><ul><li><p>In the worst case you store each element in the map: <code>O(n)</code> space.</p></li></ul><p>In an interview, you don&#8217;t need to over-lawyer the hash table details, but you should be able to say one sentence acknowledging reality:</p><blockquote><p><em>&#8220;This is </em><code>O(n)</code><em> average time using a hash map; worst-case hash behavior exists in theory, but in practice and in typical interview models we treat it as constant-time expected operations.&#8221;</em></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2Me6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcaba3f1-a1ab-43aa-b714-cd472f0c8f51_1746x798.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2Me6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcaba3f1-a1ab-43aa-b714-cd472f0c8f51_1746x798.png 424w, https://substackcdn.com/image/fetch/$s_!2Me6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcaba3f1-a1ab-43aa-b714-cd472f0c8f51_1746x798.png 848w, https://substackcdn.com/image/fetch/$s_!2Me6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcaba3f1-a1ab-43aa-b714-cd472f0c8f51_1746x798.png 1272w, https://substackcdn.com/image/fetch/$s_!2Me6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcaba3f1-a1ab-43aa-b714-cd472f0c8f51_1746x798.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2Me6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcaba3f1-a1ab-43aa-b714-cd472f0c8f51_1746x798.png" width="1456" height="665" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bcaba3f1-a1ab-43aa-b714-cd472f0c8f51_1746x798.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:665,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:106695,&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://iam.slys.dev/i/187834092?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcaba3f1-a1ab-43aa-b714-cd472f0c8f51_1746x798.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_!2Me6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcaba3f1-a1ab-43aa-b714-cd472f0c8f51_1746x798.png 424w, https://substackcdn.com/image/fetch/$s_!2Me6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcaba3f1-a1ab-43aa-b714-cd472f0c8f51_1746x798.png 848w, https://substackcdn.com/image/fetch/$s_!2Me6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcaba3f1-a1ab-43aa-b714-cd472f0c8f51_1746x798.png 1272w, https://substackcdn.com/image/fetch/$s_!2Me6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcaba3f1-a1ab-43aa-b714-cd472f0c8f51_1746x798.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>Trade-offs you can mention calmly:</p><ul><li><p>If memory is constrained, you might consider sorting with indices (O(n log n) time, <code>O(n)</code> space for the index pairs) and using two pointers, but it&#8217;s more code and more ways to slip.</p></li><li><p>If you need all pairs (a common follow-up), the map approach needs adjustment because &#8220;<em>return immediately</em>&#8221; is no longer correct, and duplicates must be counted carefully.</p></li></ul><p>The point is not to recite complexities. The point is to show you can connect code structure to performance with confidence.</p><h1>&#128187; Implementation</h1><p>Implementation is secondary to clarity of thought - but in interviews, &#8220;<em>secondary</em>&#8221; still means &#8220;<em>must be correct</em>&#8221;.</p><p>Two Sum is a great place to practice readable implementation under time pressure:</p><ul><li><p>Use descriptive names (<code>seen</code>, <code>complement</code>).</p></li><li><p>Keep control flow linear.</p></li><li><p>Return early when you find the pair.</p></li><li><p>Make the &#8220;<em>check then insert</em>&#8221; ordering explicit.</p></li></ul><p>Below are reference implementations in <strong>Python</strong>, <strong>Rust</strong>, and <strong>Scala</strong>. Each is written in a style that&#8217;s easy to explain while typing.</p><h2>&#128013; Python implementation</h2><p>Python makes the core idea very direct: <em>a dictionary from value to index</em>.</p><pre><code>from typing import List

def two_sum(nums: List[int], target: int) -&gt; List[int]:
    # Maps value -&gt; index where it was seen
    seen = {}

    for i, x in enumerate(nums):
        complement = target - x
        if complement in seen:
            return [seen[complement], i]

        # Store AFTER checking, to avoid using the same element twice
        seen[x] = i

    # With the usual constraint &#8220;exactly one solution&#8221;, we never reach here.
    raise ValueError(&#8221;No valid pair found&#8221;)</code></pre><p>What to explain out loud while coding:</p><ul><li><p>&#8220;<code>seen</code><em> stores numbers we&#8217;ve already passed, along with their indices.</em>&#8221;</p></li><li><p>&#8220;<em>At each number x, I compute the complement and see if it&#8217;s already in </em><code>seen</code><em>.</em>&#8221;</p></li><li><p>&#8220;<em>If yes, return the earlier index and the current index.</em>&#8221;</p></li><li><p>&#8220;<em>Then I insert x for future elements.</em>&#8221;</p></li></ul><p>Common Python nuance:</p><p>If the interviewer asks about duplicates, you can say: &#8220;<em>This stores the latest index for each value because we overwrite. With the &#8216;exactly one solution&#8217; guarantee it&#8217;s fine; if we wanted the earliest index consistently, we could only insert when the value is not already present.</em>&#8221;</p><p>If you want that &#8220;<em>earliest index</em>&#8221; behavior, it&#8217;s a one-line change:</p><pre><code>if x not in seen:
    seen[x] = i</code></pre><p>But don&#8217;t add it unless you need it - extra branching is extra surface area for mistakes.</p><h2>&#129408; Rust implementation</h2><p>Rust is where candidates often get tangled - not because the algorithm changes, but because they start thinking about ownership before they&#8217;ve stabilized the control flow.</p><p>The trick is to keep it simple:</p><ul><li><p>Use <code>std::collections::HashMap&lt;i32, usize&gt;</code></p></li><li><p>Iterate with <code>enumerate()</code></p></li><li><p>Look up by reference (<code>get(&amp;complement)</code>)</p></li><li><p>Return indices as <code>Vec&lt;usize&gt;</code> or <code>Option&lt;(usize, usize)&gt;</code></p></li></ul><p>Here&#8217;s a clean version that returns a pair as <code>Option&lt;(usize, usize)&gt;</code>:</p><pre><code>use std::collections::HashMap;

pub fn two_sum(nums: &amp;[i32], target: i32) -&gt; Option&lt;(usize, usize)&gt; {
    let mut seen: HashMap&lt;i32, usize&gt; = HashMap::new();

    for (i, &amp;x) in nums.iter().enumerate() {
        let complement = target - x;

        if let Some(&amp;j) = seen.get(&amp;complement) {
            return Some((j, i));
        }

        // Insert after checking to avoid using the same index twice
        seen.insert(x, i);
    }

    None
}</code></pre><p>What&#8217;s worth calling out in Rust:</p><ul><li><p><code>nums.iter().enumerate()</code> gives <code>(usize, &amp;i32)</code>. We pattern-match <code>&amp;x</code> to copy the i32 out (cheap and simple).</p></li><li><p><code>seen.get(&amp;complement)</code> returns <code>Option&lt;&amp;usize&gt;</code>. We match <code>Some(&amp;j)</code> to copy the index out.</p></li><li><p>We insert with <code>seen.insert(x, i)</code> after the check.</p></li></ul><p>If you&#8217;re asked to match a typical &#8220;<em>always exactly one solution</em>&#8221; signature, you can unwrap (or panic) at the end, but I prefer returning <code>Option</code> because it keeps the function honest:</p><pre><code>pub fn two_sum_strict(nums: &amp;[i32], target: i32) -&gt; (usize, usize) {
    two_sum(nums, target).expect(&#8221;Exactly one solution was assumed&#8221;)
}</code></pre><p>Interviewers generally like when you separate &#8220;<em>core algorithm</em>&#8221; from &#8220;<em>assumptions about input</em>&#8221;, because it signals clean engineering instincts.</p><h2>&#128995; Scala implementation</h2><p>Scala gives you a couple of choices: immutable maps (rebuilding each time) or a mutable map (most interview-friendly here). For a one-pass <code>O(n)</code> scan, a mutable <code>HashMap</code> is straightforward.</p><pre><code>import scala.collection.mutable

object TwoSum {
  def twoSum(nums: Array[Int], target: Int): Array[Int] = {
    val seen = mutable.HashMap[Int, Int]()  // value -&gt; index

    for (i &lt;- nums.indices) {
      val x = nums(i)
      val complement = target - x

      seen.get(complement) match {
        case Some(j) =&gt; return Array(j, i)
        case None    =&gt; seen.update(x, i)
      }
    }

    // Under the usual constraint &#8220;exactly one solution&#8221;, this won&#8217;t happen.
    throw new IllegalArgumentException(&#8221;No valid pair found&#8221;)
  }
}</code></pre><p>Scala design notes you can mention:</p><ul><li><p><code>nums.indices</code> is a clean way to iterate indices without manual bounds.</p></li><li><p><code>seen.get(complement)</code> returns an <code>Option[Int]</code>, which we pattern match.</p></li><li><p><code>return</code> is used here intentionally for early exit. In purely functional Scala you might avoid <code>return</code>, but in interviews clarity beats purity - especially for a simple search with an early success condition.</p></li></ul><p>If your interviewer prefers expression-oriented style, you can still keep it readable, but don&#8217;t contort yourself. Two Sum is not the place to prove you can write clever Scala.</p><h1>&#9888;&#65039; Common interview mistakes</h1><p>Two Sum is famous, which means interviewers have seen every possible self-inflicted wound. The good news is that most mistakes are predictable.</p><p>A very common mistake is returning the values, not the indices. This usually happens when the candidate starts coding before restating the problem. The fix is procedural: always say &#8220;<em>indices</em>&#8221; out loud before you type your function signature.</p><p>Another common mistake is using the same element twice by inserting before checking. The buggy pattern looks like:</p><ul><li><p>insert <code>x</code> at index <code>i</code></p></li><li><p>check whether complement exists (it will - because you just inserted x)</p></li><li><p>accidentally return <code>(i, i)</code> when <code>x * 2 == target</code></p></li></ul><p>This is why &#8220;<em>check then insert</em>&#8221; is not just a detail - it&#8217;s a correctness guarantee you can explain in one sentence.</p><p>Duplicates cause another class of bugs. Candidates sometimes store a value &#8594; index map built from the whole array first, then do a second pass. That can work, but it forces you to handle the &#8220;<em>don&#8217;t use same index</em>&#8221; rule explicitly:</p><ul><li><p>If complement equals x, you must ensure the stored index is not i.</p></li><li><p>If there are duplicates, you might overwrite the index you actually need.</p></li></ul><p>That two-pass approach isn&#8217;t wrong, but it&#8217;s more complicated to reason about live, which makes it a riskier interview choice.</p><p>Finally, some candidates overcomplicate by sorting and using two pointers. That solution can be made correct by sorting <code>(value, originalIndex)</code> pairs, but it&#8217;s longer, easier to get wrong, and harder to narrate under time pressure. Unless the interviewer explicitly removes the hash map option (rare), sorting is usually not your best first move.</p><p>The meta-lesson: in interviews, prefer solutions that are easy to <em>defend</em>, not just easy to code.</p><div><hr></div><h3>&#9888;&#65039; Know someone preparing for interviews?</h3><p>Send this to them - it might save them from an avoidable mistake.</p><p>&#128257; Share this post with a friend grinding interview prep.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/thinking-clearly-in-interviews-two?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/thinking-clearly-in-interviews-two?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h1>&#128269; Edge cases interviewers love to ask about</h1><p>Once you produce the <code>O(n) </code>hash map solution, interviewers often probe whether you understand what makes it correct by twisting the input slightly.</p><p><strong>Duplicates where the pair uses the same value twice:</strong></p><ul><li><p><code>nums = [3, 3]</code>, <code>target = 6</code></p></li><li><p>Correct output must reference two different indices.</p></li></ul><p>If your code inserts before checking, you&#8217;ll fail here. If you check first, you&#8217;ll succeed.</p><p><strong>Negative numbers and zero:</strong></p><ul><li><p><code>nums = [-1, -2, -3, -4, -5]</code>, <code>target = -8</code> &#8594; indices for -3 and -5</p></li><li><p><code>nums = [0, 4, 3, 0]</code>, <code>target = 0</code> &#8594; indices for the two zeros</p></li></ul><p>This probes whether you accidentally assume positivity or uniqueness.</p><p><strong>Very large arrays:</strong></p><p>This is less about correctness and more about whether you can justify complexity. Interviewers want to hear: &#8220;<code>O(n)</code><em> time, </em><code>O(n)</code><em> space</em>&#8221; without hesitation, and they want you to connect the dots: &#8220;<em>We store at most one entry per element</em>&#8221;.</p><p><strong>&#8220;</strong><em><strong>What if there is no solution?</strong></em><strong>&#8221;:</strong></p><p>Even though the prompt says there is exactly one, interviewers often ask this to see if you can design a robust API. In Python you might raise an exception; in Rust you might return <code>Option</code>; in Scala you might return <code>Option[Array[Int]]</code> or throw. The key is to separate: &#8220;<em>Given the constraint, we can assume success</em>&#8221; from &#8220;<em>In production, I&#8217;d encode the possibility of failure</em>&#8221;.</p><p><strong>&#8220;</strong><em><strong>What if there are multiple solutions?</strong></em><strong>&#8221;:</strong></p><p>Now your early return behavior is no longer correct if the task is &#8220;<em>return all pairs</em>&#8221;. You&#8217;d need to collect results, and duplicates become tricky (you might need counts rather than indices, or a different approach entirely). The right response is calm: &#8220;<em>The current algorithm returns the first pair it finds; if you need all pairs, the requirements change and we need to redesign</em>&#8221;.</p><p>Edge cases aren&#8217;t a trap; they&#8217;re a conversation about whether you understand why your approach works.</p><h1>&#127891; What this problem trains you to do</h1><p>Two Sum trains a skill that matters far more than this one question: turning pairwise search into single-pass decision-making.</p><p>The reusable mental model is:</p><ul><li><p>I&#8217;m scanning items in order.</p></li><li><p>At each item, I can compute what I wish I had seen earlier.</p></li><li><p>If I record what I&#8217;ve seen in a structure optimized for membership tests, I can avoid nested loops.</p></li></ul><p>That model reappears in many forms:</p><ul><li><p>Variants like &#8220;<em>find a pair with difference k</em>&#8221; (same complement idea).</p></li><li><p>&#8220;<em>Three sum</em>&#8221; style problems (where you often fix one element and reduce to Two Sum).</p></li><li><p>Subarray sum problems (prefix sums + hash maps are the same &#8220;<em>remember what matters</em>&#8221; move).</p></li><li><p>Any streaming scenario where you can&#8217;t afford to revisit all prior data.</p></li></ul><p>But the deeper interview takeaway is communication discipline:</p><ul><li><p>Restate requirements precisely.</p></li><li><p>Name the naive solution and its cost.</p></li><li><p>Introduce the key insight as a trade: space for time.</p></li><li><p>Keep the implementation aligned with the correctness story (&#8220;<em>check then insert</em>&#8221;).</p></li></ul><p>If you can do that smoothly on Two Sum, you&#8217;re not just solving a problem - you&#8217;re demonstrating the exact style of thinking interviewers trust.</p><h1>&#127793; Closing thoughts</h1><p>Two Sum looks simple, which is why it&#8217;s such a good interview mirror. It reflects whether you can slow down just enough to be precise, then speed up with a method that stays correct when the input stops being friendly.</p><p>When you practice, don&#8217;t time yourself on typing. Time yourself on clarity: can you explain, in plain language, what you&#8217;re storing, why you&#8217;re storing it, and why that guarantees you won&#8217;t miss the answer? If you can, the code almost writes itself - and when it doesn&#8217;t, you&#8217;ll know exactly which invariant you&#8217;re trying to preserve.</p><div><hr></div><h3>&#129504; If you want to build real interview intuition&#8230;</h3><p>I write deep, calm breakdowns of classic problems - without tricks or memorization.</p><p>&#128236; Subscribe to get the next one directly.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[How systems find each other — Service Discovery without magic]]></title><description><![CDATA[Systems Literacy]]></description><link>https://iam.slys.dev/p/how-systems-find-each-other-service</link><guid isPermaLink="false">https://iam.slys.dev/p/how-systems-find-each-other-service</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Tue, 10 Feb 2026 21:27:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!2Yfy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2Yfy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2Yfy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!2Yfy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!2Yfy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!2Yfy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2Yfy!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2486259,&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://iam.slys.dev/i/176729744?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2Yfy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!2Yfy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!2Yfy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!2Yfy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72ebec7c-c077-4278-b53c-e56d13f34387_1536x1024.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><div class="pullquote"><p>Imagine throwing a party - fifty guests, all arriving at different times.<br>Some leave early, others show up late. The address changes mid-party.<br>Yet somehow, everyone still finds the right house.</p><p>That&#8217;s the miracle of <strong>Service Discovery</strong> - systems finding each other in a world that&#8217;s constantly moving.</p></div><p>In a distributed system, finding the &#8220;<em>right house</em>&#8221; is an everyday challenge. Services are like party guests constantly coming and going. Addresses change (containers restart, servers autoscale, IPs reassign) but somehow everything <em>still works</em>. How? The answer is <strong>service discovery</strong>. In essence, service discovery is what keeps order in chaos, ensuring that even when locations are fluid, the connections between services remain solid. In the following sections, we&#8217;ll demystify service discovery, showing that it&#8217;s not magic at all - just smart engineering that lets services continuously find each other in a changing world.</p><h1>&#129513; The problem &#8212; nothing stays still in Distributed Systems</h1><p>In a traditional monolithic application, every function call or component interaction happens <strong>inside one process</strong> or on one machine. Locating another part of the system is straightforward because everything lives together. There&#8217;s no question about &#8220;<em>where</em>&#8221; a component is - it&#8217;s in the same binary or server, so calling it is as simple as a function call.</p><div><hr></div><p><strong>Enjoying the metaphors so far? I write posts just like this - breaking down tech concepts with plain language and real-world comparisons.</strong></p><p>&#128161; Want more like this delivered to your inbox?</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>In <strong>microservices</strong> (and other distributed architectures), nothing stays still. Services are split out across many processes or containers, often across many hosts. These service instances <strong>come and go dynamically</strong>. Auto-scaling might spin up new instances on the fly; new deployments replace old versions; if something crashes, it might be restarted elsewhere. In cloud environments, even the network identifiers are ephemeral - containers or VMs often get random IP addresses assigned at start, which can change next time they run.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> The number of instances isn&#8217;t fixed either; it can change based on load or schedule.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TJS_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94437e09-91a6-4875-8923-aad9d19459e6_2263x1708.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TJS_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94437e09-91a6-4875-8923-aad9d19459e6_2263x1708.png 424w, https://substackcdn.com/image/fetch/$s_!TJS_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94437e09-91a6-4875-8923-aad9d19459e6_2263x1708.png 848w, https://substackcdn.com/image/fetch/$s_!TJS_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94437e09-91a6-4875-8923-aad9d19459e6_2263x1708.png 1272w, https://substackcdn.com/image/fetch/$s_!TJS_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94437e09-91a6-4875-8923-aad9d19459e6_2263x1708.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TJS_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94437e09-91a6-4875-8923-aad9d19459e6_2263x1708.png" width="1456" height="1099" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/94437e09-91a6-4875-8923-aad9d19459e6_2263x1708.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1099,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:195679,&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://iam.slys.dev/i/176729744?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94437e09-91a6-4875-8923-aad9d19459e6_2263x1708.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_!TJS_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94437e09-91a6-4875-8923-aad9d19459e6_2263x1708.png 424w, https://substackcdn.com/image/fetch/$s_!TJS_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94437e09-91a6-4875-8923-aad9d19459e6_2263x1708.png 848w, https://substackcdn.com/image/fetch/$s_!TJS_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94437e09-91a6-4875-8923-aad9d19459e6_2263x1708.png 1272w, https://substackcdn.com/image/fetch/$s_!TJS_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94437e09-91a6-4875-8923-aad9d19459e6_2263x1708.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>Now, if you&#8217;re a service that needs to call another service, you can&#8217;t rely on a hard-coded IP or hostname, because that might be outdated the moment it&#8217;s deployed. Hardcoding addresses in config is brittle and practically impossible to maintain in a microservices ecosystem.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> Imagine trying to keep track of 50 party guests whose addresses keep changing - that&#8217;s what it&#8217;s like in a dynamic system. The pain point is clear: in distributed systems, <em>location is fluid</em>. What was &#8220;<em>here</em>&#8221; yesterday might be gone or moved today.</p><div class="pullquote"><p><strong>Insight:</strong> Service Discovery exists because <strong>addresses are temporary - but relationships are permanent.</strong> The <em>identity</em> of the service (like &#8220;<em>User Service</em>&#8221; or &#8220;<em>Order Service</em>&#8221;) remains, even though its location keeps changing. We need a way to call &#8220;<em>User Service</em>&#8221; without caring about its current address. In other words, find the <em>who</em> no matter <em>where</em> it is now.</p></div><h1>&#9881;&#65039; What is Service Discovery (really)?</h1><p>Let&#8217;s define it in simple terms. <strong>Service Discovery</strong> is how systems <em>dynamically</em> find and connect to each other on a network. Instead of calling a service at a fixed, known address, a service will ask a central system: &#8220;<em>Where is the <strong>User Service</strong> today?</em>&#8221; - and get back the current location (IP and port, for example) of an instance of that service. This means the caller doesn&#8217;t need to know in advance where the target service lives; it only needs to know the target&#8217;s <em>name</em> or identity, and the discovery mechanism provides the real address at runtime.</p><p>At its core, service discovery is a level of indirection that separates <strong>who</strong> you want to talk to from <strong>where</strong> they are right now. You request by a logical service name or key, and the discovery system returns the actual endpoint. This is analogous to how we use a phone&#8217;s contact list: you don&#8217;t remember the exact phone number (which might change), you just tap on &#8220;<em>Mom</em>&#8221; or &#8220;<em>Alice&#8217;s Pizza</em>&#8221; and the phone figures out the current number to dial. The number could change behind the scenes (new SIM card, updated contact info), but you don&#8217;t care - you just want to reach <em>Mom</em>. Similarly, a service just wants to reach &#8220;<em>User-Service</em>&#8221; without hardcoding its address.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Gt0J!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29972993-9cd7-4a32-b262-ccccd0d38f34_2605x1822.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Gt0J!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29972993-9cd7-4a32-b262-ccccd0d38f34_2605x1822.png 424w, https://substackcdn.com/image/fetch/$s_!Gt0J!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29972993-9cd7-4a32-b262-ccccd0d38f34_2605x1822.png 848w, https://substackcdn.com/image/fetch/$s_!Gt0J!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29972993-9cd7-4a32-b262-ccccd0d38f34_2605x1822.png 1272w, https://substackcdn.com/image/fetch/$s_!Gt0J!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29972993-9cd7-4a32-b262-ccccd0d38f34_2605x1822.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Gt0J!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29972993-9cd7-4a32-b262-ccccd0d38f34_2605x1822.png" width="1200" height="839.010989010989" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/29972993-9cd7-4a32-b262-ccccd0d38f34_2605x1822.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:1018,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:409868,&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://iam.slys.dev/i/176729744?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29972993-9cd7-4a32-b262-ccccd0d38f34_2605x1822.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_!Gt0J!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29972993-9cd7-4a32-b262-ccccd0d38f34_2605x1822.png 424w, https://substackcdn.com/image/fetch/$s_!Gt0J!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29972993-9cd7-4a32-b262-ccccd0d38f34_2605x1822.png 848w, https://substackcdn.com/image/fetch/$s_!Gt0J!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29972993-9cd7-4a32-b262-ccccd0d38f34_2605x1822.png 1272w, https://substackcdn.com/image/fetch/$s_!Gt0J!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29972993-9cd7-4a32-b262-ccccd0d38f34_2605x1822.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>To illustrate, consider a real example from a microservice framework: in a Spring Cloud/Eureka setup, one service can simply use the <strong>logical name</strong> of another. For instance, an authentication service might call <code>&#8220;USER-SERVICE&#8221;</code> (the name) instead of a URL; Eureka will resolve that name to an actual host:port at call time.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> The developer writes code against a service <em>name</em>, and service discovery takes care of mapping it to the current address. This makes the system <strong>flexible</strong> - services can be redeployed, moved, scaled, or replaced, and as long as they register themselves under the same name, other services can still find them.</p><p>In short, <strong>service discovery</strong> is the automated &#8220;<em>phonebook</em>&#8221; of distributed systems. It answers the ever-important question: <em>&#8220;Where is service X right now?&#8221;</em> and it answers it reliably even as the answer keeps changing over time.</p><h1>&#129504; The two flavors &#8212; Client-Side vs. Server-Side Discovery</h1><p>There are two primary models of how service discovery is implemented: <strong>client-side discovery</strong> and <strong>server-side discovery</strong>. Both achieve the same goal (routing a request to an available service instance), but they split responsibilities differently between the service client and the network infrastructure.</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/AI11b/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/74d4ea74-4042-4cdc-a360-94959602609d_1220x708.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/22f7a4f4-d107-443f-a13d-994eca84c01f_1220x778.png&quot;,&quot;height&quot;:267,&quot;title&quot;:&quot;Client-Side vs. Server-Side Discovery&quot;,&quot;description&quot;:&quot;&quot;,&quot;belowTheFold&quot;:true}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/AI11b/1/" width="730" height="267" frameborder="0" scrolling="no" loading="lazy"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>In <strong>client-side discovery</strong>, the client is &#8220;<em>smart</em>&#8221;. It knows about the service registry. For example, a microservice might use a discovery library (such as Netflix Ribbon with Eureka or a Consul client) to ask the registry for all live instances of &#8220;<em>User Service</em>&#8221;, then apply some load balancing algorithm (random, round-robin, etc.) to choose one instance and send the request directly to it. The client contains the discovery logic. The upside is there&#8217;s no extra network hop &#8211; the client talks straight to the target. The downside is every client must implement the discovery mechanism and keep the registry up-to-date (which typically means pulling updates or subscribing to changes).</p><p>In <strong>server-side discovery</strong>, the client is simpler &#8211; it just knows to send requests to a fixed address (for example, a load balancer URL or a proxy) and that component will do the discovery. The logic lives on the server-side component (like a reverse proxy, API gateway, or dedicated load balancer). When a request comes in, the load balancer looks up where to send it - essentially performing the discovery lookup on behalf of the client - and then forwards (proxies) the request to one of the service instances. AWS Elastic Load Balancer (ELB) works this way: your service client just calls &#8220;<em>http://my-service.loadbalancer.aws</em>&#8221; and ELB ensures it routes to one of the actual servers behind it. Kubernetes <strong>Services</strong> (the built-in kind) also behave like a server-side discovery mechanism: each Service gets a stable IP or DNS name, and behind that facade Kubernetes will direct traffic to one of the pods that currently implement that service.</p><pre><code><code>Client-side:   Client &#8594; (Service Registry lookup) &#8594; Directly to Target Service  
Server-side:  Client &#8594; (Load Balancer/Proxy) &#8594; Target Service (after LB does lookup)</code></code></pre><p>The diagram above shows the difference in call flow. In summary:</p><ul><li><p><em>Client-side discovery</em> makes the <strong>client</strong> responsible for figuring out service locations. The client must query the registry and decide <em>which</em> instance to use. This requires a bit more sophistication in each client service, but avoids an extra network hop. Many open-source systems like Netflix Eureka or HashiCorp Consul operate in this mode, often with client libraries that applications include to integrate with the registry.</p></li><li><p><em>Server-side discovery</em> offloads that work to a dedicated component (often part of the infrastructure). The client just hits a fixed endpoint and <strong>the network &#8220;</strong><em><strong>smartly</strong></em><strong>&#8221; routes</strong> the call to a service instance. This is convenient for clients (they just call a static address), but introduces an extra component that must scale and stay available. Examples include cloud load balancers (AWS ELB, Nginx or HAProxy with service discovery plugins) and Kubernetes&#8217;s internal service VIPs or service mesh proxies.</p></li></ul><pre><code><code>Client-side:   Client &#8594; (Service Registry lookup) &#8594; Directly to Target Service  </code></code></pre><p><em>Client-side service discovery.</em> The diagram above illustrates client-side discovery: the client first queries a <strong>service registry</strong> to get the current list of server instances for a given service. Then the client picks one of those instances (often using a load-balancing strategy) and sends the request directly to that instance. In this model, the client is aware of the discovery system. Netflix&#8217;s <strong>Eureka</strong> is a classic example supporting client-side discovery - applications use a Eureka client to ask the registry for service addresses. Similarly, HashiCorp <strong>Consul</strong> can be used client-side via DNS or HTTP APIs: the application itself looks up the service&#8217;s address and connects to it.</p><pre><code><code>Server-side:  Client &#8594; (Load Balancer/Proxy) &#8594; Target Service (after LB does lookup)</code></code></pre><p><em>Server-side service discovery.</em> The diagram above shows server-side discovery: the client sends its request to a fixed <strong>load balancer (or proxy)</strong> address. The load balancer (acting as a smart router) then consults the service registry or its own mapping of healthy instances and forwards the request to one of the service instances. The client doesn&#8217;t need to know anything about the service registry or where instances are - it just knows the address of the load balancer. <strong>Kubernetes</strong> implements this pattern with its Service abstraction (and kube-proxy), giving each service a stable IP/hostname and distributing incoming requests to available pods. Traditional hardware or software load balancers (like F5, Nginx, HAProxy) can also do service discovery by periodically polling a registry or receiving updates, so they always know where to route traffic.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dplQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329cb989-7de6-4887-a3c5-ecbcc0867491_1590x1445.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dplQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329cb989-7de6-4887-a3c5-ecbcc0867491_1590x1445.png 424w, https://substackcdn.com/image/fetch/$s_!dplQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329cb989-7de6-4887-a3c5-ecbcc0867491_1590x1445.png 848w, https://substackcdn.com/image/fetch/$s_!dplQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329cb989-7de6-4887-a3c5-ecbcc0867491_1590x1445.png 1272w, https://substackcdn.com/image/fetch/$s_!dplQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329cb989-7de6-4887-a3c5-ecbcc0867491_1590x1445.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dplQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329cb989-7de6-4887-a3c5-ecbcc0867491_1590x1445.png" width="1456" height="1323" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/329cb989-7de6-4887-a3c5-ecbcc0867491_1590x1445.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1323,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:153867,&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://iam.slys.dev/i/176729744?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329cb989-7de6-4887-a3c5-ecbcc0867491_1590x1445.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_!dplQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329cb989-7de6-4887-a3c5-ecbcc0867491_1590x1445.png 424w, https://substackcdn.com/image/fetch/$s_!dplQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329cb989-7de6-4887-a3c5-ecbcc0867491_1590x1445.png 848w, https://substackcdn.com/image/fetch/$s_!dplQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329cb989-7de6-4887-a3c5-ecbcc0867491_1590x1445.png 1272w, https://substackcdn.com/image/fetch/$s_!dplQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329cb989-7de6-4887-a3c5-ecbcc0867491_1590x1445.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 key trade-off between the two flavors: <strong>In client-side discovery, the intelligence lives with the client</strong> (each service that calls others must handle discovery logic). <strong>In server-side discovery, the intelligence lives in the network</strong> (the platform or an intermediary handles discovery for you). Both models can achieve high availability and load balancing; which to use often depends on your ecosystem and tooling. In practice, systems like <strong>Kubernetes</strong> or <strong>service mesh</strong> proxies have made server-side discovery very popular (since the platform automates it), whereas libraries like Eureka or Consul&#8217;s API made client-side popular in early microservice architectures (like Netflix&#8217;s). Many environments actually use a mix &#8211; for example, a client might do a DNS lookup (kind of a discovery) which is served by a service mesh sidecar proxy (server-side pattern internally). </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5ybv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06eadc2-6345-494f-a92b-25583906f31c_1430x1490.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5ybv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06eadc2-6345-494f-a92b-25583906f31c_1430x1490.png 424w, https://substackcdn.com/image/fetch/$s_!5ybv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06eadc2-6345-494f-a92b-25583906f31c_1430x1490.png 848w, https://substackcdn.com/image/fetch/$s_!5ybv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06eadc2-6345-494f-a92b-25583906f31c_1430x1490.png 1272w, https://substackcdn.com/image/fetch/$s_!5ybv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06eadc2-6345-494f-a92b-25583906f31c_1430x1490.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5ybv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06eadc2-6345-494f-a92b-25583906f31c_1430x1490.png" width="1430" height="1490" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d06eadc2-6345-494f-a92b-25583906f31c_1430x1490.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1490,&quot;width&quot;:1430,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:185814,&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://iam.slys.dev/i/176729744?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06eadc2-6345-494f-a92b-25583906f31c_1430x1490.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_!5ybv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06eadc2-6345-494f-a92b-25583906f31c_1430x1490.png 424w, https://substackcdn.com/image/fetch/$s_!5ybv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06eadc2-6345-494f-a92b-25583906f31c_1430x1490.png 848w, https://substackcdn.com/image/fetch/$s_!5ybv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06eadc2-6345-494f-a92b-25583906f31c_1430x1490.png 1272w, https://substackcdn.com/image/fetch/$s_!5ybv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06eadc2-6345-494f-a92b-25583906f31c_1430x1490.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><strong>The end goal is the same:</strong> the caller gets connected to a living, healthy instance of the target service.</p><h1>&#129518; The heart of discovery &#8212; the service registry</h1><p>Whether client-side or server-side, almost every service discovery system revolves around a <strong>service registry</strong>. This is the heart of discovery: a database (or directory) that holds the <strong>locations of services</strong> and keeps track of which instances are available. Think of the registry as a <strong>dynamic phone book</strong> for all the services in your system - one that is constantly updating itself.</p><p>So how does it work behind the scenes? When each service instance starts up, it <strong>registers itself</strong> with the registry. It provides details like its identity (service name), address (IP and port), and perhaps other metadata (like what version it is, or a health check endpoint). For example, if we have a <strong>User Service</strong>, when a new instance of User Service launches, it will announce &#8220;<em>I am User-Service instance, and I&#8217;m listening on 10.1.2.3:8080</em>&#8221; to the registry. The registry records this entry in its database.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!deaX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feebe1ee3-7b4c-4b58-ae1b-15b2620f1af4_1679x1713.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!deaX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feebe1ee3-7b4c-4b58-ae1b-15b2620f1af4_1679x1713.png 424w, https://substackcdn.com/image/fetch/$s_!deaX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feebe1ee3-7b4c-4b58-ae1b-15b2620f1af4_1679x1713.png 848w, https://substackcdn.com/image/fetch/$s_!deaX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feebe1ee3-7b4c-4b58-ae1b-15b2620f1af4_1679x1713.png 1272w, https://substackcdn.com/image/fetch/$s_!deaX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feebe1ee3-7b4c-4b58-ae1b-15b2620f1af4_1679x1713.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!deaX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feebe1ee3-7b4c-4b58-ae1b-15b2620f1af4_1679x1713.png" width="1456" height="1485" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eebe1ee3-7b4c-4b58-ae1b-15b2620f1af4_1679x1713.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1485,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:168786,&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://iam.slys.dev/i/176729744?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feebe1ee3-7b4c-4b58-ae1b-15b2620f1af4_1679x1713.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_!deaX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feebe1ee3-7b4c-4b58-ae1b-15b2620f1af4_1679x1713.png 424w, https://substackcdn.com/image/fetch/$s_!deaX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feebe1ee3-7b4c-4b58-ae1b-15b2620f1af4_1679x1713.png 848w, https://substackcdn.com/image/fetch/$s_!deaX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feebe1ee3-7b4c-4b58-ae1b-15b2620f1af4_1679x1713.png 1272w, https://substackcdn.com/image/fetch/$s_!deaX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feebe1ee3-7b4c-4b58-ae1b-15b2620f1af4_1679x1713.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 registry isn&#8217;t just a static database; it&#8217;s actively maintained. Service instances typically <em>renew</em> their registration periodically (like a heartbeat) or the registry calls them to check if they&#8217;re still alive (more on health checks in the next section). When a service shuts down normally, it <strong>deregisters</strong> itself - essentially saying &#8220;<em>take me off the list</em>&#8221;. If a service crashes or is unreachable, the registry will notice via missing heartbeats or failed health checks and remove that entry after a grace period. This way, the registry always aims to have a <em>fresh list</em> of who&#8217;s out there and healthy.</p><p>Now, when another service (a client) needs to find, say, <em>User Service</em>, it queries the registry: &#8220;<em>Give me the instances for </em><code>User-Service</code>&#8221;. The registry replies with the current list of active instances and their addresses. The client can then use one of those addresses to make the call. If no instances are available, the registry could return an empty result or an error, and the client knows the service is (temporarily) unavailable.</p><p>Some popular <strong>service registry</strong> implementations and tools include:</p><p><strong>HashiCorp Consul</strong> &#8211; a distributed key-value store and registry that supports health checks and offers both REST and DNS interfaces for discovery.</p><p><strong>Netflix Eureka</strong> &#8211; part of Netflix&#8217;s open-source suite, a registry service where clients (usually with a Java client library) register and discover services. Eureka clients also use a heartbeat mechanism to stay registered.</p><p><strong>etcd</strong> &#8211; a distributed key-value store (by CoreOS) that underpins Kubernetes. Kubernetes uses etcd to store all cluster data, including service information for discovery. In Kubernetes, you typically don&#8217;t interact with etcd directly, but it&#8217;s the source of truth for service endpoints.</p><p><strong>Apache Zookeeper</strong> &#8211; an older but rock-solid distributed coordination system, often used in the past for service discovery and configuration in systems like Hadoop or older microservice frameworks. It maintains a hierarchical data store of service names and addresses and can notify watchers of changes.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jQ80!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71d73a9-1711-4411-843b-735679719801_2692x1392.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jQ80!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71d73a9-1711-4411-843b-735679719801_2692x1392.png 424w, https://substackcdn.com/image/fetch/$s_!jQ80!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71d73a9-1711-4411-843b-735679719801_2692x1392.png 848w, https://substackcdn.com/image/fetch/$s_!jQ80!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71d73a9-1711-4411-843b-735679719801_2692x1392.png 1272w, https://substackcdn.com/image/fetch/$s_!jQ80!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71d73a9-1711-4411-843b-735679719801_2692x1392.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jQ80!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71d73a9-1711-4411-843b-735679719801_2692x1392.png" width="1200" height="620.6043956043956" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c71d73a9-1711-4411-843b-735679719801_2692x1392.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:753,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:204224,&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://iam.slys.dev/i/176729744?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71d73a9-1711-4411-843b-735679719801_2692x1392.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_!jQ80!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71d73a9-1711-4411-843b-735679719801_2692x1392.png 424w, https://substackcdn.com/image/fetch/$s_!jQ80!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71d73a9-1711-4411-843b-735679719801_2692x1392.png 848w, https://substackcdn.com/image/fetch/$s_!jQ80!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71d73a9-1711-4411-843b-735679719801_2692x1392.png 1272w, https://substackcdn.com/image/fetch/$s_!jQ80!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71d73a9-1711-4411-843b-735679719801_2692x1392.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>Other technologies and platforms have their own registries: for instance, <strong>Kubernetes</strong> doesn&#8217;t expose etcd directly to you for service discovery, but it effectively has an internal registry of services and endpoints (the Kubernetes control plane ensures that). AWS&#8217;s Elastic Load Balancing and ECS have a form of implicit registry (you register instances in a target group). Similarly, systems like <strong>Apache Mesos/Marathon</strong> had service registries. The pattern is universal &#8211; there&#8217;s always a repository of service locations somewhere.</p><div><hr></div><p><strong>&#128736;&#65039; Which tools are </strong><em><strong>you</strong></em><strong> using?</strong></p><p>Have you tried Consul, Zookeeper, or maybe you&#8217;re wrestling with AWS&#8217;s ELB quirks? I&#8217;d love to hear your thoughts, questions, or stories from the trenches.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/how-systems-find-each-other-service/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/how-systems-find-each-other-service/comments"><span>Leave a comment</span></a></p><div><hr></div><p>Think of the registry as a <strong>phone book that updates itself every second.</strong> It&#8217;s the authoritative source that knows &#8220;<em>what lives where</em>&#8221; at any given moment. Without it, every service would be shouting into the void, or we&#8217;d be back to the bad old days of maintaining long lists of IP addresses in config files (which would never stay accurate). The service registry gives us a single source of truth for service locations, and service discovery is basically every participant agreeing to use that source of truth to find each other.</p><h1>&#128269; Health Checks &#8212; knowing who&#8217;s alive</h1><p>Finding a service is one thing &#8211; but finding a <strong>working</strong> service is another. If our registry simply kept every service that ever registered listed forever, it would quickly accumulate stale entries (dead instances) and mislead clients. That&#8217;s why <strong>health checks</strong> are a crucial part of service discovery.</p><p>A health check is a simple probe (often an HTTP GET to a <code>/health</code> endpoint, a ping, or a heartbeat signal) used to verify that a service instance is <strong>alive and functioning</strong>. Service registries use health checks to keep their data accurate:</p><ul><li><p><strong>Active heartbeats:</strong> Some systems (like Eureka) have the service instances periodically send heartbeats to the registry. If heartbeats stop, the registry marks the instance as dead after a timeout.</p></li><li><p><strong>Pull health checks:</strong> Other systems (like Consul, Zookeeper) have the registry actively ping or check each service at intervals. For example, Consul can be configured with a health check command or HTTP check for each service; if the check fails, Consul will flag that instance as unhealthy.</p></li><li><p><strong>Integration with orchestration:</strong> In platforms like Kubernetes, health status is often integrated &#8211; if a pod fails its liveness or readiness probes, Kubernetes can remove it from service endpoints. That information propagates to discovery (so DNS won&#8217;t include a failing pod, etc.).</p></li></ul><p>The result is that the registry (or discovery system) maintains a list of <strong>healthy</strong> instances. When a client asks for a service, it ideally only gets back instances that are up and passing health checks. If an instance goes down unexpectedly, the health mechanism ensures it will be removed from the registry, often within seconds. Conversely, when an instance comes back or a new one is added, it will start passing health checks and get included.</p><p>Without health checks, service discovery would be giving out potentially bad phone numbers - you&#8217;d call, and nobody picks up. It would be like having a phone book full of businesses that have shut down; you keep dialing dead lines. Thus, any robust discovery system pairs <strong>registration</strong> with <strong>monitoring</strong> of health. Some registries allow a grace period or a TTL (time-to-live) for registrations - a service must renew before the TTL expires or it&#8217;s assumed dead. Others use push-based failure detection. The end goal is the same: <strong>only live services should be discoverable</strong>.</p><p>One more aspect is <strong>health metadata</strong> - sometimes an instance is <em>alive</em> but not <em>healthy</em> to serve (e.g., it&#8217;s overloaded or in maintenance mode). Advanced discovery setups propagate health states (like &#8220;<em>passing</em>&#8221;, &#8220;<em>warning</em>&#8221;, &#8220;<em>failing</em>&#8221;) so that clients can decide to avoid instances that are in warning state, etc. But for most cases, it&#8217;s a binary: in or out.</p><p>So, service discovery is <strong>continuous</strong>. It&#8217;s not a one-time mapping of names to addresses &#8211; it&#8217;s an ever-evolving directory. Registries continuously reconcile the real world (which instances are up) with their internal records. This dynamic nature is what makes service discovery powerful: it turns an unpredictable, changing network into something applications can trust, by <em>constantly expiring and refreshing information</em>. As one source neatly put it: discovery without health checks would leave you &#8220;<em>with a phone book full of disconnected numbers</em>&#8221;, which isn&#8217;t very useful.</p><h1>&#127760; Discovery in the cloud-native world</h1><p>Modern cloud-native platforms have made service discovery <em>almost invisible</em> to developers. If you use Kubernetes or a similar orchestration system, you might be using service discovery features without even realizing it - it just feels like the platform magically makes services reachable by name. But under the hood, it&#8217;s the same principles we&#8217;ve discussed.</p><p>Take <strong>Kubernetes</strong> for example. In Kubernetes, when you deploy a set of pods running &#8220;<em>User Service</em>&#8221;, you typically define a <strong>Service</strong> object (let&#8217;s say named &#8220;<em>user-service</em>&#8221;). Kubernetes will allocate a stable virtual IP address for that Service (within the cluster) and set up a DNS name like <code>user-service.default.svc.cluster.local</code> (assuming it&#8217;s in the &#8220;<em>default</em>&#8221; namespace). Now any other service in the cluster can simply refer to</p><pre><code>http://user-service </code></pre><p>(Kubernetes&#8217; DNS can shorten the full domain) and get routed to one of the pods. It feels like magic &#8211; you didn&#8217;t personally implement a registry or a lookup &#8211; but Kubernetes did it for you.</p><p>What&#8217;s happening behind the scenes? Kubernetes uses a few cooperating components for discovery:</p><ul><li><p><strong>Etcd as registry:</strong> Kubernetes&#8217; control plane (specifically the API server) uses <strong>etcd</strong> as its backing store for all cluster data. This includes the endpoints for each service. Whenever pods come and go, the Kubernetes controllers update the list of endpoints for a Service (which is stored in etcd).<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a> In essence, etcd is the service registry, keeping track of which pod IPs are tied to &#8220;user-service&#8221; at any time.</p></li><li><p><strong>DNS and environment variables:</strong> Kubernetes runs an internal DNS service (like CoreDNS) that watches for service changes. It automatically creates DNS A records so that <code>user-service.default.svc.cluster.local</code> maps to the Service&#8217;s cluster IP. It also can inject environment variables into pods (like <code>USER_SERVICE_SERVICE_HOST=10.3.245.7</code>) giving another way to find the service IP. This means as a developer you can just use the service name and trust that DNS will resolve it to the right IP.</p></li><li><p><strong>Kube-proxy or networking for routing:</strong> The cluster IP itself (and corresponding DNS name) is virtual &#8211; it&#8217;s handled by <code>kube-proxy</code> on each node (or by advanced routing in newer implementations like kube-proxy IPVS or Cilium). Kube-proxy maintains iptables or IPVS rules that map the Service IP to the set of current Pod IPs for that service. When you hit the Service IP, the request gets load balanced to one of the pod endpoints that&#8217;s actually alive. If pods are added or removed, kube-proxy updates the rules. This is server-side discovery internalized: your request goes to a stable IP, and the cluster networking finds an appropriate instance.</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_!5NfC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac74b8b6-fa80-4335-ac56-0f636d8de245_2494x1723.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5NfC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac74b8b6-fa80-4335-ac56-0f636d8de245_2494x1723.png 424w, https://substackcdn.com/image/fetch/$s_!5NfC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac74b8b6-fa80-4335-ac56-0f636d8de245_2494x1723.png 848w, https://substackcdn.com/image/fetch/$s_!5NfC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac74b8b6-fa80-4335-ac56-0f636d8de245_2494x1723.png 1272w, https://substackcdn.com/image/fetch/$s_!5NfC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac74b8b6-fa80-4335-ac56-0f636d8de245_2494x1723.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5NfC!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac74b8b6-fa80-4335-ac56-0f636d8de245_2494x1723.png" width="1200" height="829.1208791208791" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ac74b8b6-fa80-4335-ac56-0f636d8de245_2494x1723.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:1006,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:301004,&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://iam.slys.dev/i/176729744?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac74b8b6-fa80-4335-ac56-0f636d8de245_2494x1723.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_!5NfC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac74b8b6-fa80-4335-ac56-0f636d8de245_2494x1723.png 424w, https://substackcdn.com/image/fetch/$s_!5NfC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac74b8b6-fa80-4335-ac56-0f636d8de245_2494x1723.png 848w, https://substackcdn.com/image/fetch/$s_!5NfC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac74b8b6-fa80-4335-ac56-0f636d8de245_2494x1723.png 1272w, https://substackcdn.com/image/fetch/$s_!5NfC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac74b8b6-fa80-4335-ac56-0f636d8de245_2494x1723.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 upshot is that <strong>the platform handles registration and discovery for you</strong>. When a new pod comes up, Kubernetes will automatically add it to the endpoints of the Service (registering it). If a pod dies, it&#8217;s removed. Health checks (readiness probes) integrate so that if a pod is not ready, it&#8217;s temporarily removed from endpoints. All of this happens without the service authors writing any discovery code. You just name the service you want to call, and Kubernetes ensures that name resolves to something that works. It&#8217;s the same idea of service discovery, but baked into the infrastructure as an <strong>automated, built-in feature</strong>.</p><p>Service meshes (like <strong>Istio</strong>, <strong>Linkerd</strong>, etc.) go even further. They often leverage the existing discovery (from Kubernetes or another system) but add a layer of proxies that can do more intelligent routing (like per-request load balancing, traffic shifting, etc.). Even in those cases, the fundamental discovery (knowing what instances exist for a service) still often comes from the service registry (e.g., Istio taps into Kubernetes&#8217; service registry and adds its own logic on top).</p><p>To give a concrete example: imagine our <em>User Service</em> in Kubernetes. If it&#8217;s scaled to 5 pods, the Kubernetes registry (etcd) knows there are 5 endpoints. The DNS name <code>user-service.default.svc.cluster.local</code> will resolve (round-robin or via kube-proxy VIP) to one of those pod IPs. From our perspective, we just do <code>GET http://user-service:8080/api/profile</code> and we reach one of the five pods. If we deploy a new version of User Service (blue-green deploy) under the hood Kubernetes might switch the endpoints to 5 new pods and remove the old 5 &#8211; but as a caller, you still hit <code>user-service</code> and magically get the new pods. No code change, no manual update &#8211; discovery handled it. It&#8217;s &#8220;<em>magic</em>&#8221; in the sense that you don&#8217;t have to think about it, but underneath it&#8217;s the same machinery: a constantly updated mapping of service name to instance addresses.</p><div class="pullquote"><p>&#10145;&#65039; <strong>Mini insight:</strong> In modern cloud-native systems, what feels like &#8220;magic&#8221; is really just automation doing the tedious work for you. Service discovery in Kubernetes (and similar platforms) is essentially automated service registry + automated client or server-side discovery. You <em>could</em> do it yourself, but you don&#8217;t have to, because the platform engineers have done it once in a generic way for everyone. The principles remain the same: separate naming from addressing, keep track of health, update everyone&#8217;s &#8220;phone book&#8221; continuously.</p></div><h1>&#129513; When things go wrong</h1><p>Service discovery makes distributed systems feasible, but it doesn&#8217;t make them <strong>infallible</strong>. Like any distributed system component, a service discovery system can experience issues. It&#8217;s important to be aware of common failure modes and challenges:</p><ul><li><p><strong>Stale Entries:</strong> There&#8217;s a period of time between a service going down and the registry noticing. If that interval is too long (or if heartbeats/health checks fail to promptly remove a dead instance), clients might still be sent to an address that&#8217;s no longer valid. This results in errors when calling a service that is actually down. For example, if a registry has a long TTL or a health check that runs once a minute, there could be up to a minute where a dead service is still &#8220;discoverable&#8221;. The solution is to use aggressive health checks and shorter TTLs so that information is refreshed quickly. Still, a balance is needed: make it too short and you might flap or remove instances that have brief hiccups. Tuning is key.</p></li><li><p><strong>Single Point of Failure / Overloaded Registry:</strong> The service registry itself is a critical component. If the registry goes down, the whole discovery mechanism grinds to a halt (at that point, services can&#8217;t find each other, except through perhaps cached data which will become outdated). Also, if the registry is under heavy load (imagine hundreds of services registering and hundreds of clients querying every second), it can become a bottleneck. That&#8217;s why registries must be designed to be <strong>highly available and scalable</strong>. Typically, they are run as clusters themselves (e.g., multiple Consul servers, Eureka in cluster mode) to avoid a single point of failure. Caching on the client side can mitigate read load &#8211; many discovery libraries cache the last known good addresses for a service and fall back to them if the registry is momentarily unreachable. But ultimately, you need to treat the registry like an absolutely critical infra component (because it is). If it fails or lags, it&#8217;s like the phone book is missing pages or not updating &#8211; chaos can ensue.</p></li><li><p><strong>Network Partitions:</strong> In a distributed system, sometimes parts of the network can become isolated (the dreaded &#8220;<em>split brain</em>&#8221; scenario). In such a case, you might have <strong>partitioned views of service discovery</strong>. For example, half of the instances and half of the clients can&#8217;t reach the other half. Each side might have a registry that thinks the other side&#8217;s services are down. This can lead to situations where services that are actually healthy (on the other side of the partition) are seen as unavailable. Or worse, when the partition heals, two registries might have conflicting data. Robust systems handle this with techniques like <strong>zone-aware routing</strong> (so clients prefer local instances), or by ensuring the registry has a consistent view when partitioned (which often devolves to choosing one side as the source of truth). Network partitions are tricky &#8211; essentially, if your discovery mechanism itself is distributed, it has to deal with consensus and partition tolerance (per the CAP theorem). Zookeeper, for instance, will refuse to operate if it loses quorum (to avoid serving stale data). Eureka by default is more AP (available), which means it might serve stale data during a partition rather than be unavailable.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a> There&#8217;s no silver bullet; it&#8217;s the classic trade-off.</p></li><li><p><strong>Consistency and Convergence Delays:</strong> In larger systems, there may be a slight delay from the moment a service registers/deregisters to the moment all clients become aware. If your registry uses a push model (e.g., it notifies clients or a sidecar of changes), there&#8217;s propagation time. If clients poll periodically, there&#8217;s a window of staleness. Usually these delays are seconds or less, but in extreme cases they can cause brief inconsistencies (one client thinks there are 5 instances, another thinks 4). Most of the time this isn&#8217;t a huge problem (a request might fail and then retry and succeed), but it&#8217;s good to design with the expectation that discovery info is <strong>eventually consistent</strong> rather than instantly consistent.</p></li><li><p><strong>Dependency Loops:</strong> This is more of an architectural pitfall &#8211; if Service A and Service B depend on each other to start up via discovery, you can get a deadlock. For example, Service A won&#8217;t register as ready until it talks to B, and B won&#8217;t start until A is available. If both are waiting on discovery, they&#8217;ll wait forever. This is not the registry&#8217;s fault per se, but a usage pitfall. The way to avoid it is to design services such that there aren&#8217;t circular startup dependencies (or break the loop with some timeout or default behavior). It&#8217;s like two friends each waiting for the other to call first &#8211; sometimes someone has to just proceed.</p></li><li><p><strong>Security and Trust:</strong> If not properly secured, a malicious actor could potentially register fake services or deregister others. Real-world service registries often have authentication and ACLs, and in zero-trust environments, discovery info might be distributed via secure channels. This isn&#8217;t a &#8220;<em>when things go wrong</em>&#8221; in the failure sense, but it&#8217;s a risk if not addressed. After all, if the phone book can be tampered with, you might call the wrong number (and that number might be an attacker).</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_!Ntaf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4d08152-d8d4-4f53-bfa6-d4c4036b6efd_2893x2065.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ntaf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4d08152-d8d4-4f53-bfa6-d4c4036b6efd_2893x2065.png 424w, https://substackcdn.com/image/fetch/$s_!Ntaf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4d08152-d8d4-4f53-bfa6-d4c4036b6efd_2893x2065.png 848w, https://substackcdn.com/image/fetch/$s_!Ntaf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4d08152-d8d4-4f53-bfa6-d4c4036b6efd_2893x2065.png 1272w, https://substackcdn.com/image/fetch/$s_!Ntaf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4d08152-d8d4-4f53-bfa6-d4c4036b6efd_2893x2065.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ntaf!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4d08152-d8d4-4f53-bfa6-d4c4036b6efd_2893x2065.png" width="1200" height="856.3186813186813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4d08152-d8d4-4f53-bfa6-d4c4036b6efd_2893x2065.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:1039,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:410620,&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://iam.slys.dev/i/176729744?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4d08152-d8d4-4f53-bfa6-d4c4036b6efd_2893x2065.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_!Ntaf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4d08152-d8d4-4f53-bfa6-d4c4036b6efd_2893x2065.png 424w, https://substackcdn.com/image/fetch/$s_!Ntaf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4d08152-d8d4-4f53-bfa6-d4c4036b6efd_2893x2065.png 848w, https://substackcdn.com/image/fetch/$s_!Ntaf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4d08152-d8d4-4f53-bfa6-d4c4036b6efd_2893x2065.png 1272w, https://substackcdn.com/image/fetch/$s_!Ntaf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4d08152-d8d4-4f53-bfa6-d4c4036b6efd_2893x2065.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>To sum up, service discovery systems are themselves distributed systems. They have to be designed with failure in mind. As one metaphor would put it: even the best p</p><p>hone book is useless if the printing press fails halfway (or if the post office loses half the pages). In practice, solutions include running multiple registry servers (with consensus or leader election), client-side caching and fallbacks, health check tuning, and partition-aware setups (like multi-region registries that sync gradually). The good news is that mature solutions like Consul, Eureka, etcd, and Zookeeper have thought through many of these edge cases &#8211; but as an engineer, it&#8217;s still wise to be aware of them when designing your system&#8217;s architecture.</p><h1>&#128172; Service Discovery as a philosophy</h1><p>At this point, we&#8217;ve covered the mechanics of service discovery. Let&#8217;s step back and view it from a higher altitude &#8211; almost philosophically.</p><p>In a way, <strong>Service Discovery isn&#8217;t magic - it&#8217;s trust</strong>. It&#8217;s a silent agreement among all the services in a system, a sort of social contract of software: <em>&#8220;I&#8217;ll tell you where I am, if you promise to remember and find me when I move.&#8221;</em> Every service that comes online says &#8220;<em>Here I am, here&#8217;s how to reach me.</em>&#8221; The discovery system (and by extension all the other services) replies, &#8220;<em>Got it. We&#8217;ll keep track. If you go away or relocate, just let us know (or we&#8217;ll find out), and we&#8217;ll adapt.</em>&#8221;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PnVC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292c3d16-e42e-4a63-8406-6e36e01749ac_2022x1960.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PnVC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292c3d16-e42e-4a63-8406-6e36e01749ac_2022x1960.png 424w, https://substackcdn.com/image/fetch/$s_!PnVC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292c3d16-e42e-4a63-8406-6e36e01749ac_2022x1960.png 848w, https://substackcdn.com/image/fetch/$s_!PnVC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292c3d16-e42e-4a63-8406-6e36e01749ac_2022x1960.png 1272w, https://substackcdn.com/image/fetch/$s_!PnVC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292c3d16-e42e-4a63-8406-6e36e01749ac_2022x1960.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PnVC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292c3d16-e42e-4a63-8406-6e36e01749ac_2022x1960.png" width="1456" height="1411" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/292c3d16-e42e-4a63-8406-6e36e01749ac_2022x1960.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1411,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:235325,&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://iam.slys.dev/i/176729744?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292c3d16-e42e-4a63-8406-6e36e01749ac_2022x1960.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_!PnVC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292c3d16-e42e-4a63-8406-6e36e01749ac_2022x1960.png 424w, https://substackcdn.com/image/fetch/$s_!PnVC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292c3d16-e42e-4a63-8406-6e36e01749ac_2022x1960.png 848w, https://substackcdn.com/image/fetch/$s_!PnVC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292c3d16-e42e-4a63-8406-6e36e01749ac_2022x1960.png 1272w, https://substackcdn.com/image/fetch/$s_!PnVC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292c3d16-e42e-4a63-8406-6e36e01749ac_2022x1960.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>This mutual trust &#8211; that services will announce themselves and the system will faithfully keep that information up-to-date &#8211; is what allows <strong>order to emerge from chaos</strong>. In a vast sea of ephemeral instances popping in and out of existence, service discovery is the handshake that keeps everything coordinated. It turns what would be a cacophony of &#8220;<em>hello, where are you?</em>&#8221; into a well-orchestrated conversation.</p><p>In practical terms, service discovery embodies the principle of <strong>loose coupling</strong>. Services don&#8217;t need to know the internal details or exact location of others, only a stable identity. It&#8217;s like focusing on <em>who</em> you need, not <em>where</em> they are. This decoupling of identity from location is deeply powerful &#8211; it underpins the scalability and resilience of microservices. You can deploy a hundred instances or scale down to one, move them across data centers, upgrade them, or take them down for maintenance, all without breaking the contract. As long as you uphold the discovery protocol (register/unregister or pass health checks), the rest of the system will gracefully adapt.</p><p>So, beyond the technicalities, think of service discovery as the <strong>invisible glue</strong> or the <strong>neural network</strong> of your architecture&#8217;s brain. It&#8217;s constantly sensing and communicating changes in the system&#8217;s topology so that every part knows how to reach every other part. It is, fundamentally, about <strong>ensuring connectivity</strong> in a fluid world. It&#8217;s not magic at all &#8211; it&#8217;s solid engineering &#8211; but from the outside it can sure feel magical that no matter how much things move around, nothing gets lost.</p><blockquote><p>In summary: <em>Service discovery is the unsung hero that turns chaos into coordination &#8212; the invisible handshake that keeps distributed systems together.</em></p></blockquote><div><hr></div><p><strong>Know someone who&#8217;s just getting into backend development or cloud architecture</strong></p><p>If this post helped you, pass it along - help them level up too. &#128071;</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/leaderboard?&amp;utm_source=post&quot;,&quot;text&quot;:&quot;Refer a friend&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/leaderboard?&amp;utm_source=post"><span>Refer a friend</span></a></p><div><hr></div><h1>&#9997;&#65039; Summary</h1><p><strong>In distributed systems, nothing stays put. </strong>Unlike a monolith (where everything&#8217;s in one place), microservice instances are constantly changing &#8211; scaling up, shutting down, moving across hosts. Hard-coding their locations isn&#8217;t feasible.</p><p><strong>Service Discovery is the solution.</strong> It allows services to find each other <strong>dynamically</strong>, asking &#8220;<em>Where is X now?</em>&#8221; and getting a current answer. This separates the <em>service identity</em> (fixed) from the <em>service location</em> (fluid).</p><p><strong>Two discovery models &#8211; client vs server side.</strong> In <em>client-side discovery</em>, the calling service does the work of looking up the target&#8217;s location (e.g., using a library to query a registry). In <em>server-side discovery</em>, the caller just goes to a fixed entry point (like a load balancer) and that component looks up and forwards to the target. Both achieve the same end result, and many systems use a mix of both patterns.</p><p><strong>Service Registry as the source of truth.</strong> A registry is like a constantly updated phone book of all service instances. Services register themselves on startup and deregister on shutdown (or are removed if they crash). Examples include Consul, Eureka, etcd, Zookeeper, or even Kubernetes&#8217;s built-in etcd-based registry. The registry stores where each service lives at the moment.</p><p><strong>Health checks keep it accurate.</strong> The discovery system performs regular health checks or heartbeats to ensure that only healthy, alive instances are considered. Unhealthy ones are dropped until they recover. This way, &#8220;<em>discovering a service</em>&#8221; usually means discovering an <strong>available</strong> service.</p><p><strong>Cloud-native platforms automate discovery.</strong> Kubernetes, for instance, gives every service a stable DNS name and does the discovery and load balancing under the hood. Developers don&#8217;t have to manually query registries &#8211; the platform&#8217;s networking does it. It feels like magic, but it&#8217;s really just the same service discovery principles applied automatically.</p><p><strong>Things can still go wrong!</strong> Be mindful of issues like stale registry data, registry downtime, network partitions, etc., when designing your system. Use robust, highly available registry setups and perhaps client-side caching or fallbacks. And avoid circular dependencies where services depend on each other&#8217;s presence in awkward ways.</p><p>Service discovery turns &#8220;<em>finding</em>&#8221; into a first-class capability of your system. It answers the question &#8220;<em>Who serves this request?</em>&#8221; in real-time, every time. By doing so, it enables <em>trust</em> among moving parts &#8211; every service can move about freely, confident that others will still be able to find it. In the end, service discovery is what allows distributed systems to be dynamic, scalable, and fault-tolerant <strong>without losing connectivity</strong>. It&#8217;s not magic, but it sure makes the impossible possible.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p><a href="https://microservices.io/patterns/service-registry.html">Pattern: Service registry</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p><a href="https://devopscube.com/service-discovery-explained/">What is Service Discovery? [Explained With Examples]</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p><a href="https://medium.com/@tharinduimalka915/service-discovery-in-microservices-architecture-ea41e695b3df">Service Discovery in Microservices Architecture</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p><a href="https://kubex.ai/kubernetes-autoscaling/kubernetes-service-discovery/">Kubernetes Service Discovery</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p><a href="https://medium.com/knerd/eureka-why-you-shouldnt-use-zookeeper-for-service-discovery-4932c5c7e764">Eureka! Why You Shouldn&#8217;t Use ZooKeeper for Service Discovery</a></p></div></div>]]></content:encoded></item><item><title><![CDATA[How to learn System Design without getting overwhelmed]]></title><description><![CDATA[A beginner-friendly roadmap of blogs, books, and experts to help you go from confused to confident]]></description><link>https://iam.slys.dev/p/how-to-learn-system-design-without</link><guid isPermaLink="false">https://iam.slys.dev/p/how-to-learn-system-design-without</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 05 Jan 2026 21:11:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MtK7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MtK7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MtK7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!MtK7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!MtK7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!MtK7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MtK7!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3069146,&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://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MtK7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!MtK7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!MtK7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!MtK7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a67bd4-f225-470e-9ee8-fbb5acbeb221_1536x1024.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>Imagine trying to assemble a giant jigsaw puzzle without the picture on the box. You have hundreds of pieces, but no clue what the final image looks like. &#128517; When I first started coding, I <em>definitely</em> felt that way. The tech world was a pile of scattered pieces - programming languages, tools, theories - and I wasn&#8217;t sure how to fit them together. The good news is, you don&#8217;t have to solve the puzzle alone. Plenty of developers have left us a &#8220;<em>picture on the box</em>&#8221; in the form of blogs, open-source code, and classic books. These resources act as guideposts, lighting the path so you can focus on learning and building. In this post, I&#8217;ll share some of the most helpful resources (and how to use them) that will make your programming journey more accessible and <strong>fun</strong>.</p><h1>Company Tech Blogs &#128640;</h1><p>One great way to learn how real-world systems work is by following the engineering blogs of major tech companies. These blogs are like getting a backstage tour of how the pros build software at scale. Companies often publish articles about their architecture decisions, performance debugging adventures, and new technologies they&#8217;re exploring, giving you insight into industry best practices.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="http://netflixtechblog.com" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6oVR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32e1e863-92f2-4e39-a517-8d9180534fa8_940x529.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6oVR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32e1e863-92f2-4e39-a517-8d9180534fa8_940x529.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6oVR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32e1e863-92f2-4e39-a517-8d9180534fa8_940x529.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6oVR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32e1e863-92f2-4e39-a517-8d9180534fa8_940x529.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6oVR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32e1e863-92f2-4e39-a517-8d9180534fa8_940x529.jpeg" width="940" height="529" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/32e1e863-92f2-4e39-a517-8d9180534fa8_940x529.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:529,&quot;width&quot;:940,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:21281,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;http://netflixtechblog.com&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32e1e863-92f2-4e39-a517-8d9180534fa8_940x529.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_!6oVR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32e1e863-92f2-4e39-a517-8d9180534fa8_940x529.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6oVR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32e1e863-92f2-4e39-a517-8d9180534fa8_940x529.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6oVR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32e1e863-92f2-4e39-a517-8d9180534fa8_940x529.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6oVR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32e1e863-92f2-4e39-a517-8d9180534fa8_940x529.jpeg 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><h2><strong>Netflix TechBlog</strong></h2><blockquote><p><a href="http://netflixtechblog.com">netflixtechblog.com</a></p></blockquote><p>Netflix&#8217;s engineering blog shares how Netflix designs, builds, and operates its large-scale, cloud-native systems. Posts cover real-world challenges in streaming, microservices, resilience (like <em>Chaos Engineering</em>), and data pipelines - great case studies of scalable architectures in practice.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="http://engineering.fb.com" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!x8z7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc48f5ea-7c7f-45f3-af5d-ccd00e3fa02a_4292x2416.jpeg 424w, https://substackcdn.com/image/fetch/$s_!x8z7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc48f5ea-7c7f-45f3-af5d-ccd00e3fa02a_4292x2416.jpeg 848w, https://substackcdn.com/image/fetch/$s_!x8z7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc48f5ea-7c7f-45f3-af5d-ccd00e3fa02a_4292x2416.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!x8z7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc48f5ea-7c7f-45f3-af5d-ccd00e3fa02a_4292x2416.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!x8z7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc48f5ea-7c7f-45f3-af5d-ccd00e3fa02a_4292x2416.jpeg" width="1456" height="820" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dc48f5ea-7c7f-45f3-af5d-ccd00e3fa02a_4292x2416.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:820,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:313224,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;http://engineering.fb.com&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc48f5ea-7c7f-45f3-af5d-ccd00e3fa02a_4292x2416.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_!x8z7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc48f5ea-7c7f-45f3-af5d-ccd00e3fa02a_4292x2416.jpeg 424w, https://substackcdn.com/image/fetch/$s_!x8z7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc48f5ea-7c7f-45f3-af5d-ccd00e3fa02a_4292x2416.jpeg 848w, https://substackcdn.com/image/fetch/$s_!x8z7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc48f5ea-7c7f-45f3-af5d-ccd00e3fa02a_4292x2416.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!x8z7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc48f5ea-7c7f-45f3-af5d-ccd00e3fa02a_4292x2416.jpeg 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><h2><strong>Engineering at Meta</strong></h2><blockquote><p><a href="http://engineering.fb.com">engineering.fb.com</a></p></blockquote><p>Meta&#8217;s official engineering blog discusses how Facebook&#8217;s infrastructure and services operate at billions-of-users scale. It offers deep dives into distributed systems behind Facebook, Instagram, WhatsApp, etc., covering topics like scaling backend services, data storage, AI infrastructure, and reliability at massive scale.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.uber.com/blog/engineering/" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NqJJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d0e26c-f489-40ff-b221-e730426a2582_8768x5337.png 424w, https://substackcdn.com/image/fetch/$s_!NqJJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d0e26c-f489-40ff-b221-e730426a2582_8768x5337.png 848w, https://substackcdn.com/image/fetch/$s_!NqJJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d0e26c-f489-40ff-b221-e730426a2582_8768x5337.png 1272w, https://substackcdn.com/image/fetch/$s_!NqJJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d0e26c-f489-40ff-b221-e730426a2582_8768x5337.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NqJJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d0e26c-f489-40ff-b221-e730426a2582_8768x5337.png" width="1456" height="886" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/12d0e26c-f489-40ff-b221-e730426a2582_8768x5337.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:886,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:915020,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://www.uber.com/blog/engineering/&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d0e26c-f489-40ff-b221-e730426a2582_8768x5337.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_!NqJJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d0e26c-f489-40ff-b221-e730426a2582_8768x5337.png 424w, https://substackcdn.com/image/fetch/$s_!NqJJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d0e26c-f489-40ff-b221-e730426a2582_8768x5337.png 848w, https://substackcdn.com/image/fetch/$s_!NqJJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d0e26c-f489-40ff-b221-e730426a2582_8768x5337.png 1272w, https://substackcdn.com/image/fetch/$s_!NqJJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12d0e26c-f489-40ff-b221-e730426a2582_8768x5337.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>Uber Engineering Blog</strong></h2><blockquote><p><a href="https://www.uber.com/blog/engineering/">uber.com/blog/engineering</a></p></blockquote><p>Uber&#8217;s tech blog provides detailed articles on the architecture powering its real-time ride-sharing platform. It explores how Uber handles high volumes of location data and requests, with posts on microservices, geospatial databases, load balancing, and reliability techniques that keep their systems running 24/7.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://blog.cloudflare.com" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2qXW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83a34f1a-1fb2-4f61-b9db-0695959f5537_1277x431.jpeg 424w, https://substackcdn.com/image/fetch/$s_!2qXW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83a34f1a-1fb2-4f61-b9db-0695959f5537_1277x431.jpeg 848w, https://substackcdn.com/image/fetch/$s_!2qXW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83a34f1a-1fb2-4f61-b9db-0695959f5537_1277x431.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!2qXW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83a34f1a-1fb2-4f61-b9db-0695959f5537_1277x431.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2qXW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83a34f1a-1fb2-4f61-b9db-0695959f5537_1277x431.jpeg" width="1277" height="431" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/83a34f1a-1fb2-4f61-b9db-0695959f5537_1277x431.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:431,&quot;width&quot;:1277,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82648,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://blog.cloudflare.com&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83a34f1a-1fb2-4f61-b9db-0695959f5537_1277x431.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_!2qXW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83a34f1a-1fb2-4f61-b9db-0695959f5537_1277x431.jpeg 424w, https://substackcdn.com/image/fetch/$s_!2qXW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83a34f1a-1fb2-4f61-b9db-0695959f5537_1277x431.jpeg 848w, https://substackcdn.com/image/fetch/$s_!2qXW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83a34f1a-1fb2-4f61-b9db-0695959f5537_1277x431.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!2qXW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83a34f1a-1fb2-4f61-b9db-0695959f5537_1277x431.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><h2><strong>The Cloudflare Blog</strong></h2><blockquote><p><a href="https://blog.cloudflare.com">blog.cloudflare.com</a></p></blockquote><p>Cloudflare&#8217;s engineering blog is known for accessible explanations of internet-scale infrastructure. It covers how they build a fast, distributed CDN and security network, with topics like DNS and edge computing, DDoS mitigation, performance optimizations, and other systems-level insights for building a reliable global service.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://engineering.linkedin.com" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jH5Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51b79b8-e507-4410-9c56-f83e19b31ba4_2212x540.png 424w, https://substackcdn.com/image/fetch/$s_!jH5Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51b79b8-e507-4410-9c56-f83e19b31ba4_2212x540.png 848w, https://substackcdn.com/image/fetch/$s_!jH5Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51b79b8-e507-4410-9c56-f83e19b31ba4_2212x540.png 1272w, https://substackcdn.com/image/fetch/$s_!jH5Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51b79b8-e507-4410-9c56-f83e19b31ba4_2212x540.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jH5Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51b79b8-e507-4410-9c56-f83e19b31ba4_2212x540.png" width="1456" height="355" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d51b79b8-e507-4410-9c56-f83e19b31ba4_2212x540.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:355,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:25367,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://engineering.linkedin.com&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51b79b8-e507-4410-9c56-f83e19b31ba4_2212x540.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_!jH5Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51b79b8-e507-4410-9c56-f83e19b31ba4_2212x540.png 424w, https://substackcdn.com/image/fetch/$s_!jH5Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51b79b8-e507-4410-9c56-f83e19b31ba4_2212x540.png 848w, https://substackcdn.com/image/fetch/$s_!jH5Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51b79b8-e507-4410-9c56-f83e19b31ba4_2212x540.png 1272w, https://substackcdn.com/image/fetch/$s_!jH5Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51b79b8-e507-4410-9c56-f83e19b31ba4_2212x540.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h2><strong>LinkedIn Engineering</strong></h2><blockquote><p><a href="https://engineering.linkedin.com">engineering.linkedin.com</a></p></blockquote><p>LinkedIn&#8217;s engineering blog shares how the professional network designs its backend systems and data pipelines. It features posts on search index scaling, feed generation, big data infrastructure (like Kafka and Gobblin), AI recommendations, and general best practices for building distributed, data-intensive applications.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://aws.amazon.com/blogs/architecture/" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7fbV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F587aa6de-d1a2-4d78-bbb9-1862b8481b45_1510x692.png 424w, https://substackcdn.com/image/fetch/$s_!7fbV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F587aa6de-d1a2-4d78-bbb9-1862b8481b45_1510x692.png 848w, https://substackcdn.com/image/fetch/$s_!7fbV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F587aa6de-d1a2-4d78-bbb9-1862b8481b45_1510x692.png 1272w, https://substackcdn.com/image/fetch/$s_!7fbV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F587aa6de-d1a2-4d78-bbb9-1862b8481b45_1510x692.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7fbV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F587aa6de-d1a2-4d78-bbb9-1862b8481b45_1510x692.png" width="1456" height="667" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/587aa6de-d1a2-4d78-bbb9-1862b8481b45_1510x692.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:667,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:78726,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://aws.amazon.com/blogs/architecture/&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F587aa6de-d1a2-4d78-bbb9-1862b8481b45_1510x692.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_!7fbV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F587aa6de-d1a2-4d78-bbb9-1862b8481b45_1510x692.png 424w, https://substackcdn.com/image/fetch/$s_!7fbV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F587aa6de-d1a2-4d78-bbb9-1862b8481b45_1510x692.png 848w, https://substackcdn.com/image/fetch/$s_!7fbV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F587aa6de-d1a2-4d78-bbb9-1862b8481b45_1510x692.png 1272w, https://substackcdn.com/image/fetch/$s_!7fbV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F587aa6de-d1a2-4d78-bbb9-1862b8481b45_1510x692.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>AWS Architecture Blog</strong></h2><blockquote><p><a href="https://aws.amazon.com/blogs/architecture/">aws.amazon.com/blogs/architecture</a></p></blockquote><p>AWS&#8217;s official architecture blog covers cloud design patterns and best practices. It&#8217;s useful for learning how to design scalable, highly available systems on the cloud. Posts include reference architectures, case studies, and tips on microservices, serverless, database scaling, caching, and more &#8211; straight from AWS solution architects.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://stripe.com/blog/engineering" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pWoN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf189d59-56ea-47b7-86e1-7889f6d74f3c_3000x1249.png 424w, https://substackcdn.com/image/fetch/$s_!pWoN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf189d59-56ea-47b7-86e1-7889f6d74f3c_3000x1249.png 848w, https://substackcdn.com/image/fetch/$s_!pWoN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf189d59-56ea-47b7-86e1-7889f6d74f3c_3000x1249.png 1272w, https://substackcdn.com/image/fetch/$s_!pWoN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf189d59-56ea-47b7-86e1-7889f6d74f3c_3000x1249.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pWoN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf189d59-56ea-47b7-86e1-7889f6d74f3c_3000x1249.png" width="1456" height="606" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/df189d59-56ea-47b7-86e1-7889f6d74f3c_3000x1249.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:606,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:60363,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://stripe.com/blog/engineering&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf189d59-56ea-47b7-86e1-7889f6d74f3c_3000x1249.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_!pWoN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf189d59-56ea-47b7-86e1-7889f6d74f3c_3000x1249.png 424w, https://substackcdn.com/image/fetch/$s_!pWoN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf189d59-56ea-47b7-86e1-7889f6d74f3c_3000x1249.png 848w, https://substackcdn.com/image/fetch/$s_!pWoN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf189d59-56ea-47b7-86e1-7889f6d74f3c_3000x1249.png 1272w, https://substackcdn.com/image/fetch/$s_!pWoN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf189d59-56ea-47b7-86e1-7889f6d74f3c_3000x1249.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>Stripe Engineering Blog</strong></h2><blockquote><p><a href="https://stripe.com/blog/engineering">stripe.com/blog/engineering</a></p></blockquote><p>Stripe&#8217;s engineering blog discusses how they build and scale a global payments platform. It delves into topics like building reliable financial systems, API design, infrastructure for high availability (five-nines uptime), data migrations with zero downtime, and other challenges in creating robust payment and billing services at scale.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://slack.engineering" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0EnU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F224c6d97-bfae-4772-8064-5152a110f96d_1600x572.png 424w, https://substackcdn.com/image/fetch/$s_!0EnU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F224c6d97-bfae-4772-8064-5152a110f96d_1600x572.png 848w, https://substackcdn.com/image/fetch/$s_!0EnU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F224c6d97-bfae-4772-8064-5152a110f96d_1600x572.png 1272w, https://substackcdn.com/image/fetch/$s_!0EnU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F224c6d97-bfae-4772-8064-5152a110f96d_1600x572.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0EnU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F224c6d97-bfae-4772-8064-5152a110f96d_1600x572.png" width="1456" height="521" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/224c6d97-bfae-4772-8064-5152a110f96d_1600x572.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:521,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:33888,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://slack.engineering&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F224c6d97-bfae-4772-8064-5152a110f96d_1600x572.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_!0EnU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F224c6d97-bfae-4772-8064-5152a110f96d_1600x572.png 424w, https://substackcdn.com/image/fetch/$s_!0EnU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F224c6d97-bfae-4772-8064-5152a110f96d_1600x572.png 848w, https://substackcdn.com/image/fetch/$s_!0EnU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F224c6d97-bfae-4772-8064-5152a110f96d_1600x572.png 1272w, https://substackcdn.com/image/fetch/$s_!0EnU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F224c6d97-bfae-4772-8064-5152a110f96d_1600x572.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>Slack Engineering</strong></h2><blockquote><p><a href="https://slack.engineering">slack.engineering</a></p></blockquote><p>Slack&#8217;s tech blog offers insights into the backend of their popular messaging platform. Engineers at Slack share how they handle real-time messaging and event-driven architecture, scale out infrastructure to support millions of users, optimize performance, and ensure reliability. It&#8217;s full of practical lessons on building responsive, scalable web services.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://engineering.atspotify.com" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!icIy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a6fd1b-358a-4d6c-9332-3ceeb36d5d30_3432x940.png 424w, https://substackcdn.com/image/fetch/$s_!icIy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a6fd1b-358a-4d6c-9332-3ceeb36d5d30_3432x940.png 848w, https://substackcdn.com/image/fetch/$s_!icIy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a6fd1b-358a-4d6c-9332-3ceeb36d5d30_3432x940.png 1272w, https://substackcdn.com/image/fetch/$s_!icIy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a6fd1b-358a-4d6c-9332-3ceeb36d5d30_3432x940.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!icIy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a6fd1b-358a-4d6c-9332-3ceeb36d5d30_3432x940.png" width="1456" height="399" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b0a6fd1b-358a-4d6c-9332-3ceeb36d5d30_3432x940.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:399,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:51035,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://engineering.atspotify.com&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a6fd1b-358a-4d6c-9332-3ceeb36d5d30_3432x940.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_!icIy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a6fd1b-358a-4d6c-9332-3ceeb36d5d30_3432x940.png 424w, https://substackcdn.com/image/fetch/$s_!icIy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a6fd1b-358a-4d6c-9332-3ceeb36d5d30_3432x940.png 848w, https://substackcdn.com/image/fetch/$s_!icIy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a6fd1b-358a-4d6c-9332-3ceeb36d5d30_3432x940.png 1272w, https://substackcdn.com/image/fetch/$s_!icIy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a6fd1b-358a-4d6c-9332-3ceeb36d5d30_3432x940.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>Spotify Engineering</strong></h2><blockquote><p><a href="https://engineering.atspotify.com">engineering.atspotify.com</a></p></blockquote><p>Spotify&#8217;s engineering blog covers how the world&#8217;s largest audio streaming service works under the hood. It includes stories about delivering music to hundreds of millions of users &#8211; from microservices and cloud architecture to data processing (for features like Spotify Wrapped), recommendation algorithms, and scaling storage and bandwidth for a global audience.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://airbnb.tech" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pZ5d!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb78ef4b1-195c-4064-8ed0-3b3e2e21eaed_978x450.jpeg 424w, https://substackcdn.com/image/fetch/$s_!pZ5d!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb78ef4b1-195c-4064-8ed0-3b3e2e21eaed_978x450.jpeg 848w, https://substackcdn.com/image/fetch/$s_!pZ5d!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb78ef4b1-195c-4064-8ed0-3b3e2e21eaed_978x450.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!pZ5d!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb78ef4b1-195c-4064-8ed0-3b3e2e21eaed_978x450.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pZ5d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb78ef4b1-195c-4064-8ed0-3b3e2e21eaed_978x450.jpeg" width="978" height="450" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b78ef4b1-195c-4064-8ed0-3b3e2e21eaed_978x450.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:978,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:8484,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://airbnb.tech&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb78ef4b1-195c-4064-8ed0-3b3e2e21eaed_978x450.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_!pZ5d!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb78ef4b1-195c-4064-8ed0-3b3e2e21eaed_978x450.jpeg 424w, https://substackcdn.com/image/fetch/$s_!pZ5d!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb78ef4b1-195c-4064-8ed0-3b3e2e21eaed_978x450.jpeg 848w, https://substackcdn.com/image/fetch/$s_!pZ5d!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb78ef4b1-195c-4064-8ed0-3b3e2e21eaed_978x450.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!pZ5d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb78ef4b1-195c-4064-8ed0-3b3e2e21eaed_978x450.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><h2><strong>Airbnb Engineering &amp; Data Science</strong></h2><blockquote><p><a href="https://airbnb.tech">airbnb.tech</a></p></blockquote><p>Airbnb&#8217;s tech blog shares how they design the systems behind their home-sharing marketplace. It features articles on topics like service-oriented architecture, handling dynamic search and pricing at scale, data infrastructure (e.g. streaming and machine learning pipelines), and site reliability. It&#8217;s a great peek into scalable backend design for a two-sided online platform.</p><div><hr></div><p><strong>&#128233; Want more like this?</strong></p><p>Learning system design isn&#8217;t a one-week sprint.  It&#8217;s a long-term skill that grows with practice and good guidance.</p><p>No spam. No hype. Just things I wish someone had explained to me earlier.</p><p><strong>&#128073; Subscribe if you want the next post delivered straight to you.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Many other companies have similar engineering blogs - such as Meta&#8217;s Facebook Engineering and the Uber Engineering Blog - each offering a unique perspective. Don&#8217;t worry if some posts feel advanced; even a quick skim can introduce you to new concepts. Over time, you&#8217;ll start recognizing patterns and best practices that these big teams rely on. </p><blockquote><p><strong>Pro tip:</strong> if you encounter unfamiliar terms in a post, do a bit of quick research. That&#8217;s how I learned about things like <em>circuit breakers</em> and <em>CAP theorem</em> in the early days - one blog post at a time!</p></blockquote><h1>Personal Developer Blogs &#9997;&#65039;</h1><p>Company blogs are insightful, but they can be a bit formal. This is where personal developer blogs shine. Individual tech bloggers often share candid stories, practical how-to guides, and lessons learned from their own projects. It&#8217;s like sitting down with a friendly mentor who says, &#8220;<em>Hey, here&#8217;s something cool I figured out, maybe it&#8217;ll help you too</em>&#8221;. In fact, many developers start blogging specifically to document what they learn and to help others. These blogs tend to be very approachable for beginners. A few great ones to check out.</p><h2><strong>ByteByteGo Newsletter</strong></h2><blockquote><p><em>Alex Xu&#8217;s newsletter </em>| <a href="https://blog.bytebytego.com">link</a></p></blockquote><p>A weekly newsletter by <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Alex Xu&quot;,&quot;id&quot;:22329494,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F10cd1afb-9a92-433e-bbf4-f726eb8ffdb3_375x375.jpeg&quot;,&quot;uuid&quot;:&quot;89857ab2-f803-498a-a234-0a82dddeaba5&quot;}" data-component-name="MentionToDOM"></span> (author of the <em>System Design Interview</em> books). It breaks down complex system design concepts into simple terms with diagrams. ByteByteGo covers everything from caching to database sharding, often using real-world case studies (like how popular systems are designed), making it a fantastic learning resource for system design and architecture.</p><h2><strong>The System Design Newsletter</strong></h2><blockquote><p><em>Neo Kim&#8217;s newsletter </em>| <em><a href="https://newsletter.systemdesign.one">link</a></em></p></blockquote><p><span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Neo Kim&quot;,&quot;id&quot;:135589200,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c103940f-0d8b-47e7-9a33-013202e17bb8_389x389.jpeg&quot;,&quot;uuid&quot;:&quot;96049b2b-82c3-4fb0-9465-3cab679fc129&quot;}" data-component-name="MentionToDOM"></span>&#8217;s Substack focuses on teaching system design in an approachable way. It&#8217;s known for beginner-friendly explanations of how things work (e.g. JWT authentication, load balancers) and fun case studies (&#8220;<em>How Uber handles 1M requests/sec</em>&#8221; etc.). Each post breaks down a topic or a famous system&#8217;s architecture into bite-sized lessons, perfect for engineers new to scalable system concepts.</p><h2><strong>Hello, World! System Design</strong></h2><blockquote><p><em>Rohit Lakhotia&#8217;s newsletter</em> | <a href="https://hw.glich.co">link</a></p></blockquote><p>This free weekly newsletter delivers one system design case study every Monday, often examining how big tech companies solve specific problems. Rohit Lakhotia&#8217;s content is very easy to digest, with plenty of diagrams and simple language. Topics range from &#8220;<em>How does URL shortening work?</em>&#8221; to deep dives like &#8220;<em>How Twitter handles millions of tweets</em>&#8221;, helping readers gradually build up system design knowledge through real examples.</p><h2><strong>AlgoMaster Newsletter</strong></h2><blockquote><p><em>Ashish Pratap Singh&#8217;s newsletter </em>| <a href="https://blog.algomaster.io">link</a></p></blockquote><p><span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Ashish Pratap Singh&quot;,&quot;id&quot;:83602743,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bec4e97e-29d0-4080-b529-db1f5fc4d1d2_1536x1536.jpeg&quot;,&quot;uuid&quot;:&quot;51a47b72-3dd7-44f4-8454-0189d06a0b9f&quot;}" data-component-name="MentionToDOM"></span>&#8217;s newsletter covers both coding and system design interview prep. It offers a mix of free and paid content, including lessons on scalable system concepts and interview questions (like designing a rate limiter or a chat system). It&#8217;s a handy resource for mastering fundamentals (caching, queueing, etc.) and for practicing the kind of system design problems you might face in tech interviews.</p><h2><strong>High Scalability</strong></h2><blockquote><p><em>Todd Hoff&#8217;s blog </em>| <a href="https://highscalability.com">link</a></p></blockquote><p>High Scalability is a long-running blog that compiles architecture lessons from real-world systems. Todd Hoff shares &#8220;<em>case studies</em>&#8221; of websites and applications that scaled to millions of users, analyzing the patterns and decisions that worked. Browsing the archives, you&#8217;ll find discussions of everything from how Facebook&#8217;s early architecture evolved to the design principles behind cloud services. It&#8217;s a treasure trove of scalability war stories and best practices.</p><h2><strong>Martin Fowler&#8217;s Blog</strong></h2><blockquote><p><em>Martin Fowler&#8217;s website </em>| <a href="https://www.martinfowler.com">link</a></p></blockquote><p>Martin Fowler, a renowned software architect, writes about software design paradigms that underpin robust systems. His blog isn&#8217;t exclusively about massive-scale systems, but it covers essential architectural topics like microservices, enterprise integration patterns, and evolutionary architecture. For anyone learning system design, Martin&#8217;s articles help build a strong foundation in software architecture fundamentals and thought processes.</p><h2><strong>System Design Classroom</strong></h2><blockquote><p><em>Raul Junco&#8217;s newsletter </em>| <a href="https://newsletter.systemdesignclassroom.com">link</a></p></blockquote><p><span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Raul Junco&quot;,&quot;id&quot;:98661477,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/45a92f5e-1e2e-4dfa-9ff3-45fc5ad0c57e_612x612.png&quot;,&quot;uuid&quot;:&quot;7fe7249b-168f-44bf-b71f-b27ec0019e97&quot;}" data-component-name="MentionToDOM"></span>&#8217;s Substack is a fresh resource that helps developers &#8220;<em>build better software</em>&#8221; through system design insights. Each post tackles a specific concept or trade-off (for example, caching strategies, consistency vs. scalability, or pitfalls in distributed systems) in a practical way. The lessons are written in a friendly tone with concrete examples, making complex topics approachable for early-career engineers.</p><p>There are countless other personal blogs out there. Some notable mentions include <strong>Joel on Software</strong> (Joel Spolsky&#8217;s blog with great posts on software management and development), <strong>Scott Hanselman&#8217;s Blog </strong>(covering web development and productivity tips in a friendly tone), and community sites like <strong>DEV Community (Dev.to)</strong> or the <strong>freeCodeCamp blog</strong>, where lots of individual developers publish articles. The key is to find writers whose voice and topics resonate with you. When you do, follow their work. You&#8217;ll not only learn specific skills (like a new JavaScript trick or how to deploy an app) but also pick up good habits and mindsets by osmosis. And don&#8217;t be shy about interacting &#8211; many bloggers (myself included) love when readers leave comments or questions. It turns a blog into a two-way conversation, and you might just make some developer friends along the way!</p><div><hr></div><p>&#128257; <strong>Know someone who would find this useful?</strong></p><p>If you&#8217;ve ever felt overwhelmed while learning system design, you&#8217;re definitely not alone. Feel free to share this post with:</p><ul><li><p>a friend preparing for system design interviews</p></li><li><p>a junior developer who&#8217;s just getting started</p></li><li><p>or your past self from a few years ago &#128521;</p></li></ul><p><strong>Sometimes the right resource at the right time makes all the difference.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/how-to-learn-system-design-without?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://iam.slys.dev/p/how-to-learn-system-design-without?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h1>Must-Read Books &#128218;</h1><p>In this digital age, blogs and online code are readily available, so are books still worth it? Absolutely <strong>yes!</strong> Good books can organize and present knowledge in a way that builds your understanding step by step. They often go deeper into the <em>why</em> behind best practices and cover decades of wisdom that might be scattered across hundreds of blog posts. For a beginner (or any engineer, really), certain classic books are like a rite of passage - they shape how you think about writing software. Here are a few highly-recommended ones, each a gem in its own right!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://a.co/d/h75N77X" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4tZH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d4bf37b-04b0-4488-bad6-02f6b153e57d_1000x1499.jpeg 424w, https://substackcdn.com/image/fetch/$s_!4tZH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d4bf37b-04b0-4488-bad6-02f6b153e57d_1000x1499.jpeg 848w, https://substackcdn.com/image/fetch/$s_!4tZH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d4bf37b-04b0-4488-bad6-02f6b153e57d_1000x1499.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!4tZH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d4bf37b-04b0-4488-bad6-02f6b153e57d_1000x1499.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4tZH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d4bf37b-04b0-4488-bad6-02f6b153e57d_1000x1499.jpeg" width="1000" height="1499" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9d4bf37b-04b0-4488-bad6-02f6b153e57d_1000x1499.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1499,&quot;width&quot;:1000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:54472,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://a.co/d/h75N77X&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d4bf37b-04b0-4488-bad6-02f6b153e57d_1000x1499.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_!4tZH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d4bf37b-04b0-4488-bad6-02f6b153e57d_1000x1499.jpeg 424w, https://substackcdn.com/image/fetch/$s_!4tZH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d4bf37b-04b0-4488-bad6-02f6b153e57d_1000x1499.jpeg 848w, https://substackcdn.com/image/fetch/$s_!4tZH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d4bf37b-04b0-4488-bad6-02f6b153e57d_1000x1499.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!4tZH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d4bf37b-04b0-4488-bad6-02f6b153e57d_1000x1499.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><h2><em>System Design Interview &#8211; An Insider&#8217;s Guide (Volume 1)</em> </h2><blockquote><p>by <strong>Alex Xu | </strong><a href="https://a.co/d/h75N77X">link</a></p></blockquote><p>A very popular book focused on preparing for system design interviews. It introduces a clear four-step framework for tackling design problems and walks through detailed examples (like designing a URL shortener, Twitter feed, etc.). Great for beginners &#8211; it teaches fundamentals of scalable design in a concise, easy-to-understand way, as if you&#8217;re solving interview questions with an expert mentor.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://a.co/d/cClthhA" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!n8nJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50519926-25fb-4a48-b1e3-b6266ee21fbe_1000x1429.jpeg 424w, https://substackcdn.com/image/fetch/$s_!n8nJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50519926-25fb-4a48-b1e3-b6266ee21fbe_1000x1429.jpeg 848w, https://substackcdn.com/image/fetch/$s_!n8nJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50519926-25fb-4a48-b1e3-b6266ee21fbe_1000x1429.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!n8nJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50519926-25fb-4a48-b1e3-b6266ee21fbe_1000x1429.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!n8nJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50519926-25fb-4a48-b1e3-b6266ee21fbe_1000x1429.jpeg" width="1000" height="1429" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/50519926-25fb-4a48-b1e3-b6266ee21fbe_1000x1429.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1429,&quot;width&quot;:1000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:48644,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://a.co/d/cClthhA&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50519926-25fb-4a48-b1e3-b6266ee21fbe_1000x1429.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_!n8nJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50519926-25fb-4a48-b1e3-b6266ee21fbe_1000x1429.jpeg 424w, https://substackcdn.com/image/fetch/$s_!n8nJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50519926-25fb-4a48-b1e3-b6266ee21fbe_1000x1429.jpeg 848w, https://substackcdn.com/image/fetch/$s_!n8nJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50519926-25fb-4a48-b1e3-b6266ee21fbe_1000x1429.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!n8nJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50519926-25fb-4a48-b1e3-b6266ee21fbe_1000x1429.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><h2><em>System Design Interview &#8211; An Insider&#8217;s Guide (Volume 2)</em> </h2><blockquote><p>by <strong>Alex Xu &amp; Sahn Lam | </strong><a href="https://a.co/d/cClthhA">link</a></p></blockquote><p>The second volume builds on the first with all-new case studies and deeper dives. It covers more complex systems (such as designing YouTube, or a ridesharing system) and advanced topics like reliability, consistency trade-offs, and database sharding. Like Volume 1, it&#8217;s very approachable - perfect for developers who have basics down and want to level up with more practice scenarios and expert guidance.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://a.co/d/gHMT0vD" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!b5dR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb555a85-1283-44ab-9d61-13ce7745894f_1143x1500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!b5dR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb555a85-1283-44ab-9d61-13ce7745894f_1143x1500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!b5dR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb555a85-1283-44ab-9d61-13ce7745894f_1143x1500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!b5dR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb555a85-1283-44ab-9d61-13ce7745894f_1143x1500.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!b5dR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb555a85-1283-44ab-9d61-13ce7745894f_1143x1500.jpeg" width="1143" height="1500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/db555a85-1283-44ab-9d61-13ce7745894f_1143x1500.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1500,&quot;width&quot;:1143,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:230882,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://a.co/d/gHMT0vD&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb555a85-1283-44ab-9d61-13ce7745894f_1143x1500.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_!b5dR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb555a85-1283-44ab-9d61-13ce7745894f_1143x1500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!b5dR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb555a85-1283-44ab-9d61-13ce7745894f_1143x1500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!b5dR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb555a85-1283-44ab-9d61-13ce7745894f_1143x1500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!b5dR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb555a85-1283-44ab-9d61-13ce7745894f_1143x1500.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><h2><em>Designing Data-Intensive Applications</em></h2><blockquote><p>by <strong>Martin Kleppmann | </strong><a href="https://a.co/d/gHMT0vD">link</a></p></blockquote><p>A highly-acclaimed book that digs into the architecture of data systems. It explains how databases, distributed storage, and stream processing systems work under the hood, comparing different designs (SQL vs NoSQL, consistency models, batch vs streaming, etc.). This is more advanced in depth, but it&#8217;s essentially the <em>Bible</em> of distributed data systems &#8211; a must-read to truly understand scalability, fault tolerance, and data engineering in modern architectures.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://a.co/d/c0REeQl" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jLgT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79be8c25-5a0d-4356-97fe-57221209dd60_1143x1500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!jLgT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79be8c25-5a0d-4356-97fe-57221209dd60_1143x1500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!jLgT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79be8c25-5a0d-4356-97fe-57221209dd60_1143x1500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!jLgT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79be8c25-5a0d-4356-97fe-57221209dd60_1143x1500.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jLgT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79be8c25-5a0d-4356-97fe-57221209dd60_1143x1500.jpeg" width="1143" height="1500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/79be8c25-5a0d-4356-97fe-57221209dd60_1143x1500.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1500,&quot;width&quot;:1143,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:249765,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://a.co/d/c0REeQl&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79be8c25-5a0d-4356-97fe-57221209dd60_1143x1500.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_!jLgT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79be8c25-5a0d-4356-97fe-57221209dd60_1143x1500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!jLgT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79be8c25-5a0d-4356-97fe-57221209dd60_1143x1500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!jLgT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79be8c25-5a0d-4356-97fe-57221209dd60_1143x1500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!jLgT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79be8c25-5a0d-4356-97fe-57221209dd60_1143x1500.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><h2><em>Building Microservices</em></h2><blockquote><p>by <strong>Sam Newman | </strong><a href="https://a.co/d/c0REeQl">link</a></p></blockquote><p>An excellent introduction to the microservices style of architecture. Sam Newman covers why and how to split a system into services, and explores practical concerns like service communication, deployment, scaling, and monitoring. The book is full of real-world advice and anecdotes. It&#8217;s great for learners who want to understand service-oriented architecture and design systems that are modular, scalable, and easier to maintain over time.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://a.co/d/4CXJhJW" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!APEU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb12ad0b0-f142-4194-afa0-a909bbef1673_1250x1500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!APEU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb12ad0b0-f142-4194-afa0-a909bbef1673_1250x1500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!APEU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb12ad0b0-f142-4194-afa0-a909bbef1673_1250x1500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!APEU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb12ad0b0-f142-4194-afa0-a909bbef1673_1250x1500.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!APEU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb12ad0b0-f142-4194-afa0-a909bbef1673_1250x1500.jpeg" width="1250" height="1500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b12ad0b0-f142-4194-afa0-a909bbef1673_1250x1500.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1500,&quot;width&quot;:1250,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:114035,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://a.co/d/4CXJhJW&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb12ad0b0-f142-4194-afa0-a909bbef1673_1250x1500.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_!APEU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb12ad0b0-f142-4194-afa0-a909bbef1673_1250x1500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!APEU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb12ad0b0-f142-4194-afa0-a909bbef1673_1250x1500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!APEU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb12ad0b0-f142-4194-afa0-a909bbef1673_1250x1500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!APEU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb12ad0b0-f142-4194-afa0-a909bbef1673_1250x1500.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><h2><em>Release It! Design and Deploy Production-Ready Software</em></h2><blockquote><p>by <strong>Michael T. Nygard | </strong><a href="https://a.co/d/4CXJhJW">link</a></p></blockquote><p>A classic book focusing on the <strong>pragmatic</strong> aspects of system design &#8211; namely, making systems robust in real-life production. Michael Nygard introduces patterns for building resilient systems (circuit breakers, bulkheads, failsafes) and discusses the kinds of failure modes and outages that can happen at scale. Reading this will teach you how to design for stability and reliability, preparing you to avoid common pitfalls when your system is live and serving users.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://a.co/d/8zIXacC" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Whth!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05b095e2-9f39-46b4-b538-2b977e5b40a9_1143x1500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Whth!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05b095e2-9f39-46b4-b538-2b977e5b40a9_1143x1500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Whth!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05b095e2-9f39-46b4-b538-2b977e5b40a9_1143x1500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Whth!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05b095e2-9f39-46b4-b538-2b977e5b40a9_1143x1500.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Whth!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05b095e2-9f39-46b4-b538-2b977e5b40a9_1143x1500.jpeg" width="1143" height="1500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/05b095e2-9f39-46b4-b538-2b977e5b40a9_1143x1500.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1500,&quot;width&quot;:1143,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:187223,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://a.co/d/8zIXacC&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05b095e2-9f39-46b4-b538-2b977e5b40a9_1143x1500.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_!Whth!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05b095e2-9f39-46b4-b538-2b977e5b40a9_1143x1500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Whth!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05b095e2-9f39-46b4-b538-2b977e5b40a9_1143x1500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Whth!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05b095e2-9f39-46b4-b538-2b977e5b40a9_1143x1500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Whth!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05b095e2-9f39-46b4-b538-2b977e5b40a9_1143x1500.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><h2><em>Fundamentals of Software Architecture</em></h2><blockquote><p>by <strong>Mark Richards &amp; Neal Ford | </strong><a href="https://a.co/d/8zIXacC">link</a></p></blockquote><p>A comprehensive guide to software architecture principles. It&#8217;s not limited to web scale systems - it covers a broad range of architectural styles (layered, microservices, event-driven, etc.), component design, and quality attributes (like scalability, performance, security). For someone new to architecture, this book builds a solid base of knowledge, teaching you how to think like an architect and evaluate design trade-offs before diving into specific implementations.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://a.co/d/8b9r7m7" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Xv6p!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b98b3f6-ab53-4e9b-9a8a-9cc82ec85d3e_1000x1500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Xv6p!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b98b3f6-ab53-4e9b-9a8a-9cc82ec85d3e_1000x1500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Xv6p!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b98b3f6-ab53-4e9b-9a8a-9cc82ec85d3e_1000x1500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Xv6p!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b98b3f6-ab53-4e9b-9a8a-9cc82ec85d3e_1000x1500.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Xv6p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b98b3f6-ab53-4e9b-9a8a-9cc82ec85d3e_1000x1500.jpeg" width="1000" height="1500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8b98b3f6-ab53-4e9b-9a8a-9cc82ec85d3e_1000x1500.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1500,&quot;width&quot;:1000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:81554,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://a.co/d/8b9r7m7&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b98b3f6-ab53-4e9b-9a8a-9cc82ec85d3e_1000x1500.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_!Xv6p!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b98b3f6-ab53-4e9b-9a8a-9cc82ec85d3e_1000x1500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Xv6p!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b98b3f6-ab53-4e9b-9a8a-9cc82ec85d3e_1000x1500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Xv6p!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b98b3f6-ab53-4e9b-9a8a-9cc82ec85d3e_1000x1500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Xv6p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b98b3f6-ab53-4e9b-9a8a-9cc82ec85d3e_1000x1500.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><h2><em>Understanding Distributed Systems</em></h2><blockquote><p>by <strong>Roberto Vitillo | </strong><a href="https://a.co/d/8b9r7m7">link</a><strong> </strong></p></blockquote><p>A concise and beginner-friendly book that demystifies distributed systems. Roberto Vitillo explains core concepts such as network communication, replication, partitioning, and consensus in plain language with simple examples. It&#8217;s a shorter read that doesn&#8217;t assume prior knowledge - perfect for junior engineers who want to grasp how distributed systems work (and why they can be tricky) in order to design better software systems.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://a.co/d/4cQifVG" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6znJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9373d7fa-4349-4f87-ba51-a57d1082890c_800x1053.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6znJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9373d7fa-4349-4f87-ba51-a57d1082890c_800x1053.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6znJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9373d7fa-4349-4f87-ba51-a57d1082890c_800x1053.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6znJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9373d7fa-4349-4f87-ba51-a57d1082890c_800x1053.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6znJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9373d7fa-4349-4f87-ba51-a57d1082890c_800x1053.jpeg" width="800" height="1053" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9373d7fa-4349-4f87-ba51-a57d1082890c_800x1053.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1053,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82305,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://a.co/d/4cQifVG&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9373d7fa-4349-4f87-ba51-a57d1082890c_800x1053.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_!6znJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9373d7fa-4349-4f87-ba51-a57d1082890c_800x1053.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6znJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9373d7fa-4349-4f87-ba51-a57d1082890c_800x1053.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6znJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9373d7fa-4349-4f87-ba51-a57d1082890c_800x1053.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6znJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9373d7fa-4349-4f87-ba51-a57d1082890c_800x1053.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><h2><em>The Art of Scalability</em></h2><blockquote><p>by <strong>Martin L. Abbott &amp; Michael T. Fisher | </strong><a href="https://a.co/d/4cQifVG">link</a></p></blockquote><p>Written by two former web executives, this book takes a holistic view of scalability. It covers technical strategies for scaling (architecture patterns, database scaling, caching, etc.) <em>and</em> touches on the people and process side (like organizing engineering teams for scale). The authors present a framework called <em>the Scale Cube</em> for thinking about growth. It&#8217;s an insightful read for learning to design systems that can grow from a small app to an enterprise-scale platform, while avoiding common scaling bottlenecks.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://a.co/d/9UerWYt" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!E1KO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d11e65c-2d29-4dfa-9765-da2a2af4f98c_1143x1500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!E1KO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d11e65c-2d29-4dfa-9765-da2a2af4f98c_1143x1500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!E1KO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d11e65c-2d29-4dfa-9765-da2a2af4f98c_1143x1500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!E1KO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d11e65c-2d29-4dfa-9765-da2a2af4f98c_1143x1500.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!E1KO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d11e65c-2d29-4dfa-9765-da2a2af4f98c_1143x1500.jpeg" width="1143" height="1500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6d11e65c-2d29-4dfa-9765-da2a2af4f98c_1143x1500.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1500,&quot;width&quot;:1143,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:181326,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://a.co/d/9UerWYt&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/182873025?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d11e65c-2d29-4dfa-9765-da2a2af4f98c_1143x1500.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_!E1KO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d11e65c-2d29-4dfa-9765-da2a2af4f98c_1143x1500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!E1KO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d11e65c-2d29-4dfa-9765-da2a2af4f98c_1143x1500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!E1KO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d11e65c-2d29-4dfa-9765-da2a2af4f98c_1143x1500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!E1KO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d11e65c-2d29-4dfa-9765-da2a2af4f98c_1143x1500.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><h2><em>Site Reliability Engineering: How Google Runs Production Systems</em></h2><blockquote><p>by <strong>Jennifer Petoff</strong>, <strong>Betsy Beyer</strong>, <strong>Chris Jones</strong>, <strong>Niall Richard Murphy | </strong><a href="https://a.co/d/9UerWYt">link</a></p></blockquote><p>This book, authored by Google&#8217;s SREs, is all about operating and sustaining large systems reliably. It&#8217;s essentially a compilation of essays on topics like building scalable monitoring, handling on-call and incidents, automation, and engineering for resiliency (at Google scale). While it&#8217;s more about running systems, there are plenty of design insights - after all, <em>&#8220;hope is not a strategy&#8221;</em> when designing for reliability. Reading it will broaden your perspective on designing systems that not only scale, but stay available and maintainable in real-world conditions.</p><p>Of course, there are other fantastic books depending on your interests: <em>Introduction to Algorithms</em> (Cormen et al.) if you&#8217;re into computer science theory, <em>Don&#8217;t Make Me Think</em> (Steve Krug) if you want to learn about usability and user experience, <em>Head First Design Patterns</em> for an interactive way to learn design patterns, and more. But the three above are almost universally praised in the developer community for building solid foundations. If you can, I highly recommend picking up at least one of them. Try reading a chapter after you&#8217;ve done a bit of coding for the day - you&#8217;ll often find something in the book that you can immediately apply to your own code, which helps reinforce the lesson. Plus, nothing beats the feel of flipping through a book and realizing you&#8217;ve understood concepts that seemed gibberish a month ago. &#128214;&#127881;</p><h1>Conclusion &#127775;</h1><p>Piecing together the software development puzzle takes time, but with these resources, you&#8217;ve got a clear picture to work towards. Each company blog post you read, each personal blog story and each chapter in a book adds another piece to your understanding. Over time, things that once seemed mystifying will click into place. You&#8217;ll catch yourself saying, &#8220;<em>Oh, I remember reading about this!</em>&#8221; and suddenly using that knowledge in your own project. That&#8217;s one of the most rewarding feelings as a new developer.</p><p>Remember, everyone&#8217;s journey is a bit different. You might prefer reading a blog over a book, or tinkering with code over reading about code - and that&#8217;s okay! Use the mix of resources that <em>excites you the most</em>. The fact that you&#8217;re proactively learning from blogs, open-source, and books already sets you apart. As Steve McConnell noted, the average programmer reads less than one technical book per year - but here you are, eager to learn and improve. Kudos to you. &#128588;</p><p>Finally, stay curious and don&#8217;t be afraid to ask for help. The developer community is full of people who were once beginners and are happy to pay it forward. Whether it&#8217;s commenting on a blog, joining a forum, or asking a question on Stack Overflow, you&#8217;ll find that most folks are welcoming and supportive. I <strong>love</strong> engaging with readers on this blog, so if you have questions or want to share your own favorite resource, drop a comment below. Let&#8217;s learn together!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/how-to-learn-system-design-without/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/how-to-learn-system-design-without/comments"><span>Leave a comment</span></a></p><p><strong>Happy coding, and enjoy the journey!</strong> </p><p>The pieces will come together before you know it. &#129513;&#10024;</p>]]></content:encoded></item><item><title><![CDATA[Why databases get split — sharding, partitioning, and replication without fear]]></title><description><![CDATA[Systems Literacy]]></description><link>https://iam.slys.dev/p/why-databases-get-split-sharding</link><guid isPermaLink="false">https://iam.slys.dev/p/why-databases-get-split-sharding</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 22 Dec 2025 21:10:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!GZbt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GZbt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GZbt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic 424w, https://substackcdn.com/image/fetch/$s_!GZbt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic 848w, https://substackcdn.com/image/fetch/$s_!GZbt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic 1272w, https://substackcdn.com/image/fetch/$s_!GZbt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GZbt!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:149915,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/176729823?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GZbt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic 424w, https://substackcdn.com/image/fetch/$s_!GZbt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic 848w, https://substackcdn.com/image/fetch/$s_!GZbt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic 1272w, https://substackcdn.com/image/fetch/$s_!GZbt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c35ce0-783f-423e-85a8-c22b7a74b87c_1536x1024.heic 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><blockquote><p>Imagine keeping all your clothes in one drawer.<br>At first, it&#8217;s fine - a few shirts, some jeans.<br>But then seasons change, new clothes arrive, and suddenly the drawer won&#8217;t close.<br>You try to push harder. Nothing helps.</p><p>That&#8217;s what happens when a system grows - and your single database can no longer fit everything inside. It&#8217;s time to split the drawer.</p></blockquote><p>In tech terms, that overstuffed drawer is like a monolithic database. As your application grows, you eventually reach a point where no matter how much you try to squeeze in (or how powerful you make the server), <strong>one database just isn&#8217;t enough</strong>. Splitting a database can sound scary, but it&#8217;s a natural evolution when your data outgrows a single container. In this post, we&#8217;ll explore why and how databases get &#8220;<em>split</em>&#8221; through <strong>partitioning</strong>, <strong>sharding</strong>, and <strong>replication</strong> - demystifying these concepts so they&#8217;re not so fearsome after all.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BTor!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31ac6a07-c1df-4e1b-ae64-2c2da8e8c563_2161x1729.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BTor!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31ac6a07-c1df-4e1b-ae64-2c2da8e8c563_2161x1729.heic 424w, https://substackcdn.com/image/fetch/$s_!BTor!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31ac6a07-c1df-4e1b-ae64-2c2da8e8c563_2161x1729.heic 848w, https://substackcdn.com/image/fetch/$s_!BTor!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31ac6a07-c1df-4e1b-ae64-2c2da8e8c563_2161x1729.heic 1272w, https://substackcdn.com/image/fetch/$s_!BTor!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31ac6a07-c1df-4e1b-ae64-2c2da8e8c563_2161x1729.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BTor!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31ac6a07-c1df-4e1b-ae64-2c2da8e8c563_2161x1729.heic" width="725.484375" height="580.4871544471154" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/31ac6a07-c1df-4e1b-ae64-2c2da8e8c563_2161x1729.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:1165,&quot;width&quot;:1456,&quot;resizeWidth&quot;:725.484375,&quot;bytes&quot;:119567,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/176729823?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31ac6a07-c1df-4e1b-ae64-2c2da8e8c563_2161x1729.heic&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_!BTor!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31ac6a07-c1df-4e1b-ae64-2c2da8e8c563_2161x1729.heic 424w, https://substackcdn.com/image/fetch/$s_!BTor!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31ac6a07-c1df-4e1b-ae64-2c2da8e8c563_2161x1729.heic 848w, https://substackcdn.com/image/fetch/$s_!BTor!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31ac6a07-c1df-4e1b-ae64-2c2da8e8c563_2161x1729.heic 1272w, https://substackcdn.com/image/fetch/$s_!BTor!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31ac6a07-c1df-4e1b-ae64-2c2da8e8c563_2161x1729.heic 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><h1>&#129513; When one database isn&#8217;t enough</h1><p>At a small scale, a single database can handle all your application&#8217;s data and queries easily. But as the application (and its user base) grows, problems start to appear:</p><ul><li><p><strong>More users &#8594; more data:</strong> The database has to store more and more records, and operations on a single giant dataset inevitably slow down.</p></li></ul><ul><li><p><strong>More activity &#8594; more load:</strong> Heavier read/write traffic can overwhelm the database&#8217;s CPU, memory, or disk throughput.</p></li><li><p><strong>Maintenance overhead:</strong> Backups take longer, indexes grow larger, and routine queries or reports start dragging due to the sheer volume of data.</p></li></ul><p>You can try to <strong>scale vertically</strong> (add more CPU, RAM, or a faster disk to the database server) to get relief. This helps for a while, but only up to a point. Every system reaches a stage where adding hardware is like pouring water into a full glass - it just spills over. In other words, a single machine will eventually hit a physical limit, no matter how beefy it is.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> Beyond that, continuing to push growth on one database is impractical (or outright impossible) - it&#8217;s time to <strong>scale out</strong> by splitting.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qb4b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeab9475-9778-43a7-a937-ea52b5bb7d7f_2877x2171.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qb4b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeab9475-9778-43a7-a937-ea52b5bb7d7f_2877x2171.heic 424w, https://substackcdn.com/image/fetch/$s_!qb4b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeab9475-9778-43a7-a937-ea52b5bb7d7f_2877x2171.heic 848w, https://substackcdn.com/image/fetch/$s_!qb4b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeab9475-9778-43a7-a937-ea52b5bb7d7f_2877x2171.heic 1272w, https://substackcdn.com/image/fetch/$s_!qb4b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeab9475-9778-43a7-a937-ea52b5bb7d7f_2877x2171.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qb4b!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeab9475-9778-43a7-a937-ea52b5bb7d7f_2877x2171.heic" width="1200" height="905.7692307692307" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aeab9475-9778-43a7-a937-ea52b5bb7d7f_2877x2171.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:1099,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:128871,&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://iam.slys.dev/i/176729823?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeab9475-9778-43a7-a937-ea52b5bb7d7f_2877x2171.heic&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_!qb4b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeab9475-9778-43a7-a937-ea52b5bb7d7f_2877x2171.heic 424w, https://substackcdn.com/image/fetch/$s_!qb4b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeab9475-9778-43a7-a937-ea52b5bb7d7f_2877x2171.heic 848w, https://substackcdn.com/image/fetch/$s_!qb4b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeab9475-9778-43a7-a937-ea52b5bb7d7f_2877x2171.heic 1272w, https://substackcdn.com/image/fetch/$s_!qb4b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faeab9475-9778-43a7-a937-ea52b5bb7d7f_2877x2171.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><h1>&#9881;&#65039; Three survival strategies &#8212; partitioning, sharding, and replication</h1><p>So, how do we &#8220;<em>split the drawer</em>&#8221; in practice? There are three fundamental strategies (often used together) to divide and conquer a growing data set. The table below gives a quick overview:</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/2wxZR/2/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b6b850e7-cf23-4bc2-9797-6818093646a1_1220x558.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/54da5c71-d8e2-42f9-a51c-7fc33e435446_1220x628.png&quot;,&quot;height&quot;:240,&quot;title&quot;:&quot;Three survival strategies&quot;,&quot;description&quot;:&quot;&quot;,&quot;belowTheFold&quot;:true}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/2wxZR/2/" width="730" height="240" frameborder="0" scrolling="no" loading="lazy"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p><em>Partitioning organizes. Sharding scales. Replication protects.</em> Each addresses a different problem of scale. Let&#8217;s dive into each one and see how they work.</p><div><hr></div><p>&#128257; <strong>This is usually where things start to click.</strong></p><p>If this made databases feel less scary, it might help someone else too.  </p><p>Share this article with someone who&#8217;s afraid of scaling.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/why-databases-get-split-sharding?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/why-databases-get-split-sharding?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h1>&#129518; Partitioning &#8212; organizing data so it makes sense</h1><p><strong>Partitioning</strong> means dividing one big dataset into smaller, more manageable chunks. Instead of one monolithic table or collection containing everything, you break it into pieces that are easier to work with. The goal is to group related data together and separate unrelated data, which improves clarity and performance.</p><p>There are two common forms of partitioning.</p><p><strong>Vertical partitioning.</strong> Splitting data by category or feature. For example, an e-commerce application might keep user profiles in one set of tables and order history in another. In relational terms, this could mean putting different functional domains in different tables or databases (or breaking a very wide table into multiple tables by columns). Each vertical partition holds a subset of the columns/fields. Frequently accessed fields might live in one partition, while rarely used or very sensitive fields live in another. This way, each partition is focused on a specific subset of the data (often reflecting a particular feature or usage pattern).</p><p><strong>Horizontal partitioning. </strong>Splitting data by rows, usually based on some key or value range. For example, you could partition a customer table so that customers with last names A&#8211;M are in one partition and N&#8211;Z in another, or split a huge log table by month. Each horizontal partition contains a subset of the rows but has the same schema (columns) as the others. Often, horizontal partitioning is implemented as &#8220;<em>ranges</em>&#8221; (e.g. IDs 1-1,000,000 in one partition, 1,000,001-2,000,000 in the next) or lists (explicit groups, like region codes or alphabetical ranges).</p><p><strong>Why partition at all?</strong> Because each partition is smaller than the whole dataset, queries and index lookups can be faster - they only scan a relevant subset of data rather than everything. For example, if you partition a log table by date, a query for &#8220;<em>last month&#8217;s logs</em>&#8221; only needs to read the partition for that month, not the entire year&#8217;s data.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> Smaller chunks of data also mean <strong>smaller indexes</strong> and working sets, which can fit into memory more easily and speed up performance. Partitioning can even help with caching: if one subset of data is &#8220;<em>hot</em>&#8221; (frequently accessed) and others are &#8220;<em>cold</em>&#8221;, you could keep the hot partition in memory or on a fast disk, and the cold partitions on cheaper storage. For instance, stable, rarely-changing data (say, product descriptions) might be stored in one partition that you cache aggressively, while dynamic data (stock levels) is in another partition that gets updated regularly.</p><p>Another benefit is manageability: it&#8217;s easier to <strong>maintain and back up</strong> a 100 GB partition than a single 1 TB monolith. You can backup or restore one segment at a time, or even take one partition offline without affecting others, etc. Partitioning can also improve availability by isolating faults - if one partition (on one server) crashes, the others can still function, so it&#8217;s not a total outage.</p><p>In short, partitioning is like organizing your wardrobe. Instead of one chaotic closet where everything&#8217;s piled up, you have separate sections: shirts here, jackets there, pants in another. Each section is easier to search through on its own. Likewise, a partitioned database is <strong>neater and more efficient</strong>: related data stays together, and queries can target just the right &#8220;<em>section</em>&#8221; instead of rifling through everything.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lgUy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45dca50a-98f5-484c-92ef-fae34f1d8dba_1806x1056.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lgUy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45dca50a-98f5-484c-92ef-fae34f1d8dba_1806x1056.heic 424w, https://substackcdn.com/image/fetch/$s_!lgUy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45dca50a-98f5-484c-92ef-fae34f1d8dba_1806x1056.heic 848w, https://substackcdn.com/image/fetch/$s_!lgUy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45dca50a-98f5-484c-92ef-fae34f1d8dba_1806x1056.heic 1272w, https://substackcdn.com/image/fetch/$s_!lgUy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45dca50a-98f5-484c-92ef-fae34f1d8dba_1806x1056.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lgUy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45dca50a-98f5-484c-92ef-fae34f1d8dba_1806x1056.heic" width="1456" height="851" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/45dca50a-98f5-484c-92ef-fae34f1d8dba_1806x1056.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:851,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:72450,&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://iam.slys.dev/i/176729823?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45dca50a-98f5-484c-92ef-fae34f1d8dba_1806x1056.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_!lgUy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45dca50a-98f5-484c-92ef-fae34f1d8dba_1806x1056.heic 424w, https://substackcdn.com/image/fetch/$s_!lgUy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45dca50a-98f5-484c-92ef-fae34f1d8dba_1806x1056.heic 848w, https://substackcdn.com/image/fetch/$s_!lgUy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45dca50a-98f5-484c-92ef-fae34f1d8dba_1806x1056.heic 1272w, https://substackcdn.com/image/fetch/$s_!lgUy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45dca50a-98f5-484c-92ef-fae34f1d8dba_1806x1056.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><h1>&#127757; Sharding &#8212; when you need more than one database</h1><p>While partitioning often refers to splitting data within a single database system, <strong>sharding</strong> takes it one step further: it spreads the data across <em>multiple</em> database servers. In fact, sharding is basically horizontal partitioning, but done <strong>across servers or instances</strong>, not just within one. Each shard is an independent database holding a slice of the overall data, and all shards share the same schema structure (they have the same tables/collections, just different rows).</p><p>Sharding is a classic approach to <strong>horizontal scaling</strong>. Instead of pushing the limits of one big machine, you use several machines in parallel - each handling only a portion of the workload. For example, imagine a service with millions of users. Rather than keeping all user records in one database server, you could split users between two shards: say, User IDs 1 - 1,000,000 on <strong>Shard A</strong> (Server A) and User IDs 1,000,001 - 2,000,000 on <strong>Shard B</strong> (Server B). If a user with ID 500,000 logs in, the application knows to check Shard A; if ID 1,500,000, it goes to Shard B. The key that determines which shard to go to (here, the user ID) is called the <strong>shard key</strong>. The application (or a routing service) uses the shard key to direct each query to the right shard.</p><p>Sharding can also be based on other schemes. For instance, a social media platform might shard by geography: users from North America on one database instance, Europe on another, Asia on a third, etc. This way each shard handles a region&#8217;s users, potentially reducing latency by keeping data geographically close to its users. The important point is that <strong>each shard contains a distinct subset of the data</strong> and collectively the shards make up the entire dataset.</p><p>How do we decide <em>who gets what</em> in a sharded setup? There are a few common sharding strategies.</p><p><strong>Range-based sharding.</strong> Each shard owns a continuous range of data values. For example, shard 1 has IDs from 1-1M, shard 2 has 1M-2M, etc., or one shard has &#8220;<em>A-G</em>&#8221; customers, the next has &#8220;<em>H-N</em>,&#8221; and so on. This approach is easy to understand and to query when you know a specific value&#8217;s range (e.g. to find all &#8220;<em>A-G</em>&#8221; customers, you only hit that one shard). However, range sharding can lead to <strong>uneven load</strong> if the data isn&#8217;t uniform. In the alphabet example, one shard might end up with a ton of data (if many last names start with S, for instance) and become a hot spot. Using the first letter of a name would cause an unbalanced distribution because some letters are far more common than others. In such cases, the shard holding &#8220;<em>S</em>&#8221; or &#8220;<em>M</em>&#8221; might struggle while others sit mostly idle.</p><p><strong>Hash-based sharding.</strong> To avoid the imbalance of simple ranges, many systems use a hash function on the key. The hash (perhaps modded into a number of buckets) determines which shard the data goes to. This tends to distribute data evenly in a pseudo-random way (so &#8220;<em>Alice</em>&#8221; might go to Shard 1 and &#8220;<em>Zelda</em>&#8221; to Shard 3, regardless of alphabetical order, if their hashed IDs differ). The big advantage is avoiding any one very heavy shard - no obvious pattern means load is spread out. The trade-off is that <strong>related items might not live together</strong>, and queries that need a range of data (say, all users in California, or IDs between 1000 and 2000) won&#8217;t be neatly contained in one shard. Cross-shard queries become more complex, since you might have to ask every shard and then combine results. In other words, hash-based sharding sacrifices some query flexibility for balance.</p><p><strong>Geographic or domain-based sharding.</strong> This strategy uses a natural segmentation of your user base or data domain. For example, you shard by region (as mentioned) or by customer type (free vs. premium customers on different shards), etc. The benefit is often lower latency and localized failures - each shard can be placed physically closer to its users and handle their specific load. It can also simplify compliance (e.g., European user data stays on an EU server). However, if one category has a lot more users or activity than another, you can still end up with an imbalanced situation. You also have to deal with <em>global</em> queries (aggregating data from all shards) in application logic, since the database no longer has everything in one place.</p><p>Sharding is like opening new branches of a library. Imagine a single central library that&#8217;s become too crowded - people wait in long lines, it&#8217;s slow to find books, and it&#8217;s running out of space. The solution: open one branch on the north side of town and another on the south side. Now each branch (shard) serves its local neighborhood, storing copies of books relevant to that area or portion of the catalog, but no one branch has to hold <em>all</em> the books. If you know which branch has the book you need, you go straight there. Similarly, with database sharding, the application routes each query to the correct shard that holds that piece of data, instead of every query hitting one monolithic database.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pGPd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe21ff3c9-7ada-4d1e-a9fb-212ad1fb84fc_2453x1241.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pGPd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe21ff3c9-7ada-4d1e-a9fb-212ad1fb84fc_2453x1241.heic 424w, https://substackcdn.com/image/fetch/$s_!pGPd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe21ff3c9-7ada-4d1e-a9fb-212ad1fb84fc_2453x1241.heic 848w, https://substackcdn.com/image/fetch/$s_!pGPd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe21ff3c9-7ada-4d1e-a9fb-212ad1fb84fc_2453x1241.heic 1272w, https://substackcdn.com/image/fetch/$s_!pGPd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe21ff3c9-7ada-4d1e-a9fb-212ad1fb84fc_2453x1241.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pGPd!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe21ff3c9-7ada-4d1e-a9fb-212ad1fb84fc_2453x1241.heic" width="1200" height="607.4175824175824" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e21ff3c9-7ada-4d1e-a9fb-212ad1fb84fc_2453x1241.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:737,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:87686,&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://iam.slys.dev/i/176729823?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe21ff3c9-7ada-4d1e-a9fb-212ad1fb84fc_2453x1241.heic&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_!pGPd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe21ff3c9-7ada-4d1e-a9fb-212ad1fb84fc_2453x1241.heic 424w, https://substackcdn.com/image/fetch/$s_!pGPd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe21ff3c9-7ada-4d1e-a9fb-212ad1fb84fc_2453x1241.heic 848w, https://substackcdn.com/image/fetch/$s_!pGPd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe21ff3c9-7ada-4d1e-a9fb-212ad1fb84fc_2453x1241.heic 1272w, https://substackcdn.com/image/fetch/$s_!pGPd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe21ff3c9-7ada-4d1e-a9fb-212ad1fb84fc_2453x1241.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><blockquote><p><strong>Insight:</strong> By scaling horizontally with shards, we <strong>trade simplicity for capacity and resilience</strong>. A sharded system is more complex to manage than a single database (you have multiple moving parts and distributed logic), but it can handle vastly more data and traffic. There&#8217;s no single monster server carrying the entire load, and if one shard server goes down, it only affects a subset of users/data rather than bringing everything down. In short, <em>sharding sacrifices the convenience of one-stop querying in order to gain virtually unlimited scalability</em>.</p></blockquote><h1>&#128257; Replication &#8212; when safety matters more than speed</h1><p>Partitioning and sharding split <em>different</em> data across spaces - but <strong>replication</strong> is about duplicating the <em>same</em> data across multiple places. When you replicate a database, you make one or more copies of it on separate nodes. These copies stay synchronized (often with a small delay) by copying over every write/update. Why do this? <strong>Redundancy</strong>. If one database instance crashes, or if you simply need extra read capacity, having replicas of the data keeps the system running without interruption.</p><p>In formal terms, <em>database replication</em> is the process of copying and maintaining database objects (like tables, records, whole states) across multiple nodes to ensure <strong>data redundancy, durability, availability, and often better performance</strong>.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> Instead of a single point of failure, you have backups that can step in, and instead of one server handling all reads, you can spread reads across replicas. There are a couple of common replication setups.</p><p><strong>Master&#8211;slave (primary&#8211;replica) replication.</strong> In this classic model, one node is designated the <strong>primary</strong> (master) server, which handles all the writes. One or more other nodes act as <strong>read replicas</strong> (slaves) that continuously copy the primary&#8217;s data changes. All writes go to the master, which ensures there&#8217;s a single source of truth for updates (no update conflicts). The replicas subscribe to the master&#8217;s change log and apply those changes to stay up-to-date. Reads, however, can be distributed: your application can query the replicas for read-only operations, thereby offloading work from the primary.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a> For example, a popular website might use one primary database for handling user transactions, and 5 replica databases to serve various read-heavy features (like search or analytics queries) in parallel. This <strong>improves read scalability</strong> without compromising write consistency (since writes all still funnel through the single master). If the master server fails, one of the replicas can be promoted to master - this is a failover mechanism that provides high availability. The drawback to master-slave replication is that the master can still be a bottleneck for write-heavy workloads (all writes have to go to one place) and a single point of failure for writes (during normal operation). Also, replicas are usually slightly behind the master in time - if replication is asynchronous, a recent write might not yet have reached the replica when a read is done, meaning replicas can serve <strong>stale data</strong> (more on that trade-off shortly).</p><p><strong>Multi-master replication.</strong> This is a more decentralized approach where <strong>multiple nodes can accept writes</strong> and replicate to each other. In a multi-master setup, you could have, say, two or three primary nodes (perhaps in different data centers) all processing user updates, and they exchange their changes with one another to converge on the same data state. The big advantage is there&#8217;s no single choke point for writing &#8211; you can handle writes in parallel at multiple locations, and if one node fails, the others are still live (improving availability). However, multi-master replication is <strong>considerably more complex</strong>. If two masters change the same piece of data at the same time (a write conflict), the system needs a conflict resolution strategy (last write wins? merge changes? custom logic?). Ensuring consistency is tricky - you often end up with <strong>eventual consistency</strong> (updates will reach all nodes, but for a short time different nodes might have different data). Multi-master systems must deal with issues like write conflicts, network partitions, and heavier coordination overhead. This approach is used in certain distributed databases and multi-region deployments (so each region can accept local writes), but it requires careful design.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xkoj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F392819f1-65f2-4637-9bbb-fb8b9d42210c_2129x932.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xkoj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F392819f1-65f2-4637-9bbb-fb8b9d42210c_2129x932.heic 424w, https://substackcdn.com/image/fetch/$s_!xkoj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F392819f1-65f2-4637-9bbb-fb8b9d42210c_2129x932.heic 848w, https://substackcdn.com/image/fetch/$s_!xkoj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F392819f1-65f2-4637-9bbb-fb8b9d42210c_2129x932.heic 1272w, https://substackcdn.com/image/fetch/$s_!xkoj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F392819f1-65f2-4637-9bbb-fb8b9d42210c_2129x932.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xkoj!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F392819f1-65f2-4637-9bbb-fb8b9d42210c_2129x932.heic" width="1200" height="525" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/392819f1-65f2-4637-9bbb-fb8b9d42210c_2129x932.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:637,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:86336,&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://iam.slys.dev/i/176729823?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F392819f1-65f2-4637-9bbb-fb8b9d42210c_2129x932.heic&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_!xkoj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F392819f1-65f2-4637-9bbb-fb8b9d42210c_2129x932.heic 424w, https://substackcdn.com/image/fetch/$s_!xkoj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F392819f1-65f2-4637-9bbb-fb8b9d42210c_2129x932.heic 848w, https://substackcdn.com/image/fetch/$s_!xkoj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F392819f1-65f2-4637-9bbb-fb8b9d42210c_2129x932.heic 1272w, https://substackcdn.com/image/fetch/$s_!xkoj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F392819f1-65f2-4637-9bbb-fb8b9d42210c_2129x932.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>What do we achieve with replication? Primarily:</p><ul><li><p><strong>High availability and fault tolerance:</strong> If one database node crashes or becomes unreachable, the system can automatically fail over to a replica which has the same data. The app continues with minimal interruption. By keeping copies in multiple geographic locations, you can survive regional outages as well - one data center&#8217;s issue won&#8217;t take down the entire service. For example, many cloud databases keep a primary in one zone and a replica in another, so that even a data center outage doesn&#8217;t cause data loss or downtime.</p></li><li><p><strong>Read scaling and performance:</strong> By having multiple copies of the data, you can spread read queries among them. As mentioned, a primary-replica setup lets you add read capacity easily - 10 replicas can handle 10&#215; the read traffic of a single server (roughly). Also, if you have users around the world, you might place replicas in different regions. Then a user in Europe can read from a database copy located in Europe, reducing latency, while users in America hit the US replica. Each sees faster response times because the data is geographically closer. </p></li></ul><blockquote><p><strong>Note:</strong> Writes are not scaled in the primary-replica model (all go to the primary), but some distributed databases offer multi-leader or sharded-writes to spread write load too.</p></blockquote><ul><li><p><strong>Disaster recovery and backups:</strong> Replication can be used to maintain a live backup of your database. For instance, you might have your primary in one cloud provider and a replica in another provider or region. If a catastrophe hits one, the other still has your data. Replicas can also be used to run backups or heavy analytical queries without impacting the primary&#8217;s performance. Essentially, replication provides peace of mind that one failure won&#8217;t destroy your only copy of data.</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_!jCI5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962be111-bde6-4c94-a467-62e80b0e451e_1629x1169.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jCI5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962be111-bde6-4c94-a467-62e80b0e451e_1629x1169.heic 424w, https://substackcdn.com/image/fetch/$s_!jCI5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962be111-bde6-4c94-a467-62e80b0e451e_1629x1169.heic 848w, https://substackcdn.com/image/fetch/$s_!jCI5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962be111-bde6-4c94-a467-62e80b0e451e_1629x1169.heic 1272w, https://substackcdn.com/image/fetch/$s_!jCI5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962be111-bde6-4c94-a467-62e80b0e451e_1629x1169.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jCI5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962be111-bde6-4c94-a467-62e80b0e451e_1629x1169.heic" width="1456" height="1045" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/962be111-bde6-4c94-a467-62e80b0e451e_1629x1169.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1045,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:90742,&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://iam.slys.dev/i/176729823?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962be111-bde6-4c94-a467-62e80b0e451e_1629x1169.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_!jCI5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962be111-bde6-4c94-a467-62e80b0e451e_1629x1169.heic 424w, https://substackcdn.com/image/fetch/$s_!jCI5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962be111-bde6-4c94-a467-62e80b0e451e_1629x1169.heic 848w, https://substackcdn.com/image/fetch/$s_!jCI5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962be111-bde6-4c94-a467-62e80b0e451e_1629x1169.heic 1272w, https://substackcdn.com/image/fetch/$s_!jCI5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962be111-bde6-4c94-a467-62e80b0e451e_1629x1169.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>If partitioning was like organizing your closet, and sharding like opening new library branches, <strong>replication is like making photocopies of important documents and storing them in multiple safes</strong>. If one safe (or one house) burns down, your documents aren&#8217;t lost - a copy exists somewhere else. It&#8217;s all about protecting data by redundancy. You pay a cost in complexity (keeping copies in sync) and sometimes in write latency (especially if using synchronous replication), but you gain a lot in terms of safety.</p><p>One important aspect to note is <strong>consistency</strong>: depending on how replication is configured, there may be a lag between a write on the primary and that write appearing on a replica. In asynchronous replication, the primary doesn&#8217;t wait for replicas to catch up - it sends the update and moves on - so a replica might be a few seconds (or more) behind. This is known as <em>replication lag</em>, and it means if you read from a replica right after writing to the primary, you might not see the latest data.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a> Many systems solve this by directing freshly updated users to the primary (read-your-writes consistency), or by using <strong>synchronous replication</strong> (the primary waits for replicas to confirm writes, which ensures strong consistency at the cost of higher latency). As an application designer, you get to choose the trade-off: immediate consistency vs. higher performance. In any case, replication gives you the ability to <strong>recover</strong> or <strong>scale reads</strong> in ways a single database cannot.</p><h1>&#129504; How these concepts work together</h1><pre><code><code>Partitioned tables &#8594; Sharded across regions &#8594; Replicated for redundancy</code></code></pre><p>In real-world architectures, partitioning, sharding, and replication are not mutually exclusive - they&#8217;re <strong>often used together</strong> in layers to handle different aspects of scale. For example, you might first partition a very large table into subtables by category or date, and then <strong>shard</strong> those partitions across multiple servers (perhaps by region or hash of a key), and finally <strong>replicate</strong> each shard to have a standby copy. A concrete scenario could look like this:</p><ul><li><p>You partition your database by function - user data vs. product data vs. logs (organizing by domain).</p></li><li><p>Within the user data, you shard by region, so EU users are handled by a different server cluster than US users, for performance and regulatory reasons.</p></li><li><p>Each of those regional shards is replicated to multiple nodes, so it remains available if one node fails.</p></li></ul><p>These strategies can be combined in many ways. In fact, architects <strong>recommend considering all three</strong> when designing a scalable data infrastructure. It&#8217;s common, for instance, to <strong>shard data then vertically partition within each shard</strong> for manageability. And almost any sharded system will employ replication as well, because more machines mean more points of failure that you have to plan for.</p><p>Think of partitioning, sharding, and replication as complementary tools:</p><ul><li><p>Partitioning makes individual <strong>databases</strong> more efficient by keeping each piece lean and focused.</p></li><li><p>Sharding allows you to <strong>add more machines</strong> to handle load by spreading the data horizontally.</p></li><li><p>Replication makes the system <strong>resilient</strong> by providing backups and distributed access.</p></li></ul><p>Together, these techniques let modern databases scale <strong>without a single node carrying all the burden</strong>. A system like Apache Cassandra, for example, automatically partitions data across many nodes and also replicates each piece of data to multiple nodes<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a> - it&#8217;s sharding and replication combined, yielding a distributed store with no single point of failure. Similarly, cloud databases like Google Cloud Spanner or Amazon DynamoDB partition data and keep replicas across availability zones. Even a traditional SQL database like PostgreSQL can be manually sharded and set up with replication.</p><p>The key takeaway is that <strong>real scalability is achieved by blending these approaches</strong>. Each one addresses different challenges of growth (performance, capacity, availability), and together they ensure that as your application scales to millions of users, it remains fast and fault-tolerant. Modern architectures embrace this: your data gets <em>organized, distributed, and duplicated</em> in clever ways so that no one machine is a bottleneck or a single point of failure. It&#8217;s truly a dance of <strong>speed, scale, and safety</strong> working in harmony.</p><div><hr></div><p><strong>&#128236; If this helped you see the big picture&#8230;</strong>  </p><p>I write about software architecture, systems that scale, and how to reason about complexity without panic.  </p><p><strong>Subscribe if you want more posts like this - explained calmly and from first principles.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>&#9878;&#65039; The hidden costs &#8212; when splitting gets hard</h1><p>Before you enthusiastically break your database into 100 pieces, it&#8217;s important to acknowledge the trade-offs. <strong>Splitting a database (through any combination of partitioning, sharding, and replication) introduces complexity</strong>. Here are some of the hidden costs and challenges to be aware of:</p><ul><li><p><strong>Complex queries become tricky:</strong> When data is all in one place, you can join tables and run analytical queries easily. In a sharded or heavily partitioned system, a query that needs data from multiple shards/partitions is much harder. For example, joining a user profile (in shard A) with that user&#8217;s order history (in shard B) might require your application to query both shards and merge the results manually. Cross-shard joins or aggregations are either slow, require additional tooling, or are sometimes impossible. Simply put, distributing data <strong>adds complexity to querying</strong> - developers need to write more logic, or use middleware, to gather data from multiple sources. This is a big reason why sharding is often delayed until absolutely necessary.</p></li><li><p><strong>Operational overhead:</strong> Running one database is hard enough; running say 10 shards * 3 replicas each = 30 databases can be a headache. More servers mean more things that can go wrong. Routine tasks like backups, restores, schema migrations, or software upgrades have to be performed on each partition/shard, often in a coordinated way. If you change a table schema, you now have to update it across all shards consistently. If you want to back up data, you might have to script backups for dozens of servers. There&#8217;s also a maintenance cost in monitoring - keeping an eye on load and health across all the pieces. This increased administrative burden can lead to errors if not managed carefully. In short, a distributed system is <em>harder to manage</em> than a single-node system.</p></li><li><p><strong>Consistency trade-offs:</strong> As discussed, replication (especially asynchronous) can lead to <strong>eventual consistency</strong> issues - where reads might temporarily see old data until the replicas catch up. Likewise, in multi-master setups, you might get conflicting updates that need resolution. Even in sharded systems, maintaining global constraints or consistency can be challenging. For instance, ensuring a unique username across all shards means each new username might need to be checked against all shards - which is slow - or you accept that duplicates might happen in different shards. Distributed systems often have to choose between strong consistency and high availability (per the <strong>CAP theorem</strong>). So, splitting data may force you to deal with moments of inconsistency or more complex consistency mechanisms.</p></li><li><p><strong>Balancing and hot spots:</strong> Deciding how to split data is tricky; if you get it wrong, you might end up with an uneven distribution. One shard could become a <strong>hot spot</strong> - handling a disproportionate amount of traffic or storing much more data than others. For example, if an online game shards players by the first letter of their username, and it turns out most players choose names starting with &#8220;<em>S</em>&#8221;, then the &#8220;<em>S</em>&#8221; shard will lag under load. We mentioned range sharding has this risk. Mitigating it might involve re-sharding (splitting a shard into two, or moving some data to another shard) which is a non-trivial operation that can require downtime or careful data migration. Similarly, with partitioning, if one partition grows much faster, you might need to redefine the partition boundaries. <strong>Maintaining an even balance</strong> as data and access patterns change is an ongoing challenge. It often requires monitoring and possibly auto-scaling or re-partitioning mechanisms to avoid hot spots.</p></li></ul><p>It&#8217;s easy to split the wardrobe; it can be harder to find your favorite shirt afterwards. In other words, distributing data solves a lot of scaling issues, but it also means <strong>you&#8217;ve shifted complexity from hardware to software</strong>. You&#8217;ll need smart logic to query, integrity checks across shards, robust monitoring, and an ops team ready to handle failures in a distributed environment. These are by no means deal-breakers - every large-scale system deals with them - but they are the &#8220;<em>cost of doing business</em>&#8221; for scaling out.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XXrI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa71b58b4-3674-426a-a075-2dd0b4a8733e_2845x1669.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XXrI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa71b58b4-3674-426a-a075-2dd0b4a8733e_2845x1669.heic 424w, https://substackcdn.com/image/fetch/$s_!XXrI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa71b58b4-3674-426a-a075-2dd0b4a8733e_2845x1669.heic 848w, https://substackcdn.com/image/fetch/$s_!XXrI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa71b58b4-3674-426a-a075-2dd0b4a8733e_2845x1669.heic 1272w, https://substackcdn.com/image/fetch/$s_!XXrI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa71b58b4-3674-426a-a075-2dd0b4a8733e_2845x1669.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XXrI!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa71b58b4-3674-426a-a075-2dd0b4a8733e_2845x1669.heic" width="1200" height="703.8461538461538" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a71b58b4-3674-426a-a075-2dd0b4a8733e_2845x1669.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:854,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:161503,&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://iam.slys.dev/i/176729823?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa71b58b4-3674-426a-a075-2dd0b4a8733e_2845x1669.heic&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_!XXrI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa71b58b4-3674-426a-a075-2dd0b4a8733e_2845x1669.heic 424w, https://substackcdn.com/image/fetch/$s_!XXrI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa71b58b4-3674-426a-a075-2dd0b4a8733e_2845x1669.heic 848w, https://substackcdn.com/image/fetch/$s_!XXrI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa71b58b4-3674-426a-a075-2dd0b4a8733e_2845x1669.heic 1272w, https://substackcdn.com/image/fetch/$s_!XXrI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa71b58b4-3674-426a-a075-2dd0b4a8733e_2845x1669.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><h1>&#128172; The philosophy of scale</h1><blockquote><p>Splitting a database isn&#8217;t a failure - it&#8217;s evolution.</p><p>Growth always brings complexity, and architecture is about choosing where to place it.</p><p>The best systems don&#8217;t avoid scale - they embrace it by dividing their load wisely, ensuring no single node carries the world alone.</p></blockquote><p>In the end, moving to partitioning, sharding, or replication is a sign of success: it means your application grew to the point that a simple approach no longer sufficed. Rather than fearing this complexity, we manage it. Good engineering is not about preventing complexity (which is impossible at large scale), but about <strong>organizing complexity</strong> in a way that is maintainable and robust. By splitting a big problem (one giant overloaded database) into smaller ones (several well-organized, cooperative data stores), we make the system as a whole more scalable and reliable. No single database carries the entire burden, and that&#8217;s a very good thing for longevity.</p><div><hr></div><p><strong>&#128172; Every system reaches this point eventually.</strong></p><p>Where have you seen things break first - data size, traffic, or reliability?</p><p>Share your experience in the comments.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/why-databases-get-split-sharding/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/why-databases-get-split-sharding/comments"><span>Leave a comment</span></a></p><div><hr></div><h1>&#9997;&#65039; Recap</h1><p>One database can&#8217;t scale forever - at a certain point, splitting your data becomes inevitable for growth.</p><ul><li><p><strong>Partitioning</strong> = organizing a single dataset into smaller segments (by table, column, or row subsets) for clarity and performance benefits (faster queries on smaller chunks, easier maintenance).</p></li><li><p><strong>Sharding</strong> = distributing data across multiple <em>servers/databases</em> for horizontal scalability. Each shard holds a portion of the data, allowing the overall system to handle more load by adding machines.</p></li><li><p><strong>Replication</strong> = duplicating data onto multiple nodes for safety and high availability. If one node fails, a replica can serve the data; plus, read traffic can be spread out to improve throughput.</p></li></ul><p>Real-world systems often <strong>combine all three</strong> - partitioning for manageability, sharding for scale, and replication for resilience &#8211; achieving a balance of performance and redundancy. Embracing these patterns is how we evolve a simple system into one that can handle internet-scale demands without fear.</p><div><hr></div><p><strong>&#129309; Architecture gets easier when we learn it together.</strong></p><p>Refer this article to a teammate, or friend who&#8217;s learning backend  or system design fundamentals.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/leaderboard?&amp;utm_source=post&quot;,&quot;text&quot;:&quot;Refer a friend&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/leaderboard?&amp;utm_source=post"><span>Refer a friend</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p><a href="https://learn.microsoft.com/en-us/azure/architecture/best-practices/data-partitioning">Data partitioning guidance</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p><a href="https://planetscale.com/blog/sharding-vs-partitioning-whats-the-difference">Sharding vs. partitioning: What&#8217;s the difference?</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p><a href="https://aerospike.com/blog/what-is-database-replication">What is database replication, and why is it important?</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p><a href="https://bugfree.ai/knowledge-hub/master-slave-vs-multi-master-replication">Master-Slave vs Multi-Master Replication</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p><a href="https://medium.com/@nyshnt/understanding-database-replication-ensuring-consistency-and-performance-in-large-scale-3c2bdf1d25a4">Understanding Database Replication: Ensuring Consistency and Performance in Large-Scale Applications</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p><a href="https://docs.datastax.com/en/cassandra-oss/3.x/cassandra/architecture/archDataDistributeAbout.html">Data distribution and replication</a></p></div></div>]]></content:encoded></item><item><title><![CDATA[What it means for a system to be consistent — and why it does not always have to be]]></title><description><![CDATA[Systems Literacy]]></description><link>https://iam.slys.dev/p/what-it-means-for-a-system-to-be</link><guid isPermaLink="false">https://iam.slys.dev/p/what-it-means-for-a-system-to-be</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Mon, 01 Dec 2025 21:22:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ge6h!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ge6h!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ge6h!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ge6h!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ge6h!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ge6h!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ge6h!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2644937,&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://iam.slys.dev/i/176729656?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ge6h!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ge6h!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ge6h!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ge6h!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65940e46-184f-412d-b4d3-3ae58ab101d8_1536x1024.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><blockquote><p>Have you ever sent a bank transfer and noticed it didn&#8217;t appear right away?<br>Or added something to your online cart, only to see it disappear after refreshing the page?</p><p>Nothing&#8217;s broken.<br>That&#8217;s just how distributed systems work - in a world where <strong>truth travels in pieces.</strong></p></blockquote><p>In moments like these, it may feel like the digital world is <em>out of sync</em>. But in reality, nothing is malfunctioning at all &#8211; it&#8217;s simply the nature of distributed systems. Data and truth don&#8217;t arrive everywhere <strong>instantly</strong>; they travel in pieces, catching up from one server to another. This means different parts of a system might briefly disagree on what &#8220;<em>truth</em>&#8221; is.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-88p!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F182e5eeb-b449-4b20-a393-99425a3a2b37_1741x877.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-88p!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F182e5eeb-b449-4b20-a393-99425a3a2b37_1741x877.png 424w, https://substackcdn.com/image/fetch/$s_!-88p!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F182e5eeb-b449-4b20-a393-99425a3a2b37_1741x877.png 848w, https://substackcdn.com/image/fetch/$s_!-88p!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F182e5eeb-b449-4b20-a393-99425a3a2b37_1741x877.png 1272w, https://substackcdn.com/image/fetch/$s_!-88p!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F182e5eeb-b449-4b20-a393-99425a3a2b37_1741x877.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-88p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F182e5eeb-b449-4b20-a393-99425a3a2b37_1741x877.png" width="1456" height="733" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/182e5eeb-b449-4b20-a393-99425a3a2b37_1741x877.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:733,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:134222,&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://iam.slys.dev/i/176729656?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F182e5eeb-b449-4b20-a393-99425a3a2b37_1741x877.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_!-88p!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F182e5eeb-b449-4b20-a393-99425a3a2b37_1741x877.png 424w, https://substackcdn.com/image/fetch/$s_!-88p!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F182e5eeb-b449-4b20-a393-99425a3a2b37_1741x877.png 848w, https://substackcdn.com/image/fetch/$s_!-88p!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F182e5eeb-b449-4b20-a393-99425a3a2b37_1741x877.png 1272w, https://substackcdn.com/image/fetch/$s_!-88p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F182e5eeb-b449-4b20-a393-99425a3a2b37_1741x877.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>Instead of being a binary attribute (consistent vs. inconsistent), <strong>consistency</strong> in distributed computing is more of a sliding scale &#8211; a balance between accuracy, speed, and the realities of networked life. Understanding this balance is key to designing systems that are both reliable and responsive.</p><div><hr></div><p>&#128161; <strong>Like this kind of thinking?</strong></p><p>I write weekly about distributed systems, architecture, and developer-friendly design - all in plain English. </p><p>&#128236; <strong>Subscribe here</strong> to get the next one straight to your inbox.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>&#129513; What &#8220;consistency&#8221; really means</h1><p>When engineers talk about a system being &#8220;<em>consistent</em>&#8221;, they mean that everyone (or every part of the system) sees the <strong>same data at the same time</strong>. In an ideal world, the moment something changes &#8211; like you adding an item to your cart or transferring money &#8211; <strong>all</strong> parts of the system would immediately reflect that change. Your friends in a group chat would all see the latest message simultaneously, no matter where they are.</p><p>In practice, especially in distributed systems spread across multiple servers or regions, this ideal is hard to achieve. Data often needs time to &#8220;<em>catch up</em>&#8221; between nodes. One server might record an update a few seconds (or milliseconds) before another. Imagine a group of friends trying to plan dinner over text messages. Some reply instantly, others take an hour. Only after some time does everyone finally agree on the same plan. That&#8217;s a pretty good metaphor for <strong>eventual consistency</strong> &#8211; the idea that a system&#8217;s parts will <strong>eventually</strong> agree on the truth, even if they&#8217;re temporarily out of sync.</p><p>Crucially, consistency isn&#8217;t all-or-nothing. It&#8217;s tempting to think a system is either perfectly consistent or completely broken, but there&#8217;s a lot of gray area in between. We often <strong>trade off</strong> a bit of immediate consistency to gain other benefits (like performance or uptime). The next sections will explore why those trade-offs exist and how modern systems manage them.</p><h1>&#9881;&#65039; The CAP theorem &#8212; three letters that changed everything</h1><p>In the early 2000s, computer scientist <em>Eric Brewer</em> proposed something known as the <strong>CAP theorem<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></strong>. This theorem frames a fundamental tension in distributed systems using three properties:</p><ul><li><p><strong>C - Consistency:</strong> All nodes see the same data at the same time. After you write data, any read from any node returns that new data.</p></li><li><p><strong>A &#8211; Availability:</strong> The system always responds to requests. Every request gets a response (even if some nodes are down), although the response might not always contain the very latest data.</p></li><li><p><strong>P &#8211; Partition Tolerance:</strong> The system keeps working despite network failures or &#8220;<em>partitions</em>&#8221; that temporarily break the network into isolated pieces.</p></li></ul><p>Brewer&#8217;s insight (later formalized by <em>Gilbert</em> and <em>Lynch</em>) was that in a distributed system <strong>you cannot have all three of these at once</strong>. The CAP theorem famously says: <em>pick two, and sacrifice the third</em>. If you want consistency and availability at all times, your system cannot tolerate certain network failures. If you need to survive network partitions (and most distributed systems do), you face a choice: <strong>favor consistency or favor availability</strong>. In theory, we&#8217;d love systems that are fast, always up, and perfectly accurate; in reality, we&#8217;re forced to compromise.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qmtc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc49ffe6f-3d30-4999-beb7-d2e0c3a13bf4_1848x1514.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qmtc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc49ffe6f-3d30-4999-beb7-d2e0c3a13bf4_1848x1514.png 424w, https://substackcdn.com/image/fetch/$s_!qmtc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc49ffe6f-3d30-4999-beb7-d2e0c3a13bf4_1848x1514.png 848w, https://substackcdn.com/image/fetch/$s_!qmtc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc49ffe6f-3d30-4999-beb7-d2e0c3a13bf4_1848x1514.png 1272w, https://substackcdn.com/image/fetch/$s_!qmtc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc49ffe6f-3d30-4999-beb7-d2e0c3a13bf4_1848x1514.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qmtc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc49ffe6f-3d30-4999-beb7-d2e0c3a13bf4_1848x1514.png" width="1456" height="1193" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c49ffe6f-3d30-4999-beb7-d2e0c3a13bf4_1848x1514.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1193,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:205244,&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://iam.slys.dev/i/176729656?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc49ffe6f-3d30-4999-beb7-d2e0c3a13bf4_1848x1514.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_!qmtc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc49ffe6f-3d30-4999-beb7-d2e0c3a13bf4_1848x1514.png 424w, https://substackcdn.com/image/fetch/$s_!qmtc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc49ffe6f-3d30-4999-beb7-d2e0c3a13bf4_1848x1514.png 848w, https://substackcdn.com/image/fetch/$s_!qmtc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc49ffe6f-3d30-4999-beb7-d2e0c3a13bf4_1848x1514.png 1272w, https://substackcdn.com/image/fetch/$s_!qmtc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc49ffe6f-3d30-4999-beb7-d2e0c3a13bf4_1848x1514.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>This doesn&#8217;t mean a system can never be consistent, available, and partition-tolerant in different moments. Rather, CAP highlights a design <strong>priority</strong>: when a network partition happens, would you rather your system go offline (to remain consistent) or stay online but potentially show slightly inconsistent data? Different systems answer this question differently, which leads us to CP vs. AP systems.</p><h1>&#129504; How systems choose between consistency and availability</h1><p>Real-world systems have to make a conscious choice when designing for the CAP trade-off: <strong>consistency or availability</strong> (since we assume partitions will happen eventually). Depending on the domain, one is often valued over the other.</p><p><strong>CP systems (Consistency + Partition tolerance):</strong> These systems choose to remain consistent even if it means not always being fully available during a network problem. A classic example is a banking system. If there&#8217;s any uncertainty or network hiccup, a bank&#8217;s software would rather refuse your transaction or make you wait than show the wrong balance. Accuracy is paramount - no one wants to see money &#8220;<em>appear</em>&#8221; or &#8220;<em>disappear</em>&#8221; incorrectly. In fact, when designing a financial system, consistency is considered crucial, even if that means occasionally sacrificing availability. In a CP system, if a few servers can&#8217;t talk to each other (a partition), they might halt some operations or switch to a read-only mode until they can synchronize, ensuring no conflicting data gets created. It&#8217;s a cautious approach: &#8220;<em>Let&#8217;s pause until we&#8217;re sure everyone&#8217;s on the same page</em>&#8221;.</p><p><strong>AP systems (Availability + Partition tolerance):</strong> These systems take the opposite approach: they prioritize uptime and responsiveness, even if it means some parts of the system might be briefly out-of-date. Many social networks and online services fall into this category. You&#8217;ve probably noticed that your social media feed will load with older posts rather than showing an error &#8211; that&#8217;s on purpose. You&#8217;d prefer to see <strong>something</strong> rather than nothing. Facebook, Twitter, Instagram &#8211; all these platforms tolerate showing you data that might not be perfectly up-to-the-second, as long as the service stays up and interactive. They embrace <strong>eventual consistency</strong> under the hood. A new post might appear immediately to the author, while followers see it moments later; such temporary inconsistency is acceptable for the sake of availability and user experience<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>. In an AP system, even if parts of the network are cut off from each other, each partition can continue accepting requests (reads and writes) and sync up later. It&#8217;s a &#8220;<em>show something now, fix it later</em>&#8221; philosophy.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AT65!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b2ce146-7df1-4655-ac6b-44425e5274cb_2910x1344.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AT65!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b2ce146-7df1-4655-ac6b-44425e5274cb_2910x1344.png 424w, https://substackcdn.com/image/fetch/$s_!AT65!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b2ce146-7df1-4655-ac6b-44425e5274cb_2910x1344.png 848w, https://substackcdn.com/image/fetch/$s_!AT65!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b2ce146-7df1-4655-ac6b-44425e5274cb_2910x1344.png 1272w, https://substackcdn.com/image/fetch/$s_!AT65!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b2ce146-7df1-4655-ac6b-44425e5274cb_2910x1344.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AT65!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b2ce146-7df1-4655-ac6b-44425e5274cb_2910x1344.png" width="1200" height="553.8461538461538" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3b2ce146-7df1-4655-ac6b-44425e5274cb_2910x1344.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:672,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:171333,&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://iam.slys.dev/i/176729656?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b2ce146-7df1-4655-ac6b-44425e5274cb_2910x1344.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_!AT65!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b2ce146-7df1-4655-ac6b-44425e5274cb_2910x1344.png 424w, https://substackcdn.com/image/fetch/$s_!AT65!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b2ce146-7df1-4655-ac6b-44425e5274cb_2910x1344.png 848w, https://substackcdn.com/image/fetch/$s_!AT65!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b2ce146-7df1-4655-ac6b-44425e5274cb_2910x1344.png 1272w, https://substackcdn.com/image/fetch/$s_!AT65!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b2ce146-7df1-4655-ac6b-44425e5274cb_2910x1344.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 choice between CP and AP often boils down to the question: <strong>What matters more for this application, absolute accuracy or uptime?</strong> In a bank, you&#8217;d rather wait a bit longer and <em>see the correct balance</em> (consistency over availability). On Instagram or Twitter, you&#8217;d rather see <strong>something</strong> now &#8211; maybe a slightly stale feed &#8211; than encounter a &#8220;<em>service unavailable</em>&#8221; error (availability over immediate consistency). Neither approach is &#8220;<em>wrong</em>&#8221; - they&#8217;re just optimized for different goals.</p><h1>&#129518; Types of consistency (with human analogies)</h1><p>Consistency isn&#8217;t a single setting but a spectrum of models, each with its own guarantees about how and when data updates become visible. To demystify some common consistency levels, let&#8217;s look at a few in simple terms, along with analogies from everyday life:</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/Czdk1/3/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4f4c8644-de91-4039-a48b-1eb6289529f8_1220x952.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1a732276-27f5-404a-bede-5c660b5fe55b_1220x1022.png&quot;,&quot;height&quot;:373,&quot;title&quot;:&quot;Consistency levels&quot;,&quot;description&quot;:&quot;&quot;,&quot;belowTheFold&quot;:true}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/Czdk1/3/" width="730" height="373" frameborder="0" scrolling="no" loading="lazy"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>Each of these offers a different balance between immediacy and simplicity. <strong>Strong consistency</strong> is the intuitive ideal &#8211; as soon as something changes, every user and every node in the system sees the same latest data. It&#8217;s like having a single up-to-date copy of the truth that everyone consults. This is great for correctness, but it can be slow or impractical in a distributed setting (imagine if our group chat required every message to be confirmed by every phone in the chat before anyone sees it &#8211; that would be painfully slow!).</p><p>At the other end, <strong>eventual consistency</strong> is very flexible. It doesn&#8217;t promise that everyone sees the latest data <strong>right now</strong> &#8211; only that if you stop making updates and wait awhile, all replicas will <strong>eventually</strong> catch up to the same state. Different users might see slightly different information for a short time, but the differences disappear over time. Many large-scale systems use this model to stay fast and available. It&#8217;s like those text messages: you might get replies with delay, but eventually everyone gets all the messages and the conversation makes sense as a whole.</p><p><strong>Causal consistency</strong> is a bit smarter about ordering &#8211; it ensures that causally related updates (like a post and then a comment on that post) are seen in the correct order everywhere<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>. If event A causes event B, no one will see B without seeing A first. For example, your reply will never appear to have happened before the comment that triggered it. This model relaxes the requirement of strong consistency (which enforces one single timeline for all events) and allows unrelated events to be observed in different orders by different nodes, as long as the cause-and-effect relationships are preserved. It&#8217;s a nice middle ground that many systems strive for &#8211; improving performance (by not globally ordering everything) without confusing users about what caused what.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eqw_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fefc702ff-4054-47c8-a273-580f81946394_2039x1625.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eqw_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fefc702ff-4054-47c8-a273-580f81946394_2039x1625.png 424w, https://substackcdn.com/image/fetch/$s_!eqw_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fefc702ff-4054-47c8-a273-580f81946394_2039x1625.png 848w, https://substackcdn.com/image/fetch/$s_!eqw_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fefc702ff-4054-47c8-a273-580f81946394_2039x1625.png 1272w, https://substackcdn.com/image/fetch/$s_!eqw_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fefc702ff-4054-47c8-a273-580f81946394_2039x1625.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eqw_!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fefc702ff-4054-47c8-a273-580f81946394_2039x1625.png" width="1200" height="956.0439560439561" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/efc702ff-4054-47c8-a273-580f81946394_2039x1625.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:1160,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:277629,&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://iam.slys.dev/i/176729656?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fefc702ff-4054-47c8-a273-580f81946394_2039x1625.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_!eqw_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fefc702ff-4054-47c8-a273-580f81946394_2039x1625.png 424w, https://substackcdn.com/image/fetch/$s_!eqw_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fefc702ff-4054-47c8-a273-580f81946394_2039x1625.png 848w, https://substackcdn.com/image/fetch/$s_!eqw_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fefc702ff-4054-47c8-a273-580f81946394_2039x1625.png 1272w, https://substackcdn.com/image/fetch/$s_!eqw_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fefc702ff-4054-47c8-a273-580f81946394_2039x1625.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>Finally, <strong>read-your-writes</strong> consistency is a guarantee often applied on a per-user basis. It doesn&#8217;t concern multiple users agreeing; it just ensures <strong>you</strong> don&#8217;t get confused by your own actions. After you make an update, when you go to read that same data, you&#8217;ll at least see your own update reflected<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>. For instance, if you edit your profile and then refresh the page, the new info should be there for you. Even if some distant server hasn&#8217;t gotten the update yet, the system will make sure your reads fetch a copy that includes your changes. This avoids the &#8220;<em>Didn&#8217;t I just change that?</em>&#8221; frustration. It&#8217;s a pragmatic way to make an eventually consistent system feel more solid to a user by giving them a consistent view of <strong>their</strong> own actions.</p><blockquote><p><strong>Insight:</strong> Think of these consistency models as different rhythms at which a distributed system <strong>reaches the truth</strong>. Strong consistency is like a synchronized orchestra &#8211; strict but slow. Eventual consistency is more like a jam session where everyone will harmonize, but not instantly. The other models (causal, read-your-writes, etc.) add rules to make the tune sound &#8220;<em>right</em>&#8221; to the listeners. Modern system design often blends these models, choosing stricter consistency for some operations and looser consistency for others. In fact, many systems treat consistency as a spectrum and <strong>mix models</strong> as needed &#8211; for example, ensuring read-your-writes and causal ordering to keep users happy, while still allowing some eventual behavior for scalability.</p></blockquote><h1>&#128260; How consistency affects the user</h1><p>From a user&#8217;s perspective, consistency is never about the theory &#8211; it&#8217;s about how the app feels. Interestingly, users often don&#8217;t notice (or care about) whether a system is strongly or eventually consistent, as long as the <strong>experience</strong> makes sense. The onus is on us as engineers to hide inconsistencies and make things feel natural.</p><p>Great systems cleverly mask the quirks of distributed data. A common technique is using <strong>optimistic updates</strong> on the front-end: when you perform an action (like sending a message or &#8220;<em>liking</em>&#8221; a post), the app immediately reflects that change in the UI without waiting for the server to confirm. For example, your email application might show a new email in your &#8220;<em>Sent</em>&#8221; folder instantly, while in reality the server is still in the process of delivering it. This gives you instant feedback. Under the hood, the system will reconcile any discrepancy afterward (if the send fails or the server disagrees, you might see a small &#8220;<em>retry</em>&#8221; or &#8220;<em>syncing&#8230;</em>&#8221; notice). By showing local results first and syncing in the background, the system feels <strong>responsive</strong> and &#8220;<em>consistent enough</em>&#8221; from the user&#8217;s point of view.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MBYv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42c8a8f8-b242-4176-9bfe-e31388d36f5c_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MBYv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42c8a8f8-b242-4176-9bfe-e31388d36f5c_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!MBYv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42c8a8f8-b242-4176-9bfe-e31388d36f5c_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!MBYv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42c8a8f8-b242-4176-9bfe-e31388d36f5c_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!MBYv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42c8a8f8-b242-4176-9bfe-e31388d36f5c_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MBYv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42c8a8f8-b242-4176-9bfe-e31388d36f5c_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/42c8a8f8-b242-4176-9bfe-e31388d36f5c_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1384245,&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://iam.slys.dev/i/176729656?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42c8a8f8-b242-4176-9bfe-e31388d36f5c_1024x1024.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_!MBYv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42c8a8f8-b242-4176-9bfe-e31388d36f5c_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!MBYv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42c8a8f8-b242-4176-9bfe-e31388d36f5c_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!MBYv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42c8a8f8-b242-4176-9bfe-e31388d36f5c_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!MBYv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42c8a8f8-b242-4176-9bfe-e31388d36f5c_1024x1024.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"><em>An email client interface showing a message moved to the Sent folder immediately after hitting &#8220;Send&#8221;, possibly with a subtle indicator that it&#8217;s still sending. The user perceives it as sent while the system finalizes delivery in the background.</em></figcaption></figure></div><p>Another strategy is to mark things as &#8220;<em>pending</em>&#8221; or &#8220;<em>updating</em>&#8221;. For instance, a mobile app might display a just-uploaded photo in your gallery right away but with a faint overlay or spinner, indicating it&#8217;s being saved to the cloud. You see it immediately, and once the server confirms the upload (or if it fails), the app updates that status (removing the spinner or showing an error). Thus, eventual consistency is happening behind the scenes, but the user experience remains smooth. </p><div><hr></div><p>&#128172; <strong>Ever noticed this in real life?</strong></p><p>Drop a comment below - I&#8217;d love to hear your thoughts on systems that felt &#8220;<em>off</em>&#8221; or surprisingly seamless. What&#8217;s one app where you&#8217;ve felt the inconsistency (or didn&#8217;t, thanks to great UX)?</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/what-it-means-for-a-system-to-be/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/what-it-means-for-a-system-to-be/comments"><span>Leave a comment</span></a></p><div><hr></div><p>In other words, the system is eventually consistent, but it <strong>feels</strong> intuitively consistent to the user. Engineering articles often note that eventual consistency is perfectly acceptable with the right UX design &#8211; e.g., showing progress indicators or using short animations to cover up the waiting.</p><p>In essence, the goal isn&#8217;t perfect data truth at every millisecond &#8211; it&#8217;s a <strong>consistent experience</strong>. Gmail, for example, will show your outgoing email as &#8220;<em>Sent</em>&#8221; immediately, giving you the confidence to move on, even though the message is actually queued and being processed behind the scenes. Social networks show your new status update to you right after you post it (thanks to read-your-writes guarantees), ensuring you&#8217;re not confused, while the system takes a bit of time to propagate that update to all your followers. Users are happy because the app behaves in a natural, snappy way &#8211; they don&#8217;t see the gears turning underneath. A well-designed distributed system often embraces eventual consistency internally but makes it <strong>feel</strong> almost like strong consistency to the end user.</p><h1>&#9878;&#65039; When consistency matters most</h1><p>Not all applications are equal when it comes to consistency needs. Some domains demand that data be absolutely up-to-date and consistent across the board, while others can tolerate a little lag or discrepancy. Knowing which is which helps engineers choose the right approach:</p><h2>&#9989; <strong>Contexts that demand strong consistency</strong></h2><ul><li><p><strong>Financial transactions (banking, payments):</strong> Money is serious business. Account balances, transfers, stock trades &#8211; these must be correct and consistent globally to prevent errors like double-spending or overdrafts. Banks will often prefer to delay or lock an operation rather than allow an inconsistent view of funds. Similarly, online trading platforms need all traders to see the same prices and account info at the same time.</p></li><li><p><strong>Medical records:</strong> In healthcare systems, showing outdated patient information could be dangerous. If one doctor&#8217;s screen shows an old medication list while another&#8217;s shows an updated list, the consequences could be life-threatening. For this reason, patient records systems lean toward strong consistency to ensure everyone sees the latest, correct data.</p></li><li><p><strong>Booking and reservation systems:</strong> If two people think they booked the last seat on a flight or the same hotel room because of a momentary inconsistency, you have a big problem. These systems often enforce strong consistency (or use transactions) to prevent double-bookings. It&#8217;s better to make one user wait or even receive an error than to allow an overlap that must be fixed later.</p></li></ul><h2>&#9989; <strong>Contexts where eventual consistency is fine (even desirable)</strong></h2><ul><li><p><strong>Social media feeds:</strong> As mentioned earlier, social platforms are a prime example. It&#8217;s not critical that every user sees every like or comment the instant it happens &#8211; a slight delay is usually unnoticeable, and the system values being always available and fast over being perfectly synchronized.</p></li><li><p><strong>Analytics dashboards:</strong> Dashboards that show website traffic, app usage, or ad metrics typically don&#8217;t need up-to-the-second accuracy. If your analytics are a minute behind real time, that&#8217;s usually acceptable. What matters is that the system can ingest data reliably and stay up, rather than being perfectly current. These systems often favor availability and partition tolerance, updating stats gradually.</p></li><li><p><strong>Caches and recommendation systems:</strong> Systems that cache data (like content delivery networks, or in-memory caches for database queries) or that serve recommendations (like &#8220;<em>people you may know</em>&#8221; or product suggestions) can tolerate some staleness. Slight delays in propagation are acceptable and help maintain global availability. For example, an e-commerce site&#8217;s &#8220;<em>popular products</em>&#8221; list might not update instantly with every single purchase; it might recompute every hour. That&#8217;s fine &#8211; users get quick responses, and the data will refresh eventually without harming the experience.</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_!HT_F!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F267104d3-3b76-4536-9f0e-3e9b59d925b0_1547x2623.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HT_F!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F267104d3-3b76-4536-9f0e-3e9b59d925b0_1547x2623.png 424w, https://substackcdn.com/image/fetch/$s_!HT_F!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F267104d3-3b76-4536-9f0e-3e9b59d925b0_1547x2623.png 848w, https://substackcdn.com/image/fetch/$s_!HT_F!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F267104d3-3b76-4536-9f0e-3e9b59d925b0_1547x2623.png 1272w, https://substackcdn.com/image/fetch/$s_!HT_F!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F267104d3-3b76-4536-9f0e-3e9b59d925b0_1547x2623.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HT_F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F267104d3-3b76-4536-9f0e-3e9b59d925b0_1547x2623.png" width="1456" height="2469" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/267104d3-3b76-4536-9f0e-3e9b59d925b0_1547x2623.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2469,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:284030,&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://iam.slys.dev/i/176729656?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F267104d3-3b76-4536-9f0e-3e9b59d925b0_1547x2623.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_!HT_F!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F267104d3-3b76-4536-9f0e-3e9b59d925b0_1547x2623.png 424w, https://substackcdn.com/image/fetch/$s_!HT_F!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F267104d3-3b76-4536-9f0e-3e9b59d925b0_1547x2623.png 848w, https://substackcdn.com/image/fetch/$s_!HT_F!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F267104d3-3b76-4536-9f0e-3e9b59d925b0_1547x2623.png 1272w, https://substackcdn.com/image/fetch/$s_!HT_F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F267104d3-3b76-4536-9f0e-3e9b59d925b0_1547x2623.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 guiding insight here is: <strong>match the consistency model to the business needs</strong>. If a small inconsistency would cause real harm, confusion, or violate trust, that&#8217;s when you require strong consistency. If a slight delay or mismatch is essentially invisible or harmless to the user, then &#8220;<em>good enough</em>&#8221; consistency (and the performance gains that come with it) is usually exactly right. Indeed, strong consistency is crucial for things like banking, booking, and healthcare<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>, whereas eventual consistency is often a smart choice for high-scale, user-facing systems like social networks, analytics, and caching where speed and availability are a priority.</p><h1>&#129513; Designing with imperfection in mind</h1><p>Embracing eventual or partial consistency doesn&#8217;t mean you give up on correctness &#8211; it means you design your system to <strong>handle inconsistency gracefully</strong>. In fact, building a robust distributed system is as much about planning for things to be temporarily out-of-sync as it is about trying to keep them in sync.</p><p>Here are some strategies for designing with these realities in mind:</p><ul><li><p><strong>Define where consistency matters (and where it can relax):</strong> Identify which parts of your data <strong>must</strong> be absolutely consistent and which can be more relaxed. For instance, an e-commerce site might enforce strong consistency for the inventory count of products (so you never sell more than you have in stock), but use eventual consistency for things like the product recommendation list or user reviews. By isolating the truly critical data, you can apply stricter methods there and let other areas enjoy more flexibility.</p></li><li><p><strong>Use the UI to mask delays:</strong> As discussed, a smart UI/UX can hide a lot of inconsistency. Design your user interface such that it doesn&#8217;t expose intermediate states awkwardly. If a piece of data might take time to update everywhere, show a loading state, or label it as &#8220;<em>syncing&#8230;</em>&#8221;, or update it in the background. This way, a technical delay becomes a non-issue for the user. A well-timed progress bar or a subtle &#8220;<em>refreshing</em>&#8221; indicator can turn an otherwise confusing inconsistency into an expected, even reassuring, part of the experience.</p></li><li><p><strong>Implement repair and reconciliation mechanisms:</strong> Since you expect that different parts of the system might diverge temporarily, build in processes that will <strong>reconcile</strong> data after the fact. This could be a background job that compares and merges divergent copies of data (for example, if two edits occurred separately during a partition, reconcile them once connectivity is restored), or a periodic &#8220;<em>anti-entropy</em>&#8221; task that ensures all replicas eventually converge. Even simple retry logic can help &#8211; if one service fails to get an update due to a network glitch, it can try again later. The system should have a way to <em>heal</em> its inconsistent pieces over time.</p></li><li><p><strong>Plan for idempotency and conflict resolution:</strong> In distributed systems, the same operation might happen twice or out of order due to retries and delays. Design your operations to be <strong>idempotent</strong> (so that applying the same update more than once has the same effect as doing it once) and define clear conflict resolution rules. For example, if two updates to the same record happen on different partitions, decide which one &#8220;<em>wins</em>&#8221; or how to merge them (last write wins, merge field-by-field, etc.). Having these rules in place prevents chaos when the system syncs up.</p></li><li><p><strong>Monitor and adapt:</strong> Finally, actively monitor your system&#8217;s consistency behavior. Keep an eye on metrics like replication lag (how far apart replicas are), the frequency of stale reads, or how often conflicts occur and get resolved. If you notice issues &#8211; e.g., data taking too long to become consistent, or users noticing anomalies &#8211; you can adjust your approach (maybe tighten consistency in certain areas, or improve your conflict resolution). Designing for imperfection is an ongoing process of tuning.</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_!wwiw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabc3727-d164-4657-a3fd-2e01f92d577c_2658x2317.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wwiw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabc3727-d164-4657-a3fd-2e01f92d577c_2658x2317.png 424w, https://substackcdn.com/image/fetch/$s_!wwiw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabc3727-d164-4657-a3fd-2e01f92d577c_2658x2317.png 848w, https://substackcdn.com/image/fetch/$s_!wwiw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabc3727-d164-4657-a3fd-2e01f92d577c_2658x2317.png 1272w, https://substackcdn.com/image/fetch/$s_!wwiw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabc3727-d164-4657-a3fd-2e01f92d577c_2658x2317.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wwiw!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabc3727-d164-4657-a3fd-2e01f92d577c_2658x2317.png" width="1200" height="1045.8791208791208" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dabc3727-d164-4657-a3fd-2e01f92d577c_2658x2317.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:1269,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:262664,&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://iam.slys.dev/i/176729656?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabc3727-d164-4657-a3fd-2e01f92d577c_2658x2317.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_!wwiw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabc3727-d164-4657-a3fd-2e01f92d577c_2658x2317.png 424w, https://substackcdn.com/image/fetch/$s_!wwiw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabc3727-d164-4657-a3fd-2e01f92d577c_2658x2317.png 848w, https://substackcdn.com/image/fetch/$s_!wwiw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabc3727-d164-4657-a3fd-2e01f92d577c_2658x2317.png 1272w, https://substackcdn.com/image/fetch/$s_!wwiw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabc3727-d164-4657-a3fd-2e01f92d577c_2658x2317.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>A good distributed system isn&#8217;t one that never encounters divergent data &#8211; that&#8217;s nearly impossible at scale. Instead, it&#8217;s one that <strong>handles divergence intelligently</strong>. Think of it like a team that doesn&#8217;t panic when members initially disagree; they have processes to discuss and resolve differences and eventually reach consensus. Similarly, your system might have moments where Node A thinks X=5 while Node B thinks X=7. That&#8217;s okay if your design ensures they will reconcile (and that users aren&#8217;t adversely affected in the meantime). In other words, success is not measured by never having inconsistencies, but by how quickly and safely the system can <strong>make peace</strong> after a temporary disagreement.</p><h1>&#128172; Consistency as a philosophy</h1><p>Stepping back from the nuts and bolts, there&#8217;s a broader way to look at consistency in distributed systems. It&#8217;s less about <strong>absolute truth at every moment</strong> and more about the <strong>ability to eventually agree on truth</strong>. In life, people don&#8217;t always instantly agree or share the exact same knowledge &#8211; but with communication and time, they sync up. Distributed systems are similar: they might momentarily hold different views of the data, but as long as they have ways to share updates and resolve conflicts, they will converge on a single reality.</p><p>In fact, consistency in distributed systems is as much a <strong>philosophy of convergence</strong> as it is a technical property. We design protocols (and choose consistency models) to ensure that no matter what happens &#8211; even if updates arrive late or out of order &#8211; there are rules that will guide every part of the system toward the correct final state. The beauty is that systems, like people, <strong>don&#8217;t have to agree on everything right away</strong>. What&#8217;s crucial is that they have a path to eventually reach agreement.</p><p>This perspective can be liberating. It means that temporary inconsistency isn&#8217;t failure; it&#8217;s a normal phase of the system&#8217;s operation. By accepting that &#8220;<em>truth travels in pieces</em>&#8221; and planning accordingly, we make our systems more resilient and often more efficient. We stop chasing perfect consistency at every moment and focus on what really matters: that in the end (often a fraction of a second later, sometimes longer), everyone sees the same result and the world makes sense again.</p><p>Consistency, then, isn&#8217;t about rigidly enforcing one absolute truth at all times; it&#8217;s about <strong>ensuring the truth isn&#8217;t lost and will be shared</strong>. It&#8217;s a promise that the system, given enough time and correct mechanisms, can line up all its pieces of truth into a coherent whole. And it&#8217;s a reminder that sometimes patience (waiting for things to sync up) and clever design (hiding the waiting, ordering updates) can achieve the best of both worlds &#8211; a system that feels fast and fluid for users, yet still converges on correctness behind the scenes.</p><h1>&#9997;&#65039; Recap</h1><ul><li><p><strong>Consistency</strong> = agreement between nodes. All users or components see the same data, yielding a uniform view of the system. In distributed systems, achieving this often comes at the cost of speed or uptime.</p></li><li><p><strong>CAP theorem:</strong> In any distributed system, you can&#8217;t have all three of Consistency, Availability, and Partition tolerance at once. Because network partitions will happen, every design must choose which to prioritize (consistency vs. availability) when partitions occur.</p></li><li><p><strong>Many flavors of consistency:</strong> It&#8217;s not just &#8220;<em>consistent or not</em>&#8221;. There are multiple levels (strong, eventual, causal, read-your-writes, etc.), each defining how and when a system reaches agreement on updates. These models let us fine-tune the balance between immediacy and performance.</p></li><li><p><strong>Consistency vs. user experience:</strong> Great systems don&#8217;t necessarily enforce strict consistency everywhere &#8211; they just make the system <em>feel</em> consistent for users. Techniques like optimistic UI updates and careful UI cues (e.g. &#8220;<em>syncing</em>&#8221; indicators) hide the delays inherent in eventual consistency, so the user trusts what they see.</p></li><li><p><strong>Right tool for the job:</strong> Know when you truly need strong consistency (e.g. finance, medical, critical data) and when &#8220;<em>good enough</em>&#8221; will do. Often, relaxing consistency in non-critical areas yields huge gains in scalability and fault tolerance.</p></li><li><p><strong>Design for eventual consistency:</strong> Accept that temporary mismatches will occur and build your system to resolve them. Use <strong>reconciliation</strong> processes, <strong>conflict resolution</strong> strategies, and clear client guarantees (like read-your-writes) to manage inconsistencies. This isn&#8217;t a weakness &#8211; it&#8217;s a wise design strategy that acknowledges reality.</p></li></ul><div><hr></div><div class="poll-embed" data-attrs="{&quot;id&quot;:412222}" data-component-name="PollToDOM"></div><div><hr></div><p>In summary, consistency isn&#8217;t a black-or-white property but a nuanced aspect of system design. The goal is to ensure the system can eventually align on a single source of truth and, in the meantime, keep things running smoothly. By understanding the shades of consistency and learning how to mask or manage inconsistency, we can build distributed systems that are both <strong>reliable</strong> and <strong>user-friendly</strong> &#8211; systems that don&#8217;t always have to be perfectly consistent every split-second, as long as they get there when it counts.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p><a href="https://www.splunk.com/en_us/blog/learn/cap-theorem.html">CAP Theorem &amp; Strategies for Distributed Systems</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p><a href="https://arpitbhayani.me/blogs/eventual-consistency">Why Eventual Consistency is Preferred in Distributed Systems</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p><a href="https://www.usenix.org/system/files/login/articles/08_lloyd_41-43_online.pdf">A Short Primer on Causal Consistency</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p><a href="https://blog.algomaster.io/p/strong-vs-eventual-consistency">Strong vs. Eventual Consistency</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p><a href="https://shivangsnewsletter.com/p/understanding-database-consistency">Understanding Database Consistency Levels And Applying Them To A Single Web Service</a></p></div></div>]]></content:encoded></item><item><title><![CDATA[How systems handle failure — retries, circuit breakers, and idempotency]]></title><description><![CDATA[Systems Literacy]]></description><link>https://iam.slys.dev/p/how-systems-handle-failure-retries</link><guid isPermaLink="false">https://iam.slys.dev/p/how-systems-handle-failure-retries</guid><dc:creator><![CDATA[Jakub Slys]]></dc:creator><pubDate>Tue, 18 Nov 2025 20:32:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!YsF7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YsF7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YsF7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!YsF7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!YsF7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!YsF7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YsF7!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:213463,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/176729895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YsF7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!YsF7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!YsF7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!YsF7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1b310c-63e4-4a2e-8bd4-28aba7fcf91a_1536x1024.jpeg 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><blockquote><p><strong>You click &#8220;Buy Now&#8221;.</strong><br>The spinner turns&#8230; and turns&#8230; and then disappears.<br>You check your bank app - payment went through.<br>But the website shows: <em>&#8220;Error - please try again&#8221;.</em></p><p>Congratulations. You&#8217;ve just met the gray zone of distributed systems -<br>where a single operation might have succeeded, failed, or both.</p></blockquote><p>Welcome to the world where software fails in confusing ways. In distributed systems, it&#8217;s possible for an action (like a payment) to <strong>actually succeed</strong> on the backend while the frontend thinks it failed. The result? Confusion, duplicate charges, and frustrated users. This scenario isn&#8217;t just a bad day for you; it&#8217;s a reality that <strong>reliable systems must handle gracefully</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!W2fx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d39dec-790a-44c6-8f12-e67e61f6849c_1324x918.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!W2fx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d39dec-790a-44c6-8f12-e67e61f6849c_1324x918.jpeg 424w, https://substackcdn.com/image/fetch/$s_!W2fx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d39dec-790a-44c6-8f12-e67e61f6849c_1324x918.jpeg 848w, https://substackcdn.com/image/fetch/$s_!W2fx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d39dec-790a-44c6-8f12-e67e61f6849c_1324x918.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!W2fx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d39dec-790a-44c6-8f12-e67e61f6849c_1324x918.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!W2fx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d39dec-790a-44c6-8f12-e67e61f6849c_1324x918.jpeg" width="1324" height="918" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c3d39dec-790a-44c6-8f12-e67e61f6849c_1324x918.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:918,&quot;width&quot;:1324,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:88112,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://iam.slys.dev/i/176729895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d39dec-790a-44c6-8f12-e67e61f6849c_1324x918.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_!W2fx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d39dec-790a-44c6-8f12-e67e61f6849c_1324x918.jpeg 424w, https://substackcdn.com/image/fetch/$s_!W2fx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d39dec-790a-44c6-8f12-e67e61f6849c_1324x918.jpeg 848w, https://substackcdn.com/image/fetch/$s_!W2fx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d39dec-790a-44c6-8f12-e67e61f6849c_1324x918.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!W2fx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d39dec-790a-44c6-8f12-e67e61f6849c_1324x918.jpeg 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>In this post, we&#8217;ll explore how modern systems <strong>embrace failure</strong> without hurting the user experience. We&#8217;ll demystify how <strong>retries</strong>, <strong>circuit breakers</strong>, and <strong>idempotency</strong> work together so that even when things go wrong, they go <em>right</em>. By the end, you&#8217;ll see why resilient software doesn&#8217;t fear failure - it expects it, plans for it, and bounces back before anyone even notices.</p><div><hr></div><p><strong>&#128161; Before we go deeper - if you&#8217;re enjoying posts like this, consider subscribing to my publication.</strong><br>I write about system design, architecture, AI, reliability, and the &#8220;<em>behind-the-scenes</em>&#8221; stuff that helps you grow as a developer.</p><p>Subscribing means you won&#8217;t miss new breakdowns, diagrams, or hands-on explanations - and it genuinely helps me keep creating more content like this for you.</p><p><strong>Join in if you want to learn this stuff together. &#128640;</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>&#9881;&#65039; Failure is normal in distributed systems</h1><p>In a distributed system, <strong>failure is not an exception - it&#8217;s normal</strong>. Networks drop packets. Services time out. Messages can arrive twice (or not at all). Servers might restart right in the middle of a transaction. Yet users still expect the system to &#8220;<em>just work</em>&#8221; smoothly despite all that chaos happening behind the scenes.</p><p>Why so much chaos? Because once you have many components interacting over a network, the unlikely becomes likely. A hardware failure that&#8217;s rare on one machine becomes <strong>common when you have thousands<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></strong>. A &#8220;<em>once in a million</em>&#8221; glitch might occur a few times a day across a global fleet of servers. As a classic principle of cloud computing states: <em>&#8220;In distributed systems, failure is normal&#8230; failures are assumed, designs work around them, and software anticipates them&#8221;.</em></p><p>The key insight is this:</p><p>&#10145;&#65039; <strong>Reliability isn&#8217;t about avoiding failure; it&#8217;s about making failure uneventful.</strong> In other words, a well-designed system experiences faults all the time, but it handles them so gracefully that nobody notices. Instead of treating failures as catastrophic anomalies, resilient systems treat them as expected events and have <strong>built-in responses</strong>. Just like a car has shock absorbers for bumps in the road, distributed software has patterns to absorb the shocks of downtime, lost messages, and random errors.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lYOZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38aec2d6-066a-4e79-97bf-760755b73c53_1440x1191.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lYOZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38aec2d6-066a-4e79-97bf-760755b73c53_1440x1191.jpeg 424w, https://substackcdn.com/image/fetch/$s_!lYOZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38aec2d6-066a-4e79-97bf-760755b73c53_1440x1191.jpeg 848w, https://substackcdn.com/image/fetch/$s_!lYOZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38aec2d6-066a-4e79-97bf-760755b73c53_1440x1191.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!lYOZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38aec2d6-066a-4e79-97bf-760755b73c53_1440x1191.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lYOZ!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38aec2d6-066a-4e79-97bf-760755b73c53_1440x1191.jpeg" width="1200" height="992.5" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/38aec2d6-066a-4e79-97bf-760755b73c53_1440x1191.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:1191,&quot;width&quot;:1440,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:124594,&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://iam.slys.dev/i/176729895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38aec2d6-066a-4e79-97bf-760755b73c53_1440x1191.jpeg&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_!lYOZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38aec2d6-066a-4e79-97bf-760755b73c53_1440x1191.jpeg 424w, https://substackcdn.com/image/fetch/$s_!lYOZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38aec2d6-066a-4e79-97bf-760755b73c53_1440x1191.jpeg 848w, https://substackcdn.com/image/fetch/$s_!lYOZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38aec2d6-066a-4e79-97bf-760755b73c53_1440x1191.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!lYOZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38aec2d6-066a-4e79-97bf-760755b73c53_1440x1191.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>So, the mindset shift is crucial: <strong>don&#8217;t panic when things go wrong - plan for it.</strong> The next sections will cover exactly how to plan for failures using retries, circuit breakers, and idempotency so that even when something breaks, your application <em>bends without breaking</em>.</p><h1>&#128257; The first line of defense &#8212; retries</h1><p>When an operation fails transiently (say, a network request times out), often the simplest fix is: <strong>try again</strong>. This is the idea of <strong>retries</strong> &#8211; automatically attempting an operation again in hopes that a momentary glitch resolves. In many cases, a quick retry turns a failure into a success: the network hiccup passes, the second call reaches the server, and all is well. In fact, retries are a major reason distributed systems can mask inevitable flakiness and still meet high uptime goals<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>.</p><p>However, retries are a double-edged sword. If used naively, they can <strong>make things worse</strong> instead of better.</p><p><strong>Retry storms and overload.</strong> If a service is truly down or overloaded, blind retries from many clients can create a <em>stampede</em>. Imagine hundreds of clients all retrying failed requests at the same time &#8211; the failing service gets bombarded with even more traffic just when it&#8217;s least able to handle it. This feedback loop is known as a <strong>retry storm</strong>, and it can turn a small outage into a cascade of failures. In one scenario, a failure in a deep call chain caused a 2^N explosion of retries, overwhelming the database and prolonging the outage. In short, retries are &#8220;<em>selfish</em>&#8221; - each client is just trying to succeed, but collectively they can hammer the server<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>.</p><p><strong>Duplicate actions.</strong> Retrying an operation that actually <em>did</em> go through can trigger duplicate effects. For example, if the first &#8220;<em>Buy Now</em>&#8221; <em>did</em> charge your card but the acknowledgement got lost, a retry might charge you again. Without precautions, you could end up with two payments, two created accounts, or duplicate messages. In technical terms, <strong>at-least-once delivery</strong> means the same action might happen twice. (Ever gotten two confirmation emails for one order? That&#8217;s a duplicate caused by a retry.) Unless the system is built to handle this, a single user action could be processed multiple times<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>.</p><p><strong>How do we retry safely?</strong> The answer is controlled retries, using techniques like <strong>exponential backoff</strong> and <strong>jitter</strong>.</p><p><strong>Exponential Backoff.</strong> Instead of retrying immediately at a constant rate, exponential backoff waits progressively longer between attempts. For example, wait 1 second before the first retry, 2 seconds before the next, then 4 seconds, then 8, and so on. This gives the failing service time to recover and prevents slamming it with rapid-fire calls. It&#8217;s like knocking on a door: if no one answers, you wait a bit longer before knocking again. This pattern is extremely common for handling transient failures gracefully<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>.</p><p><strong>Jitter (Random Delay).</strong> A big problem is when many clients retry in sync &#8211; even with backoff, if they all wait 1, 2, 4, 8 seconds, they&#8217;ll still line up and hit at once. <strong>Jitter</strong> introduces randomness to break the synchronization. For instance, if backoff says &#8220;<em>wait 2 seconds</em>&#8221;, each client actually waits a random time up to 2 seconds (maybe one waits 1.7s, another 2.3s). This <strong>spreads out the retries</strong> and avoids a thundering herd. Jitter is like adding a small random delay to your next knock on the door, so that a whole crowd of people won&#8217;t all knock in unison.</p><p>With <strong>exponential backoff and jitter</strong>, retries become polite and <em>intelligent</em>. They exponentially decrease the retry rate under duress and randomize the timing so that servers aren&#8217;t overwhelmed. Most cloud SDKs and HTTP clients implement these patterns by default (e.g., the AWS SDK uses capped exponential backoff with jitter to protect services).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YJdP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b7019fc-0c0a-420e-9a10-6be3b495bb9f_1498x665.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YJdP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b7019fc-0c0a-420e-9a10-6be3b495bb9f_1498x665.jpeg 424w, https://substackcdn.com/image/fetch/$s_!YJdP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b7019fc-0c0a-420e-9a10-6be3b495bb9f_1498x665.jpeg 848w, https://substackcdn.com/image/fetch/$s_!YJdP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b7019fc-0c0a-420e-9a10-6be3b495bb9f_1498x665.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!YJdP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b7019fc-0c0a-420e-9a10-6be3b495bb9f_1498x665.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YJdP!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b7019fc-0c0a-420e-9a10-6be3b495bb9f_1498x665.jpeg" width="1200" height="532.4175824175824" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6b7019fc-0c0a-420e-9a10-6be3b495bb9f_1498x665.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:646,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:84782,&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://iam.slys.dev/i/176729895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b7019fc-0c0a-420e-9a10-6be3b495bb9f_1498x665.jpeg&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_!YJdP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b7019fc-0c0a-420e-9a10-6be3b495bb9f_1498x665.jpeg 424w, https://substackcdn.com/image/fetch/$s_!YJdP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b7019fc-0c0a-420e-9a10-6be3b495bb9f_1498x665.jpeg 848w, https://substackcdn.com/image/fetch/$s_!YJdP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b7019fc-0c0a-420e-9a10-6be3b495bb9f_1498x665.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!YJdP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b7019fc-0c0a-420e-9a10-6be3b495bb9f_1498x665.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>Finally, it&#8217;s critical to <strong>limit retries</strong>. Have a maximum number of attempts or a total timeout budget. Unbounded retries can loop forever or until the system collapses &#8211; definitely an anti-pattern. Many systems also <strong>differentiate errors</strong>: for example, do not retry on a 400 Bad Request (the request is invalid and won&#8217;t magically succeed later). Save retries for the <em>maybe-fixable</em> errors like timeouts, 5xx server errors, or network glitches.</p><blockquote><p><strong>Metaphor:</strong> Don&#8217;t shout the same question every second to someone who didn&#8217;t hear you. If you get silence or a muffled answer, pause. Wait a bit longer each time, and try again &#8211; <strong>gently</strong>. By raising your voice gradually and adding a random pause, you avoid becoming part of a chaotic shouting chorus.</p></blockquote><h1>&#9889; Circuit breaker &#8212; knowing when to stop trying</h1><p>If retries are the first line of defense, the <strong>circuit breaker</strong> pattern is the emergency stop. Sometimes the most resilient thing you can do is <strong>stop</strong> calling a failing service and give it time to heal. A circuit breaker in software plays a similar role to one in your house&#8217;s electrical panel: when too many failures occur, it &#8220;<em>trips</em>&#8221; to prevent further damage.</p><p>Imagine a downstream service that has been timing out for dozens of requests in a row. Instead of blindly retrying again and again, a smart client will <strong>flip the circuit open</strong>: it refuses to send more requests for a short period. This prevents wasting resources on doomed attempts and stops adding load to a service that&#8217;s already in trouble. As Martin Fowler put it, without circuit breakers a chain of failures can consume critical resources and lead to a <strong>cascading collapse</strong> of multiple systems<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>. The circuit breaker breaks the chain.</p><p>A typical software circuit breaker has three states:</p><ol><li><p><strong>Closed (normal operation):</strong> Everything is working, so the circuit is closed and calls flow normally. The client keeps a <strong>failure count</strong>. As long as calls succeed (or failures stay below a threshold), the breaker remains closed. It&#8217;s like the switch is &#8220;<em>on</em>&#8221; allowing electricity (requests) through.</p></li><li><p><strong>Open (tripped):</strong> After a certain number of failures in a row (say 5 failures within a minute), the breaker &#8220;<em>trips</em>&#8221; open. In the Open state, <strong>no calls are made at all</strong> - the client fails fast, immediately returning an error or fallback response without even attempting the operation. It&#8217;s as if the switch is &#8220;<em>off</em>&#8221; - requests get short-circuited. The open state usually lasts for a predefined timeout interval. The idea is to <strong>stop hammering the failing service</strong> and give it a break<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a>.</p></li><li><p><strong>Half-open (probe/test):</strong> After the open timeout expires, the circuit goes half-open. In this state, the client will <strong>allow a limited number of test requests</strong> to see if the service has recovered. It&#8217;s like cautiously closing the circuit just a little to test the waters. If a test request succeeds, that&#8217;s a good sign - the breaker may reset to Closed (fully close the circuit) and resume normal operations, resetting the failure counter. If a test request fails, the breaker snaps back to Open and the timeout period starts over. This prevents a flapping service from getting flooded immediately as it comes back online.</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_!sczj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813748a5-3353-4ee5-99ba-1941f234e06c_1375x590.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sczj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813748a5-3353-4ee5-99ba-1941f234e06c_1375x590.jpeg 424w, https://substackcdn.com/image/fetch/$s_!sczj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813748a5-3353-4ee5-99ba-1941f234e06c_1375x590.jpeg 848w, https://substackcdn.com/image/fetch/$s_!sczj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813748a5-3353-4ee5-99ba-1941f234e06c_1375x590.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!sczj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813748a5-3353-4ee5-99ba-1941f234e06c_1375x590.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sczj!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813748a5-3353-4ee5-99ba-1941f234e06c_1375x590.jpeg" width="1200" height="514.9090909090909" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/813748a5-3353-4ee5-99ba-1941f234e06c_1375x590.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:590,&quot;width&quot;:1375,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:58040,&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://iam.slys.dev/i/176729895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813748a5-3353-4ee5-99ba-1941f234e06c_1375x590.jpeg&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_!sczj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813748a5-3353-4ee5-99ba-1941f234e06c_1375x590.jpeg 424w, https://substackcdn.com/image/fetch/$s_!sczj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813748a5-3353-4ee5-99ba-1941f234e06c_1375x590.jpeg 848w, https://substackcdn.com/image/fetch/$s_!sczj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813748a5-3353-4ee5-99ba-1941f234e06c_1375x590.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!sczj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813748a5-3353-4ee5-99ba-1941f234e06c_1375x590.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>These states and transitions ensure that once a service starts consistently failing, we <strong>stop persistent retries</strong> (which were just hurting) and periodically check if it&#8217;s okay again. The pattern improves overall <strong>stability and responsiveness</strong> - clients don&#8217;t hang waiting for timeouts on every request when the service is known to be down; they fail fast or fall back to a default behavior. Meanwhile, the failing component gets breathing room to recover instead of being DDoS&#8217;ed by its own clients.</p><blockquote><p><strong>Metaphor:</strong> The circuit breaker is like a friend who says: <em>&#8220;They&#8217;re not answering and must be overwhelmed. Let&#8217;s stop calling them for a while - they clearly need time to recover.&#8221;</em> It&#8217;s the voice of reason that prevents you from redialing a number 100 times in a minute. Only after a pause will your friend say, <em>&#8220;Okay, try again now and see if they pick up.&#8221;</em> If they do, great! If not, wait a bit longer and try later.</p></blockquote><p>The insight here is that <strong>persistence without awareness isn&#8217;t resilience - it&#8217;s self-sabotage</strong>. A robust system needs to know when to <strong>quit</strong> and when to <strong>retry</strong>. Circuit breakers add that awareness by monitoring error rates. They essentially enforce the rule: <em>&#8220;Don&#8217;t keep doing the same failing thing over and over&#8221;.</em> Instead, fail fast and handle it gracefully (perhaps return a cached response or a friendly error to the user), and try again later when there&#8217;s a chance of success.</p><p>Most modern frameworks and libraries have circuit breaker implementations (<em>Netflix&#8217;s Hystrix</em> popularized this, and tools like <em>Resilience4j</em> in Java or <em>Polly</em> in .NET provide them). They typically allow configuration of the failure threshold, open-state timeout, and half-open trial settings. Additionally, breakers often work in tandem with retries: e.g., you might retry a few times on an error, but if failures persist, open the circuit. In fact, <strong>combining Retries + Circuit Breaker</strong> is common &#8211; retry a transient error, but if errors keep happening, stop trying for a bit.</p><h1>&#129518; Idempotency &#8212; doing the same thing twice without breaking anything</h1><p>Retries and circuit breakers help manage <em>when</em> and <em>how</em> to repeat operations safely. But there&#8217;s a deeper prerequisite for safe retries: the operation itself must be <strong>safe to repeat</strong>. This is where <strong>idempotency</strong> comes in.</p><p>An operation is <strong>idempotent</strong> if performing it multiple times has the <strong>same effect</strong> as doing it once. If you send the same request twice, an idempotent service will not create a mess or unintended side effects. The classic example: <strong>setting a value vs. incrementing a value</strong>.</p><p><strong>Idempotent example:</strong> &#8220;<em>Set user&#8217;s balance to $100</em>&#8221;. Do it once, the balance is $100. Do it five times, the balance is still $100. No harm in the repeats.</p><p><strong>Non-idempotent example:</strong> &#8220;<em>Add $100 to user&#8217;s balance</em>&#8221;. Do it once, balance increases by $100. Do it five times (accidentally), and you&#8217;ve added $500 - likely not what you intended!</p><p>In formal terms, <em>if </em><code>f(x)</code><em> is an idempotent operation, then </em><code>f(f(x)) = f(x)</code>. The outcome doesn&#8217;t change after the first application. <strong>Repeating it doesn&#8217;t amplify the effect</strong>.</p><p>Why is this so crucial? Because in distributed systems, you <strong>often can&#8217;t be sure if a request succeeded or not</strong>, so you may need to retry - and that retry should not cause bad side effects. If an operation is not idempotent, even a single retry can produce incorrect results. Think of charging a credit card, sending an email, or creating a user account - if those actions are duplicated, you get double charges, duplicate emails, or multiple accounts for the same user. Not good.</p><p><strong>Key idea:</strong> Retries are safe <em>only if</em> the operation is idempotent (or the system has other ways to avoid double-processing). This is why many APIs that involve side effects are designed to be idempotent or provide idempotency mechanisms. For instance, HTTP <strong>GET</strong> is by definition idempotent (it&#8217;s just a read), and HTTP <strong>PUT</strong> is generally designed to be idempotent (replace resource state), whereas <strong>POST</strong> often is not (create a new resource, which if repeated creates duplicates).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!u1u_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6df25c67-953e-4230-8d04-6bcdcfdd1a10_1262x1205.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!u1u_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6df25c67-953e-4230-8d04-6bcdcfdd1a10_1262x1205.jpeg 424w, https://substackcdn.com/image/fetch/$s_!u1u_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6df25c67-953e-4230-8d04-6bcdcfdd1a10_1262x1205.jpeg 848w, https://substackcdn.com/image/fetch/$s_!u1u_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6df25c67-953e-4230-8d04-6bcdcfdd1a10_1262x1205.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!u1u_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6df25c67-953e-4230-8d04-6bcdcfdd1a10_1262x1205.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!u1u_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6df25c67-953e-4230-8d04-6bcdcfdd1a10_1262x1205.jpeg" width="728" height="695.1188589540412" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6df25c67-953e-4230-8d04-6bcdcfdd1a10_1262x1205.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:1205,&quot;width&quot;:1262,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:134357,&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://iam.slys.dev/i/176729895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6df25c67-953e-4230-8d04-6bcdcfdd1a10_1262x1205.jpeg&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_!u1u_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6df25c67-953e-4230-8d04-6bcdcfdd1a10_1262x1205.jpeg 424w, https://substackcdn.com/image/fetch/$s_!u1u_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6df25c67-953e-4230-8d04-6bcdcfdd1a10_1262x1205.jpeg 848w, https://substackcdn.com/image/fetch/$s_!u1u_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6df25c67-953e-4230-8d04-6bcdcfdd1a10_1262x1205.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!u1u_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6df25c67-953e-4230-8d04-6bcdcfdd1a10_1262x1205.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>So how do we achieve idempotency in practice? A few common techniques and patterns:</p><p><strong>Unique Request Identifiers (Idempotency keys):</strong> The client generates a unique token (say a UUID) for each logical operation and sends it with the request. The server keeps track of which tokens it has seen. If a duplicate request with the same token arrives (e.g., due to a retry), the server recognizes it and <strong>skips the duplicate action</strong>, or simply returns the same result as the original request without performing the action again<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-9" href="#footnote-9" target="_self">9</a>. For example, payment APIs (Stripe, PayPal, etc.) let you provide an idempotency key so that if the network flakes out after you hit &#8220;<em>Pay</em>&#8221;, you can safely retry with the same key and not charge the customer twice. AWS uses this approach widely - many AWS APIs (like EC2 instance creation) accept a client token, and if the same token is reused, AWS knows <em>&#8220;Ah, I&#8217;ve already handled this request&#8221;</em>. This shifts the ambiguity of &#8220;<em>did it succeed or not?</em>&#8221; into a clear record on the server side.</p><p><strong>Deduplication stores / logs:</strong> The server can record the outcome of operations in a database or log keyed by the operation&#8217;s unique ID. For example, a service might have a <code>processed_requests</code> table where it inserts the request ID when processing a transaction. If it receives the same ID again, it checks the table, sees it&#8217;s already processed, and aborts or returns the previous result<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-10" href="#footnote-10" target="_self">10</a>. This pattern is common in message processing: consumers keep track of message IDs they&#8217;ve seen to avoid processing the same message twice. The storage could be an in-memory cache, a SQL table, or even attaching identifiers to the resource created (like tagging a database entry with the request ID that created it).</p><p><strong>Designing APIs for idempotency:</strong> Sometimes it&#8217;s as simple as choosing the right operation semantics. If you design an API call as &#8220;<em><strong>create or return existing</strong></em>&#8221; (like a put-if-absent), it can be made idempotent. For instance, an API might treat duplicate create requests as a no-op after the first time. Another approach is using <strong>PUT vs. POST</strong> semantics: e.g., <code>PUT /orders/{id}</code> with the same order ID can be made to either create the order if not exists or return the existing one if it does, ensuring that retrying the PUT doesn&#8217;t create multiple orders. Good API design encourages making operations idempotent whenever possible. In fact, one of Amazon&#8217;s best practices is: <em>&#8220;design APIs to be idempotent, meaning they can be safely retried&#8221;.</em></p><p><strong>Potency by &#8220;</strong><em><strong>Replace</strong></em><strong>&#8221; Instead of &#8220;</strong><em><strong>Add</strong></em><strong>&#8221;:</strong> As mentioned, structure operations as <strong>set to a value</strong> rather than <strong>add a value</strong>, or <strong>idempotent upsert</strong> instead of insert. For example, if an event says &#8220;<em>user paid $100 on order #123</em>&#8221;, record that specific event once rather than something like &#8220;<em>increase balance by 100</em>&#8221; which can&#8217;t be repeated. In databases, use <strong>UPSERT</strong> (update-or-insert) so that applying the same record twice results in one record (updated) not duplicates. Idempotent design often requires thinking in terms of final state (&#8220;<em>ensure it ends up like this</em>&#8221;) rather than one-time actions (&#8220;<em>do this once</em>&#8221;).</p><p>It&#8217;s worth noting that idempotency sometimes requires <strong>extra work</strong> on the backend - keeping logs of request IDs, handling more complex logic - but it <em>significantly improves reliability</em>. It removes ambiguity. In our initial story, the reason it&#8217;s a gray zone is because the system wasn&#8217;t fully idempotent from the user&#8217;s perspective. The payment went through (state changed) but the website didn&#8217;t see the result, and it couldn&#8217;t safely retry the exact operation without risking a double charge. With an idempotent design (say the site had sent a charge request with an idempotency key), the client could retry the charge call safely - the payment service would say &#8220;<em>I already did that, here&#8217;s the result, no double charge</em>&#8221;.</p><blockquote><p><strong>Metaphor:</strong> Idempotency is like saying, <em>&#8220;I already told you once - doing it again shouldn&#8217;t change the outcome&#8221;.</em> If you ask a friend to turn off the light, and they did it, asking again won&#8217;t plunge the room into <em>double</em> darkness - it&#8217;s just off. But if you asked them to &#8220;<em>turn the dimmer down by 20%</em>&#8221;, asking twice would make it twice as dark. Idempotency is preferring the first style of request (&#8220;<em>set it to this state</em>&#8221;) so that repeating the request doesn&#8217;t cause chaos.</p></blockquote><p>Before we move on, an important link: <strong>Idempotency + Retries + Circuit Breakers</strong> all dance together. Idempotency makes retries safe, and circuit breakers stop futile retries from overwhelming anyone. In the next section, we&#8217;ll see how these patterns combine to create a system that can <em>heal itself</em> in the face of failure.</p><h2>&#129504; Putting it together &#8212; a system that heals itself</h2><p>Let&#8217;s walk through a failure scenario and see how <strong>retries, circuit breakers, and idempotency complement each other</strong> to save the day:</p><p><strong>Scenario:</strong> You have a service <code>A</code> (perhaps a frontend service) that calls service <code>B</code> (say, a payment service). A user initiates an action that triggers <code>A -&gt; B</code> call.</p><ul><li><p><strong>Step 1: Initial failure and retry.</strong> Service B doesn&#8217;t respond (maybe it&#8217;s slow or temporarily down). Service A&#8217;s request to B <strong>times out</strong>. Now, A doesn&#8217;t immediately give up; it knows this could be a transient fault. Thanks to a retry policy, A <em>retries the request</em> to B after, say, 1 second. Importantly, the operation is designed to be <strong>idempotent</strong> - perhaps it includes a unique transaction ID for this payment. Therefore, if B actually got the first request and processed it, the second request will be recognized as a duplicate and ignored (or B will return the previous result). If the first request never hit B, then the retry is truly needed. Either way, retrying won&#8217;t cause a double effect. The retry gives B a second chance to do the work, in case the issue was a fluke.</p></li><li><p><strong>Step 2: Multiple failures and circuit open.</strong> Unfortunately, service B is having a bad time - it&#8217;s still unresponsive on the retry attempt. A times out again. At this point, our retry policy might attempt again with exponential backoff (wait 2 seconds, try again). Let&#8217;s say we try a couple of times, but all attempts within a short window keep failing. Now the <strong>circuit breaker</strong> logic kicks in: after, say, 3 failed tries in a row, A&#8217;s circuit breaker for calls to B <strong>trips open</strong>. This means <em>for the next 30 seconds, A will not send any requests to B at all</em>. Any user actions that would call B immediately get a failure or fallback response. This sounds harsh, but it prevents A from continuously piling requests onto B when B is definitely down. It&#8217;s containing the damage. Users might get a quick error message like &#8220;<em>Service unavailable, please try after some time</em>&#8221; <em>immediately</em> rather than staring at spinners that will inevitably timeout. Meanwhile, B gets a breather without traffic.</p></li><li><p><strong>Step 3: Self-healing test (Half-open).</strong> After the cooldown period (30s) passes, A&#8217;s circuit breaker moves to <strong>half-open</strong>. It will now allow a <em>limited</em> test through - maybe it lets one or a few requests go to B instead of all of them. So the next user action triggers a real call to B. If B is still down and this call fails, the breaker snaps back to Open and the cycle repeats (wait longer). But suppose by now the team fixed B or it recovered on its own. That test request <strong>succeeds</strong>. Great! The circuit breaker takes this as a signal that B is healthy again. It transitions back to <strong>Closed</strong> state, resetting the failure count. Now A resumes normal operations and starts sending all requests to B again.</p></li><li><p><strong>Step 4: Graceful recovery and continuation.</strong> Traffic flows normally; the system healed itself. Users maybe noticed a brief hiccup, but the system didn&#8217;t meltdown. If idempotency keys were in play, any retried operations during the half-open testing are safe and don&#8217;t double-execute. If some actions couldn&#8217;t be completed while the circuit was open, perhaps A queued them or returned an error that the user can retry. But importantly, when B came back, it wasn&#8217;t immediately crushed by a thundering herd, because the circuit breaker let only a trickle of requests through to confirm health before fully opening the gates.</p></li></ul><p>Throughout this dance, <strong>observability</strong> (which we&#8217;ll discuss next) would be tracking what&#8217;s happening: metrics like &#8220;<em>how many retries occurred</em>&#8221; or &#8220;<em>is the circuit breaker open right now</em>&#8221; would alert engineers that B was failing and recovering.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WNBS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2693d111-c9df-44c3-b59b-9b65a4198d89_1503x939.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WNBS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2693d111-c9df-44c3-b59b-9b65a4198d89_1503x939.jpeg 424w, https://substackcdn.com/image/fetch/$s_!WNBS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2693d111-c9df-44c3-b59b-9b65a4198d89_1503x939.jpeg 848w, https://substackcdn.com/image/fetch/$s_!WNBS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2693d111-c9df-44c3-b59b-9b65a4198d89_1503x939.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!WNBS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2693d111-c9df-44c3-b59b-9b65a4198d89_1503x939.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WNBS!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2693d111-c9df-44c3-b59b-9b65a4198d89_1503x939.jpeg" width="1200" height="750" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2693d111-c9df-44c3-b59b-9b65a4198d89_1503x939.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:910,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:109018,&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://iam.slys.dev/i/176729895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2693d111-c9df-44c3-b59b-9b65a4198d89_1503x939.jpeg&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_!WNBS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2693d111-c9df-44c3-b59b-9b65a4198d89_1503x939.jpeg 424w, https://substackcdn.com/image/fetch/$s_!WNBS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2693d111-c9df-44c3-b59b-9b65a4198d89_1503x939.jpeg 848w, https://substackcdn.com/image/fetch/$s_!WNBS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2693d111-c9df-44c3-b59b-9b65a4198d89_1503x939.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!WNBS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2693d111-c9df-44c3-b59b-9b65a4198d89_1503x939.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><strong>Insight:</strong> Resilient systems aren&#8217;t perfect; they <em>expect</em> things to break. The magic is in <strong>how they fail</strong>:</p><ul><li><p>They <em>fail fast</em> when needed (circuit open),</p></li><li><p>They <em>retry calmly</em> when it&#8217;s worth it (controlled retries with backoff),</p></li><li><p>They <em>avoid dangerous side effects</em> (idempotent operations),</p></li><li><p>And they <em>recover automatically</em> (circuit half-open to closed transition).</p></li></ul><p>In essence, the system &#8220;<em>takes a deep breath</em>&#8221; during an outage: it stops pressing on the broken part, waits a bit, tries again carefully, and then continues as normal. This is often referred to as <strong>graceful degradation</strong> and self-healing. Users might see slightly slower responses or a friendly error message, but not a catastrophic failure or inconsistent data.</p><blockquote><p><strong>Real-world example:</strong> Netflix (which pioneered a lot of these patterns) will show you older cached content or a &#8220;<em>Netflix is temporarily unavailable</em>&#8221; screen rather than just crash, if its backend services are failing. After a few seconds, it will transparently retry connecting. You, as the user, are only mildly inconvenienced instead of being completely unable to use the service.</p></blockquote><h1>&#129513; Observability &#8212; seeing failures before users do</h1><p>All these resilience patterns (retries, breakers, idempotency) work best when you can <strong>see what&#8217;s happening inside your system</strong>. That&#8217;s where <strong>observability</strong> comes in. Observability means having the right data (logs, metrics, traces) to understand the system&#8217;s behavior and catch issues early. It&#8217;s the <strong>nervous system</strong> of your software that lets you sense trouble and react before users are impacted.</p><p><strong>You can&#8217;t fix what you can&#8217;t see.</strong> Instrument your system to track the crucial signals:</p><ul><li><p><strong>Metrics:</strong> Define and watch metrics that relate to reliability. For example:</p><ul><li><p><strong>Retry counts and rates</strong> - how often are retries happening? A spike in retries might mean a downstream service is flaking out.</p></li><li><p><strong>Circuit breaker state</strong> - expose whether the circuit is open, half-open, or closed, and count how many times it opens. If a circuit breaker is tripping frequently, something is wrong downstream (or your threshold is too sensitive).</p></li><li><p><strong>Error rates and latency</strong> - increased error percentage or slow responses can warn of impending failures (which might trigger retries or breaker trips).</p></li><li><p><strong>Idempotency rejections or duplicates</strong> - if you track how many duplicate requests were detected and suppressed, it could indicate clients are retrying a lot (or misbehaving).</p></li></ul><p>Use tools like <em>Prometheus</em> or <em>CloudWatch</em> to collect these metrics. Set up <strong>alerts</strong> when thresholds are breached (e.g., alert if circuit opens more than X times in an hour, or if retry rate &gt; Y%). A well-tuned alert can notify you of a failing dependency <em>before</em> users start calling support.</p></li><li><p><strong>Logging with context (Correlation IDs):</strong> When errors do occur, logs are invaluable for debugging - but only if you can trace what happened. Implement <strong>correlation IDs</strong> (also called trace IDs) that flow through your system: from the user request through all the microservices it touches. By logging the ID with every error or important event, you can reconstruct the path of a request. For instance, if a user&#8217;s payment failed after 3 retries, you&#8217;d see one correlation ID with logs from service A&#8217;s retries, and maybe logs in service B if it was reached. Structured logs (JSON logs with key fields like <code>requestId</code>, <code>userId</code>, etc.) make it easier to filter and search. As an example, a log entry might contain <code>correlationId: abc-123</code> along with an error message &#8211; using that ID you pull all related logs across services. This way, when something goes wrong, you don&#8217;t have a needle in a haystack; you have a thread to pull.</p></li><li><p><strong>Distributed Tracing:</strong> Traces are like an x-ray of how a single transaction flows through multiple services. Tools like <em><strong>OpenTelemetry</strong></em> (with <em>Jaeger</em>, <em>Zipkin</em>, etc.) allow you to capture timings and causality: e.g., Service A called B, which called C. If a request is slow or fails, a trace can pinpoint where. Maybe 95% of the time was spent waiting on a database call in service C, or maybe service B had a 5-second pause. Tracing lets you visualize that call graph and timings. This is incredibly useful for failures that aren&#8217;t obvious - for example, a trace might reveal that your retries are all hitting the same instance of a service that is down, whereas if the load balancer sent one to a different instance it would work (maybe indicating you need smarter retry logic or that a whole zone is down). Many organizations sample traces and have dashboards to see error traces in near-real-time.</p></li><li><p><strong>Visualization &amp; Dashboards:</strong> Platforms like <em>Grafana</em> can chart your metrics over time - you might see a spike in &#8220;<em>retry_count</em>&#8221; or &#8220;<em>circuit_breaker_open{service=B}</em>&#8221; on a graph, correlating with a dip in throughput. Dashboards help connect the dots, and when an incident happens, you can quickly assess which part of the system is the bottleneck or failing.</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_!fBBJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e824720-7c28-412f-bc48-1d04c9015c0d_1343x870.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fBBJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e824720-7c28-412f-bc48-1d04c9015c0d_1343x870.jpeg 424w, https://substackcdn.com/image/fetch/$s_!fBBJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e824720-7c28-412f-bc48-1d04c9015c0d_1343x870.jpeg 848w, https://substackcdn.com/image/fetch/$s_!fBBJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e824720-7c28-412f-bc48-1d04c9015c0d_1343x870.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!fBBJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e824720-7c28-412f-bc48-1d04c9015c0d_1343x870.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fBBJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e824720-7c28-412f-bc48-1d04c9015c0d_1343x870.jpeg" width="728" height="471.60089352196576" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9e824720-7c28-412f-bc48-1d04c9015c0d_1343x870.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:870,&quot;width&quot;:1343,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:106607,&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://iam.slys.dev/i/176729895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e824720-7c28-412f-bc48-1d04c9015c0d_1343x870.jpeg&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_!fBBJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e824720-7c28-412f-bc48-1d04c9015c0d_1343x870.jpeg 424w, https://substackcdn.com/image/fetch/$s_!fBBJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e824720-7c28-412f-bc48-1d04c9015c0d_1343x870.jpeg 848w, https://substackcdn.com/image/fetch/$s_!fBBJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e824720-7c28-412f-bc48-1d04c9015c0d_1343x870.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!fBBJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e824720-7c28-412f-bc48-1d04c9015c0d_1343x870.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>To sum up observability: <strong>collect, instrument, and monitor</strong> everything important. It&#8217;s not just for reacting, but proacting. If you notice a retry storm building or a database getting slower, you can intervene (maybe increase capacity or roll out a fix) <em>before</em> it full-on crashes. Observability is the difference between <em>randomly discovering failures when users scream</em> versus <em>detecting a problem at 3 AM and fixing it before the morning rush</em>.</p><blockquote><p>As a metaphor: Observability is the <strong>dashboard in your car</strong> with the check-engine lights and temperature gauges. You want to know if the engine is overheating <em>before</em> it explodes, and you want a speedometer to know if you&#8217;re going too fast around those reliability curves. Without a dashboard, you&#8217;re driving blind. Without software observability, you&#8217;re flying blind in production.</p></blockquote><p>In practice, implement basic monitoring on all these resilience mechanisms. Track how many times you are retrying, how long those retries wait, when circuits open/close, and any anomalies with duplicate request handling. Many libraries emit events for these (e.g., <em>Hystrix</em> had a stream of metrics you could tap into). Use that data to continually tune your thresholds too - maybe your circuit breaker is too sensitive (opening on one failure) or not sensitive enough (waiting until 100 failures). Observability guides those improvements.</p><div><hr></div><p><strong>&#128172; I&#8217;d really love to hear from you.</strong><br>What part of retry logic, circuit breakers, or idempotency clicked the most for you?</p><p>Or - what still feels confusing or &#8220;<em>magical</em>&#8221;?</p><p>Drop a comment below. I respond to everyone, and your questions often inspire new posts I wouldn&#8217;t have thought of on my own.</p><p>Don&#8217;t be shy - this is a friendly corner of the internet. &#128578;</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/how-systems-handle-failure-retries/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/how-systems-handle-failure-retries/comments"><span>Leave a comment</span></a></p><div><hr></div><h2>&#9878;&#65039; Failure design as a mindset</h2><p>Let&#8217;s step back from the technical details and talk philosophy. <strong>Building reliable systems isn&#8217;t about never making mistakes or preventing every failure.</strong> It&#8217;s about <strong>expecting failures</strong>, accepting that they <em>will</em> happen, and designing your system&#8217;s behavior in those moments so that nothing catastrophic occurs. In other words, <strong>embrace the chaos, but be ready for it</strong>.</p><p>Think of it this way: if you deploy code to thousands of servers, at any given time <em>something</em> is probably slightly broken. Hard drives crash, networks partition, a bug slips through. A novice might react with &#8220;<em>Oh no, everything must be 100% perfect or we&#8217;re doomed!</em>&#8221; But an expert mindset is, &#8220;<em>Failures will happen - let&#8217;s make sure when they do, it&#8217;s no big deal</em>&#8221;. This mindset shift is at the heart of Site Reliability Engineering (SRE) principles at companies like Google. They even intentionally introduce failures (chaos engineering) to test that their systems can handle them.</p><p>So, design for <strong>graceful failure</strong>. It&#8217;s an art:</p><ul><li><p><strong>Expect failures</strong>: Every external call might not return. Every write to disk might fail. Plan for it.</p></li><li><p><strong>Embrace them in testing</strong>: Simulate random outages or slowdowns and verify your system stays responsive (even if in degraded mode).</p></li><li><p><strong>Design recovery paths</strong>: As we&#8217;ve seen - wait, retry, timeout, failover, fallback. Always ask, &#8220;<em>If this part breaks, what will the user experience? Can we do better?</em>&#8221;</p></li><li><p><strong>Stay calm under pressure</strong>: A good system doesn&#8217;t panic when things break. It has that &#8220;<em>deep breath</em>&#8221; behavior we described. No frantic infinite loops or cascades, just controlled responses.</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_!DwK8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e9328f7-9410-4d3e-a7fc-9825f58075db_1397x670.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DwK8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e9328f7-9410-4d3e-a7fc-9825f58075db_1397x670.jpeg 424w, https://substackcdn.com/image/fetch/$s_!DwK8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e9328f7-9410-4d3e-a7fc-9825f58075db_1397x670.jpeg 848w, https://substackcdn.com/image/fetch/$s_!DwK8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e9328f7-9410-4d3e-a7fc-9825f58075db_1397x670.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!DwK8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e9328f7-9410-4d3e-a7fc-9825f58075db_1397x670.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DwK8!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e9328f7-9410-4d3e-a7fc-9825f58075db_1397x670.jpeg" width="1200" height="575.5189692197566" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1e9328f7-9410-4d3e-a7fc-9825f58075db_1397x670.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:670,&quot;width&quot;:1397,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:82914,&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://iam.slys.dev/i/176729895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e9328f7-9410-4d3e-a7fc-9825f58075db_1397x670.jpeg&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_!DwK8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e9328f7-9410-4d3e-a7fc-9825f58075db_1397x670.jpeg 424w, https://substackcdn.com/image/fetch/$s_!DwK8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e9328f7-9410-4d3e-a7fc-9825f58075db_1397x670.jpeg 848w, https://substackcdn.com/image/fetch/$s_!DwK8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e9328f7-9410-4d3e-a7fc-9825f58075db_1397x670.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!DwK8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e9328f7-9410-4d3e-a7fc-9825f58075db_1397x670.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>One great quote that encapsulates this: <em>&#8220;The goal isn&#8217;t to prevent all failures, but to handle them so gracefully that users never notice&#8221;</em>. A perfectly resilient system could be on fire behind the scenes, but from the user&#8217;s perspective, it&#8217;s running smoothly or only minimally affected. Maybe some features are temporarily limited, or a loading spinner takes a second longer, but the overall service remains available.</p><p>In practical terms, this mindset means investing in things like <strong>redundancy</strong> (so one failure doesn&#8217;t take you completely down), <strong>graceful degradation</strong> (if one component is offline, others still provide value), and <strong>automatic recovery</strong> (services restart, self-heal, reroute traffic, etc.). It also means not treating the engineers who caused a failure as villains &#8211; instead, treat each incident as a learning exercise to improve the system (this is more on the human/process side, blameless post-mortems, etc., but it&#8217;s part of the reliability culture).</p><p>To conclude this section, let&#8217;s re-imagine our opening story with a happier ending: You click &#8220;<em>Buy Now</em>&#8221;. The payment service <em>does</em> charge your card but fails to respond. The website&#8217;s client side has a <strong>retry with an idempotency key</strong> in place, so it automatically tries again after a moment. The payment service sees the duplicate request, realizes it&#8217;s the same transaction, and instead of charging twice, it returns &#8220;<em>OK, got it</em>&#8221; along with the receipt from the first charge. The website gets the successful response on the second attempt and shows &#8220;<em>Order confirmed!</em>&#8221; to you. You never even knew there was a hiccup. That&#8217;s a failure that was made uneventful - by design.</p><div><hr></div><p><strong>&#10084;&#65039; If this post helped you understand failure handling in distributed systems, consider sharing it.</strong><br>It helps more people - especially beginners - discover practical explanations without the gatekeeping.</p><p>Sharing the post is one of the best ways you can support my work. Thank you. &#128588;</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://iam.slys.dev/p/how-systems-handle-failure-retries?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://iam.slys.dev/p/how-systems-handle-failure-retries?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h2>&#9997;&#65039; Summary</h2><p>We covered a lot, so let&#8217;s summarize the key points of how systems handle failure gracefully:</p><ul><li><p><strong>Failures happen constantly in distributed systems</strong> - networks glitch, servers crash, messages duplicate. Reliability comes from <em>managing</em> failures, not magically avoiding them. Design your mindset and system with the expectation of failure.</p></li><li><p><strong>Retries</strong> are the first tool to handle transient issues. By retrying operations that might have failed due to a temporary issue, systems can turn flakiness into success. But retries must be used judiciously: apply <strong>exponential backoff</strong> and <strong>jitter</strong> to avoid thundering herds. Always cap the number of retries. Done right, retries significantly boost availability (e.g., achieving that extra &#8220;<em>nine</em>&#8221; of uptime); done wrong, they cause retry storms that <strong>amplify outages</strong>.</p></li><li><p><strong>Circuit Breakers</strong> act as a safety valve. When a service is consistently failing, the circuit breaker &#8220;<em>opens</em>&#8221; to stop further attempts, preventing overload and giving time to recover. It&#8217;s a protective measure to <strong>fail fast</strong> and avoid cascading failures. With closed, open, half-open states, circuit breakers can gracefully recover and resume operations once the failures subside. Think of it as the system knowing when to say &#8220;<em>enough for now</em>&#8221; for its own good.</p></li><li><p><strong>Idempotency</strong> is the unsung hero that ensures retries (and unexpected repeats) don&#8217;t cause chaos. An idempotent operation yields the same result no matter how many times it&#8217;s performed. By designing APIs and actions to be idempotent (using unique request IDs, deduplication, and careful state management), we make safe retries possible. Idempotency prevents the nightmare of duplicate side effects &#8211; no double charges, no extra emails - even if a message or request is processed twice.</p></li><li><p><strong>Together, these techniques form a self-healing system.</strong> Imagine a scenario: a service hiccups, clients retry (thanks to idempotency, without side effects), a persistent failure triggers a circuit breaker to stop the bleeding, and after a pause the system recovers and continues as normal. Each component (retry, breaker, idempotent design) covers a different aspect, and combined they allow a system to withstand failures large and small without collapsing.</p></li><li><p><strong>Observability</strong> underpins all of this. You need to monitor and measure retries, failures, and recovery actions. Metrics, logs, and traces give you the visibility to tune the system and catch problems early. It&#8217;s how you verify that your retries aren&#8217;t causing harm, or that your circuit breaker isn&#8217;t tripping too often. Observability is your early warning system and debugging aid.</p></li></ul><p>Finally, remember that <strong>resilience is a mindset and a continuous process</strong>. As the saying goes, <em>distributed systems will fail - and that&#8217;s okay</em>. Our job is to build systems that <strong>&#8220;</strong><em><strong>take a deep breath, wait, retry, and carry on</strong></em><strong>&#8221;</strong> when things go wrong. By embracing failure and designing for it, we create software that doesn&#8217;t just avoid crashing, but rather <em>learns to live with failures</em> in a way that users barely notice. That is the art of reliable system design: not preventing rain from ever falling, but carrying an umbrella and wearing waterproof boots so a little rain never stops the parade.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p><a href="https://ptgmedia.pearsoncmg.com/images/9780321943187/samplepages/9780321943187.pdf">The Practice of Cloud System Administration</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p><a href="https://dzone.com/articles/overcoming-the-retry-dilemma-in-distributed-systems">Overcoming the Retry Dilemma in Distributed Systems</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p><a href="https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/">Timeouts, retries, and backoff with jitter</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p><a href="https://estuary.dev/blog/exactly-once-delivery/">What is Exactly-Once Delivery and Why It&#8217;s So Hard to Achieve</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p><a href="https://betterstack.com/community/guides/monitoring/exponential-backoff/">Mastering Exponential Backoff in Distributed Systems</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p><a href="https://martinfowler.com/bliki/CircuitBreaker.html">Circuit Breaker</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p><a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker">Circuit Breaker pattern</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p><a href="https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/">Making retries safe with idempotent APIs</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-9" href="#footnote-anchor-9" class="footnote-number" contenteditable="false" target="_self">9</a><div class="footnote-content"><p><a href="https://temporal.io/blog/error-handling-in-distributed-systems"> Error handling in distributed systems: A guide to resilience patterns </a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-10" href="#footnote-anchor-10" class="footnote-number" contenteditable="false" target="_self">10</a><div class="footnote-content"><p><a href="https://microservices.io/post/microservices/patterns/2020/10/16/idempotent-consumer.html">Handling duplicate messages using the Idempotent consumer pattern</a></p></div></div>]]></content:encoded></item></channel></rss>