<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>RD Blog</title><link>https://rdiachenko.com/</link><description>Notes and essays on distributed systems, LLM inference, and software engineering, from understanding an idea to making it work.</description><generator>Hugo</generator><language>en</language><managingEditor>ruslan@rdiachenko.com (Ruslan Diachenko)</managingEditor><webMaster>ruslan@rdiachenko.com (Ruslan Diachenko)</webMaster><lastBuildDate>Tue, 08 Sep 2026 13:00:00 +0100</lastBuildDate><atom:link href="https://rdiachenko.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Do We Still Understand the Systems We Build?</title><link>https://rdiachenko.com/posts/essays/building-faster-than-we-understand/</link><pubDate>Tue, 08 Sep 2026 13:00:00 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/essays/building-faster-than-we-understand/</guid><description>Agents let us deliver software faster. But that speed can leave us with less understanding and weaker judgement.</description><content:encoded><![CDATA[<p>I&rsquo;ve been thinking about what happens to our understanding of a system when agents allow us to build it much faster. Part of the answer lies in how we used to learn a system while building it.</p>
<h2 id="the-learning-loop-we-lost">
The learning loop we lost
<a href="#the-learning-loop-we-lost" class="heading-anchor" aria-label="Anchor link for: The learning loop we lost">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Writing code without agents took much longer. But that slower process also helped us learn. While implementing a feature, we had to follow the main flows, work across different parts of the codebase, and deal with edge cases. By the time we finished, we usually had a better understanding of how the system worked.</p>
<p>It was not typing the code that gave us this knowledge. It was the <strong>time</strong> we spent inside the system.</p>
<p>Agents have changed this relationship. They can generate and modify code much faster than we can absorb the changes. The speed of production has increased, but the speed at which our brains learn how a system works has not.</p>
<p>We may understand each change when we review it. But then we move on to the next task, the next plan, and the next implementation. Each change makes sense on its own, while our understanding of how all the pieces fit together slowly weakens or becomes outdated.</p>
<h2 id="what-planning-and-review-cannot-replace">
What planning and review cannot replace
<a href="#what-planning-and-review-cannot-replace" class="heading-anchor" aria-label="Anchor link for: What planning and review cannot replace">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>An obvious counterargument is that we were already building software through small pull requests before agents. We were already reviewing changes made by other people and moving from one task to the next.</p>
<p>That&rsquo;s true, but I think there&rsquo;s still an important difference.</p>
<p>When colleagues left comments on our pull requests, we had to understand the feedback, think through the problem, discuss it, and change the code. Sometimes we disagreed. Sometimes the discussion showed that everyone had missed something. The process was slower, but it gave us time to sit with the problem and learn from it.</p>
<p>Now we can pass the same comment directly to an agent. The agent updates the code, we check that the issue is resolved, and everyone moves on. The feedback loop still exists, but we may no longer work through it ourselves. The problem gets fixed, but the learning opportunity is gone.</p>
<p>It doesn&rsquo;t have to be this way. We can still stop and think through the feedback ourselves. But the easiest path is to let the agent deal with it, especially when many other tasks are waiting.</p>
<p>Another counterargument is that we now spend more time writing requirements, specifications, and architecture plans. If we design a change and review its implementation plan, shouldn&rsquo;t we already understand the system?</p>
<p>That&rsquo;s partly true, but planning, implementation, and real-world use give us different kinds of knowledge.</p>
<p>A specification tells us what we want to build. Implementation shows us how that idea fits into the existing codebase. Real-world use shows us whether it actually works.</p>
<p>During implementation, we often discover hidden dependencies, old assumptions, unexpected states, and edge cases that we missed during planning. It&rsquo;s almost impossible to predict everything in advance.</p>
<p>An agent can find these problems too, but we may see only the final solution or a short summary. The information is available, but we may not spend enough time with it to fully understand or retain it.</p>
<h2 id="faster-delivery-weaker-judgement">
Faster delivery, weaker judgement
<a href="#faster-delivery-weaker-judgement" class="heading-anchor" aria-label="Anchor link for: Faster delivery, weaker judgement">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Maybe we&rsquo;re not only losing knowledge of a particular system but also getting less practice using our own technical judgement.</p>
<p>Judgement develops through making decisions, seeing their results, and fixing our mistakes. When we face a challenging problem and ask an agent for an answer before thinking it through ourselves, we skip part of that practice.</p>
<p>The agent may give us a good answer, and we may understand it when we read it. But understanding someone else&rsquo;s reasoning isn&rsquo;t always the same as doing the reasoning ourselves.</p>
<p>I&rsquo;ve noticed this in myself. When I face a hard problem, my first reaction is usually to ask an agent. Thinking it through on my own feels slower and more uncomfortable. My brain naturally looks for a shortcut.</p>
<p>At the same time, agents don&rsquo;t always reduce the number of decisions we need to make. Because they increase the speed of production, they can create many more decisions for us to review. We may write less code ourselves while having to approve more plans, changes, and technical choices throughout the day.</p>
<p>This creates a dangerous cycle. Faster production creates more decisions. More decisions create mental fatigue. Fatigue makes us delegate more thinking to agents. Less practice weakens our judgement, making us depend on agents even more.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/essays/building-faster-than-we-understand/dangerous-cycle-cover_hu_a52c226a3fb65919.webp"
                    media="(max-width: 768px)"
                    width="1120"
                    height="588">
            <source
                    srcset="https://rdiachenko.com/posts/essays/building-faster-than-we-understand/dangerous-cycle-cover_hu_5dee25135bed6221.webp"
                    media="(min-width: 769px)"
                    width="1600"
                    height="840">
            <img
                    src="https://rdiachenko.com/posts/essays/building-faster-than-we-understand/dangerous-cycle-cover_hu_5dee25135bed6221.webp"
                    alt="A closed loop of six stages: faster production, more decisions, mental fatigue, more delegation of thinking, weaker judgement, and more dependence on agents, which returns to faster production."
                    width="1600"
                    height="840"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. A self-reinforcing cycle of agentic development.</small></figcaption></figure>
<p>I often feel more mentally drained at the end of a working day than I did when development moved more slowly. Constantly switching between tasks and reviewing so many decisions doesn&rsquo;t feel sustainable. If we don&rsquo;t intentionally slow down, it&rsquo;s easy to keep generating, keep approving, and gradually lose the ability to judge the work properly.</p>
<h2 id="knowing-when-to-slow-down">
Knowing when to slow down
<a href="#knowing-when-to-slow-down" class="heading-anchor" aria-label="Anchor link for: Knowing when to slow down">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The obvious way to break this cycle is to think first and ask the agent second. We need to form our own view, make some decisions ourselves, and then use the agent to challenge or improve our thinking.</p>
<p>But this is harder than it sounds.</p>
<p>Once agents make faster delivery possible, that pace can quickly become expected. Product teams and management then start planning around it, even though our ability to think, review, and make careful decisions hasn&rsquo;t increased at the same rate.</p>
<p>An engineer who stops to think deeply can start to feel like the bottleneck. The pressure no longer comes only from our own desire to take the easier path. It also comes from an environment that rewards visible output more than careful thinking.</p>
<p>This creates a hidden risk for companies. The same process that increases a company&rsquo;s output may weaken its ability to understand and control what it produces. In the short term, this looks like higher productivity. The cost may become visible only later, when the system behaves unexpectedly and too few people understand it well enough to question what was built or take control.</p>
<p>Maintaining control doesn&rsquo;t mean thinking deeply about every change an agent makes. There are too many, and doing so would remove much of the value agents provide. The challenge is to know where speed is safe and where we still need to slow down and think for ourselves.</p>
<p>Some decisions are local, easy to reverse, and safe to delegate. Others affect the whole system and may be expensive to undo. Those decisions still require independent human judgement.</p>
<p>This is where system design becomes especially important. Decisions about how the parts fit together affect how much the system costs, how secure and reliable it is, how well it performs, and how difficult it will be to change.</p>
<p>The effects of these decisions are often difficult to see immediately. A design can seem reasonable today and still create serious problems months later. The code works, the tests pass, and the feature is released. That doesn&rsquo;t mean the decision was good for the system as a whole.</p>
<p>Agents can help us explore options and identify tradeoffs. But we still need enough understanding to judge their suggestions. If we delegate both the reasoning and the final decision, we risk becoming a proxy between the agent and the codebase. We may approve changes without knowing whether they make sense at all.</p>
<h2 id="the-feedback-we-cannot-speed-up">
The feedback we cannot speed up
<a href="#the-feedback-we-cannot-speed-up" class="heading-anchor" aria-label="Anchor link for: The feedback we cannot speed up">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Even when we give ourselves enough time to think before making an important decision, some answers only come from seeing the system in use.</p>
<p>We need to see how people use it, how it behaves under load, how much it costs, how it fails, and how difficult it is to change later. This is how we learn whether we made a good decision.</p>
<p>Agents can write tests, simulate users, and explore possible failures. All of this can speed up some feedback loops, but it can&rsquo;t show us everything. Real users, production incidents, changing requirements, maintenance, and time still teach us things that are difficult to predict.</p>
<p>Agents can make implementation faster, but we still have to wait for real-world feedback.</p>
<p>If a system changes too quickly, we face two problems. First, we don&rsquo;t have enough time to understand its current state. Second, by the time we learn whether an earlier decision was good, the system may already have changed several times. We may receive feedback about a version of the system that no longer exists.</p>
<p>We can now build systems faster than we can think about them, and change them faster than we can learn from them.</p>
<p>So, do we still understand the systems we build?</p>
<p>We can, but it no longer happens automatically. In the past, our understanding and judgement often developed as we implemented changes, responded to feedback, and saw the results of our decisions. Now we have to make sure those learning loops still happen.</p>
<p>The answer isn&rsquo;t to stop using agents or to make every decision ourselves. We need to know what we can safely delegate. For important decisions, we need to stay involved long enough to learn from the results.</p>
<p>When code becomes easy to generate, producing more of it may no longer be the most valuable skill. What matters more is knowing when to slow down, when to think first, and when the agent&rsquo;s answer isn&rsquo;t enough.</p>
<p>That may be the real moat.</p>
]]></content:encoded></item><item><title>Parse, Don't Validate, in Rust</title><link>https://rdiachenko.com/posts/rust/parse-dont-validate/</link><pubDate>Thu, 06 Aug 2026 12:45:00 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/rust/parse-dont-validate/</guid><description>Validation throws away what it learned. Parsing returns it. In Rust, that moves invariants into the type system, where both developers and coding agents have to respect them.</description><content:encoded><![CDATA[<p>OpenAI published a post about 
<a href="https://openai.com/index/harness-engineering/" target="_blank" rel="nofollow noopener">harness engineering</a>
, describing how they run a codebase written almost entirely by Codex. One rule stood out:</p>
<blockquote>
<p>We require Codex to parse data shapes at the boundary, but are not prescriptive on how that happens (the model seems to like Zod, but we didn&rsquo;t specify that specific library).</p>
</blockquote>
<p>&ldquo;Parse data shapes at the boundary&rdquo; links to Alexis King&rsquo;s 
<a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/" target="_blank" rel="nofollow noopener">Parse, don&rsquo;t validate</a>
. A 2019 idea from Haskell turns out to be one of the rules they rely on to keep an agent-written codebase under control.</p>
<p>The whole idea is what you get back after the check:</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/rust/parse-dont-validate/parse-dont-validate-cover_hu_5dcba0a900c29631.webp"
                    media="(max-width: 768px)"
                    width="1120"
                    height="588">
            <source
                    srcset="https://rdiachenko.com/posts/rust/parse-dont-validate/parse-dont-validate-cover_hu_520302b7a950fee5.webp"
                    media="(min-width: 769px)"
                    width="1600"
                    height="840">
            <img
                    src="https://rdiachenko.com/posts/rust/parse-dont-validate/parse-dont-validate-cover_hu_520302b7a950fee5.webp"
                    alt="Validation returns an empty type and the facts it checked are discarded. Parsing returns a Deploy type that holds those facts."
                    width="1600"
                    height="840"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Validation throws away what it learned. Parsing returns it.</small></figcaption></figure>
<p>Here&rsquo;s what that looks like in Rust. Java gets the first example, because that&rsquo;s where I learned to validate.</p>
<p>All the code in this post is in the 
<a href="https://github.com/rdiachenko/rd-blog/tree/main/parse-dont-validate" target="_blank" rel="nofollow noopener">rd-blog repository</a>
.</p>
<h2 id="validation-in-java">
Validation in Java
<a href="#validation-in-java" class="heading-anchor" aria-label="Anchor link for: Validation in Java">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>If you&rsquo;ve built Spring Boot applications, this should look familiar:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">record</span> <span class="nc">DeployRequest</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Pattern</span><span class="p">(</span><span class="n">regexp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;[a-z0-9-]{3,30}&#34;</span><span class="p">)</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">service</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Pattern</span><span class="p">(</span><span class="n">regexp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;dev|staging|production&#34;</span><span class="p">)</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">environment</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Positive</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">replicas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nd">@PostMapping</span><span class="p">(</span><span class="s">&#34;/deploys&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="n">ResponseEntity</span><span class="o">&lt;?&gt;</span><span class="w"> </span><span class="nf">deploy</span><span class="p">(</span><span class="nd">@Valid</span><span class="w"> </span><span class="nd">@RequestBody</span><span class="w"> </span><span class="n">DeployRequest</span><span class="w"> </span><span class="n">req</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">deployService</span><span class="p">.</span><span class="na">submit</span><span class="p">(</span><span class="n">req</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">ResponseEntity</span><span class="p">.</span><span class="na">accepted</span><span class="p">().</span><span class="na">build</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>There&rsquo;s nothing wrong with this code. The important part is that validation does not change the type.</p>
<p><code>req.service()</code> returns a <code>String</code> before <code>@Valid</code> runs and a <code>String</code> after. A request that passed validation has the same type as one that has not been validated, so nothing in the value itself tells the compiler which one it has.</p>
<p>That tradeoff makes sense in Java, where introducing wrapper types adds more object types and framework friction. In Rust, newtypes are much cheaper, so you can keep the result of validation in the type instead.</p>
<h2 id="validation-vs-parsing">
Validation vs parsing
<a href="#validation-vs-parsing" class="heading-anchor" aria-label="Anchor link for: Validation vs parsing">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Compare these two signatures:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">validate</span><span class="p">(</span><span class="n">req</span>: <span class="kp">&amp;</span><span class="nc">DeployRequest</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">DeployError</span><span class="o">&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">parse</span><span class="p">(</span><span class="n">req</span>: <span class="nc">DeployRequest</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="n">Deploy</span><span class="p">,</span><span class="w"> </span><span class="n">DeployError</span><span class="o">&gt;</span></span></span></code></pre></div></div>
</div>
<p>The return types differ on success, and that difference is the whole idea. <strong>Validation</strong> answers the question and then throws away what it learned. <strong>Parsing</strong> answers the same question and returns a value whose type represents data that passed those checks. Downstream code can require that parsed type instead of accepting the raw request.</p>
<p>Rust makes that loss explicit in the signature. <code>()</code> is the type with exactly one value, so it carries no information. A function returning <code>Result&lt;(), E&gt;</code> has nothing to return on success. If the function&rsquo;s job is to check a value and it learns something useful about it, returning <code>()</code> throws that information away.</p>
<p>There is another Rust-specific difference in those signatures: <code>validate</code> borrows the request, while <code>parse</code> takes ownership of it. A successful parse consumes the raw <code>DeployRequest</code> and returns a <code>Deploy</code>, so the caller can&rsquo;t accidentally keep using the original request afterward.</p>
<h2 id="the-standard-library-already-does-this">
The standard library already does this
<a href="#the-standard-library-already-does-this" class="heading-anchor" aria-label="Anchor link for: The standard library already does this">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The standard library often keeps useful guarantees in the type it returns. Nobody writes <code>is_utf8(&amp;[u8]) -&gt; bool</code>, because <code>std::str::from_utf8</code> returns a <code>&amp;str</code> instead of a yes/no answer. <code>&amp;str</code> is a <code>&amp;[u8]</code> plus a proof that the bytes are valid UTF-8, so any function that takes <code>&amp;str</code> can rely on that guarantee without checking the bytes again.</p>
<p><code>NonZeroU32</code> does the same thing for numbers. It&rsquo;s a <code>u32</code> plus a guarantee that it isn&rsquo;t zero, and <code>NonZeroU32::new</code> checks that constraint when you construct one:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">workers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">NonZeroU32</span>::<span class="n">new</span><span class="p">(</span><span class="n">count</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">per_worker</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">total</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">workers</span><span class="p">;</span><span class="w">  </span><span class="c1">// no division-by-zero case to handle
</span></span></span></code></pre></div></div>
</div>
<p>A function that takes <code>NonZeroU32</code> no longer has to handle zero as an input.</p>
<h2 id="the-same-code-in-rust">
The same code in Rust
<a href="#the-same-code-in-rust" class="heading-anchor" aria-label="Anchor link for: The same code in Rust">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Here&rsquo;s the Java example in Rust:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">DeployRequest</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">service</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">environment</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">replicas</span>: <span class="kt">u32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">validate</span><span class="p">(</span><span class="n">req</span>: <span class="kp">&amp;</span><span class="nc">DeployRequest</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">DeployError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="n">is_service_name</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">.</span><span class="n">service</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="n">DeployError</span>::<span class="n">BadServiceName</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="n">is_environment</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">.</span><span class="n">environment</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="n">DeployError</span>::<span class="n">UnknownEnvironment</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="n">replicas</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="n">DeployError</span>::<span class="n">ZeroReplicas</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>The function now knows that <code>replicas</code> is not zero, but the return type has nowhere to keep that information.</p>
<p>The problem appears when another function needs to rely on that check:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">memory_per_replica_mb</span><span class="p">(</span><span class="n">req</span>: <span class="kp">&amp;</span><span class="nc">DeployRequest</span><span class="p">,</span><span class="w"> </span><span class="n">total_mb</span>: <span class="kt">u32</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">u32</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">total_mb</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="n">replicas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Nothing in the signature says that <code>validate</code> was called earlier, so this test compiles, type-checks, and panics:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[test]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[should_panic(expected = </span><span class="s">&#34;attempt to divide by zero&#34;</span><span class="cp">)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">nothing_stops_you_from_skipping_the_check</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">r</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">req</span><span class="p">(</span><span class="s">&#34;checkout-api&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;production&#34;</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">memory_per_replica_mb</span><span class="p">(</span><span class="o">&amp;</span><span class="n">r</span><span class="p">,</span><span class="w"> </span><span class="mi">1024</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<h2 id="refining-the-type">
Refining the type
<a href="#refining-the-type" class="heading-anchor" aria-label="Anchor link for: Refining the type">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The fix for <code>DeployRequest</code> is to give each field its own type, keep the inner value private, and only allow construction through a function that can fail.</p>
<p>The important boundary is the module. Code outside the module can&rsquo;t construct these types directly, so it has to use their parse functions or constructors. That means each check only needs to be correct in one place.</p>
<p>All three types live in the same module and use the same error type:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">mod</span> <span class="nn">domain</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">num</span>::<span class="n">NonZeroU32</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cp">#[derive(Debug, PartialEq, Eq)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">enum</span> <span class="nc">ParseError</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">BadServiceName</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">UnknownEnvironment</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">ZeroReplicas</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// domain types below
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Start with the service name:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[derive(Debug, Clone, PartialEq, Eq, Hash)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">ServiceName</span><span class="p">(</span><span class="nb">String</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">ServiceName</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">parse</span><span class="p">(</span><span class="n">raw</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="bp">Self</span><span class="p">,</span><span class="w"> </span><span class="n">ParseError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">canonical</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">raw</span><span class="p">.</span><span class="n">trim</span><span class="p">().</span><span class="n">to_ascii_lowercase</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">len_ok</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="mi">3</span><span class="o">..=</span><span class="mi">30</span><span class="p">).</span><span class="n">contains</span><span class="p">(</span><span class="o">&amp;</span><span class="n">canonical</span><span class="p">.</span><span class="n">len</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">chars_ok</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">canonical</span><span class="p">.</span><span class="n">chars</span><span class="p">().</span><span class="n">all</span><span class="p">(</span><span class="o">|</span><span class="n">c</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">c</span><span class="p">.</span><span class="n">is_ascii_lowercase</span><span class="p">()</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">c</span><span class="p">.</span><span class="n">is_ascii_digit</span><span class="p">()</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="sc">&#39;-&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="n">len_ok</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">chars_ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nb">Ok</span><span class="p">(</span><span class="bp">Self</span><span class="p">(</span><span class="n">canonical</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nb">Err</span><span class="p">(</span><span class="n">ParseError</span>::<span class="n">BadServiceName</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">as_str</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kp">&amp;</span><span class="kt">str</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">&amp;</span><span class="bp">self</span><span class="p">.</span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>The field is private, so code outside the module can&rsquo;t create a <code>ServiceName</code> directly. It has to call <code>ServiceName::parse</code>.</p>
<p>The <code>parse</code> also canonicalises before checking. It trims and lowercases the input, validates that value, and stores the same value. If you validate first and normalise later, you may end up storing something different from what you actually checked.</p>
<p><code>as_str()</code> makes the conversion back to a plain string explicit. I prefer that over <code>Deref&lt;Target = str&gt;</code>, which would let a <code>ServiceName</code> behave like a <code>str</code> implicitly in many places.</p>
<p>The environment uses the same pattern, but the valid values come from a fixed set:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">const</span><span class="w"> </span><span class="no">ENVIRONMENTS</span>: <span class="p">[</span><span class="o">&amp;</span><span class="kt">str</span><span class="p">;</span><span class="w"> </span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">&#34;dev&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;staging&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;production&#34;</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Environment</span><span class="p">(</span><span class="o">&amp;</span><span class="nb">&#39;static</span><span class="w"> </span><span class="kt">str</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">Environment</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">parse</span><span class="p">(</span><span class="n">raw</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="bp">Self</span><span class="p">,</span><span class="w"> </span><span class="n">ParseError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">canonical</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">raw</span><span class="p">.</span><span class="n">trim</span><span class="p">().</span><span class="n">to_ascii_lowercase</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="no">ENVIRONMENTS</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">into_iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="o">|</span><span class="n">name</span><span class="o">|</span><span class="w"> </span><span class="o">*</span><span class="n">name</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">canonical</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="n">Environment</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">ok_or</span><span class="p">(</span><span class="n">ParseError</span>::<span class="n">UnknownEnvironment</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kp">&amp;</span><span class="nb">&#39;static</span> <span class="kt">str</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>The type is named after what the value means, not what it is made of. <code>Environment</code> isn&rsquo;t &ldquo;a string someone typed&rdquo;. It&rsquo;s an environment this system deploys to. Add another environment later and <code>ENVIRONMENTS</code> changes, while the meaning of <code>Environment</code> stays the same.</p>
<p>The replica count is slightly different. serde has already parsed it into a <code>u32</code>, so <code>Replicas::new</code> only has to check the remaining rule: the value must not be zero. For that, it can reuse a type from <code>std</code>:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Replicas</span><span class="p">(</span><span class="n">NonZeroU32</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">Replicas</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">raw</span>: <span class="kt">u32</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="bp">Self</span><span class="p">,</span><span class="w"> </span><span class="n">ParseError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">NonZeroU32</span>::<span class="n">new</span><span class="p">(</span><span class="n">raw</span><span class="p">).</span><span class="n">ok_or</span><span class="p">(</span><span class="n">ParseError</span>::<span class="n">ZeroReplicas</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">Ok</span><span class="p">(</span><span class="bp">Self</span><span class="p">(</span><span class="n">count</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">NonZeroU32</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p><code>Replicas</code> wraps <code>NonZeroU32</code>, so zero cannot be represented by the type. <code>get</code> returns that <code>NonZeroU32</code> rather than a plain <code>u32</code>, so a function that receives it does not have to check for zero again.</p>
<p>These types should not derive <code>Default</code> unless the default value is valid. An empty <code>String</code>, for example, is not a valid <code>ServiceName</code>.</p>
<p>Now put those parsed fields together:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Deploy</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">service</span>: <span class="nc">ServiceName</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">environment</span>: <span class="nc">Environment</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">replicas</span>: <span class="nc">Replicas</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">TryFrom</span><span class="o">&lt;</span><span class="n">DeployRequest</span><span class="o">&gt;</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">Deploy</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">type</span> <span class="nc">Error</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ParseError</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">try_from</span><span class="p">(</span><span class="n">req</span>: <span class="nc">DeployRequest</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="bp">Self</span><span class="p">,</span><span class="w"> </span><span class="bp">Self</span>::<span class="n">Error</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">Ok</span><span class="p">(</span><span class="bp">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">service</span>: <span class="nc">ServiceName</span>::<span class="n">parse</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">.</span><span class="n">service</span><span class="p">)</span><span class="o">?</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">environment</span>: <span class="nc">Environment</span>::<span class="n">parse</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">.</span><span class="n">environment</span><span class="p">)</span><span class="o">?</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">replicas</span>: <span class="nc">Replicas</span>::<span class="n">new</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="n">replicas</span><span class="p">)</span><span class="o">?</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p><code>Deploy</code> checks the same three things as <code>validate</code>. The difference is that, on success, it returns a value that keeps what those checks learned in its types.</p>
<p>Before parsing:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">memory_per_replica_mb</span><span class="p">(</span><span class="n">req</span>: <span class="kp">&amp;</span><span class="nc">DeployRequest</span><span class="p">,</span><span class="w"> </span><span class="n">total_mb</span>: <span class="kt">u32</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">u32</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">total_mb</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="n">replicas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>After parsing:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">memory_per_replica_mb</span><span class="p">(</span><span class="n">deploy</span>: <span class="kp">&amp;</span><span class="nc">Deploy</span><span class="p">,</span><span class="w"> </span><span class="n">total_mb</span>: <span class="kt">u32</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">u32</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">total_mb</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">deploy</span><span class="p">.</span><span class="n">replicas</span><span class="p">.</span><span class="n">get</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>The function no longer depends on validation having happened somewhere earlier. <code>Replicas</code> already guarantees that the divisor is non-zero.</p>
<p><code>try_from</code> also consumes the request. After the conversion, the raw <code>DeployRequest</code> has moved out of scope, so you can&rsquo;t accidentally keep using the unchecked form. Neither Haskell nor Java gives you that by default, because in both the original value stays in scope.</p>
<p>Outside the <code>domain</code> module, constructing an invalid <code>ServiceName</code> is a compile error rather than a review comment:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">sneaky</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ServiceName</span><span class="p">(</span><span class="s">&#34;Not A Service&#34;</span><span class="p">.</span><span class="n">to_string</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// error[E0603]: tuple struct constructor `ServiceName` is private
</span></span></span></code></pre></div></div>
</div>
<p><strong>Refining</strong> is one way to put knowledge into a type. It stops invalid values from being constructed. Another is <strong>restructuring</strong>, which stops invalid combinations from being represented at all.</p>
<h2 id="removing-invalid-states">
Removing invalid states
<a href="#removing-invalid-states" class="heading-anchor" aria-label="Anchor link for: Removing invalid states">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Here&rsquo;s what you get by mapping a database row straight into a struct:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">DeploymentRow</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">is_running</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">is_finished</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">finished_at_ms</span>: <span class="nb">Option</span><span class="o">&lt;</span><span class="kt">u64</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">failure_reason</span>: <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>4 fields, 16 combinations, and only a few that describe a real deployment. A deployment can be marked as both running and finished, or finished without a timestamp, and the type system accepts it. Any code that reads this struct then has to deal with those invalid combinations too.</p>
<p>An enum can encode only the states the system actually allows:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">enum</span> <span class="nc">Deployment</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Queued</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">RollingOut</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">started_at_ms</span>: <span class="kt">u64</span> <span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Live</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">since_ms</span>: <span class="kt">u64</span> <span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">RolledBack</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">reason</span>: <span class="nb">String</span> <span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>4 states, 4 variants, each carrying exactly the data that state needs. A queued deployment has no <code>finished_at_ms</code>, so code can&rsquo;t try to use one. There are no invalid combinations to handle, and adding a fifth state later makes every incomplete match fail to compile.</p>
<p>This also removes the duplication between <code>is_finished</code> and <code>finished_at_ms</code>. Both represented the same fact, but they could disagree.</p>
<p>The standard library uses this idea too. In Java, a comparator returns an integer, even though only negative, zero, and positive matter. Rust returns <code>Ordering</code> instead, with exactly three variants: <code>Less</code>, <code>Equal</code>, and <code>Greater</code>.</p>
<h2 id="put-the-rule-in-the-type">
Put the rule in the type
<a href="#put-the-rule-in-the-type" class="heading-anchor" aria-label="Anchor link for: Put the rule in the type">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Both newtypes and enums move rules out of control flow and into the type itself.</p>
<p>With validation, a function can still accept the raw type after the check has run:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">submit</span><span class="p">(</span><span class="n">req</span>: <span class="kp">&amp;</span><span class="nc">DeployRequest</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<p>Nothing in that signature says whether <code>req</code> passed validation.</p>
<p>With parsed domain types, the requirement becomes part of the function signature:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">submit</span><span class="p">(</span><span class="n">deploy</span>: <span class="kp">&amp;</span><span class="nc">Deploy</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<p>Now callers have to provide a <code>Deploy</code>, and the function can rely on the guarantees that come with it.</p>
<p>That is the practical difference. With validation, the caller has to make sure the check happened first. With parsing, the function can require a type that could only be produced after those checks passed.</p>
<h2 id="shotgun-parsing">
Shotgun parsing
<a href="#shotgun-parsing" class="heading-anchor" aria-label="Anchor link for: Shotgun parsing">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>So far, the benefit has been about types. The next problem is what happens when code starts changing state before validation is finished.</p>
<p>Shotgun parsing is when input checks are spread through the same code that acts on the input. The program starts doing work before it has finished deciding whether the input is valid.</p>
<p>The name comes from the 2016 LangSec paper 
<a href="https://langsec.org/papers/langsec-cwes-secdev2016.pdf" target="_blank" rel="nofollow noopener">The Seven Turrets of Babel</a>
: instead of parsing the input once at the start, checks are added wherever they happen to be needed.</p>
<p>Here&rsquo;s a deploy handler that does it:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">deploy_shotgun</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">world</span>: <span class="kp">&amp;</span><span class="nc">mut</span><span class="w"> </span><span class="n">World</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">req</span>: <span class="kp">&amp;</span><span class="nc">DeployRequest</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">DeployError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="n">is_service_name</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">.</span><span class="n">service</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="n">DeployError</span>::<span class="n">BadServiceName</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// effect 1
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">world</span><span class="p">.</span><span class="n">deployments</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="n">service</span><span class="p">.</span><span class="n">clone</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="n">is_environment</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">.</span><span class="n">environment</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="n">DeployError</span>::<span class="n">UnknownEnvironment</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// effect 2
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">route</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;</span><span class="si">{}</span><span class="s"> -&gt; </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="n">environment</span><span class="p">,</span><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="n">service</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">world</span><span class="p">.</span><span class="n">traffic</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">route</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="n">replicas</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// too late
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="n">DeployError</span>::<span class="n">ZeroReplicas</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">entry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;deployed </span><span class="si">{}</span><span class="s"> to </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="n">service</span><span class="p">,</span><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="n">environment</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">world</span><span class="p">.</span><span class="n">audit</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">entry</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Send it a valid service name, a real environment, and a replica count of <code>0</code>:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> cargo run
</span></span><span class="line"><span class="cl"><span class="go">shotgun     -&gt; rejected with ZeroReplicas
</span></span></span><span class="line"><span class="cl"><span class="go">  deployments : [&#34;checkout-api&#34;]
</span></span></span><span class="line"><span class="cl"><span class="go">  traffic     : [&#34;production -&gt; checkout-api&#34;]
</span></span></span><span class="line"><span class="cl"><span class="go">  audit       : []
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">parse-first -&gt; rejected with ZeroReplicas
</span></span></span><span class="line"><span class="cl"><span class="go">  deployments : []
</span></span></span><span class="line"><span class="cl"><span class="go">  traffic     : []
</span></span></span><span class="line"><span class="cl"><span class="go">  audit       : []
</span></span></span></code></pre></div></div>
</div>
<p>The deploy was rejected, but the program has already changed its state. The deployment was recorded and traffic was updated. The audit log is empty too, because the audit write came after the failing check, so the one record that would tell you what happened is the one record you don&rsquo;t have.</p>
<p>The paper states the consequence directly:</p>
<blockquote>
<p>Shotgun parsing necessarily deprives the program of the ability to reject invalid input instead of processing it. Late-discovered errors in an input stream will result in some portion of invalid input having been processed, with the consequence that program state is difficult to accurately predict.</p>
</blockquote>
<p>The problem isn&rsquo;t that any individual check is wrong. You can fix this example by moving every check before the first side effect, but once validation and processing are mixed together, later changes can easily break that ordering again.</p>
<p>The safer approach is to separate the two steps: first parse the input, then act on the parsed value:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">deploy</span><span class="p">(</span><span class="n">world</span>: <span class="kp">&amp;</span><span class="nc">mut</span><span class="w"> </span><span class="n">World</span><span class="p">,</span><span class="w"> </span><span class="n">deploy</span>: <span class="kp">&amp;</span><span class="nc">Deploy</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">world</span><span class="p">.</span><span class="n">deployments</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">deploy</span><span class="p">.</span><span class="n">service</span><span class="p">.</span><span class="n">as_str</span><span class="p">().</span><span class="n">to_owned</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">world</span><span class="p">.</span><span class="n">traffic</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="fm">format!</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;</span><span class="si">{}</span><span class="s"> -&gt; </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">deploy</span><span class="p">.</span><span class="n">environment</span><span class="p">.</span><span class="n">name</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">deploy</span><span class="p">.</span><span class="n">service</span><span class="p">.</span><span class="n">as_str</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">world</span><span class="p">.</span><span class="n">audit</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="fm">format!</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;deployed </span><span class="si">{}</span><span class="s"> to </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">deploy</span><span class="p">.</span><span class="n">service</span><span class="p">.</span><span class="n">as_str</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">deploy</span><span class="p">.</span><span class="n">environment</span><span class="p">.</span><span class="n">name</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Then at the boundary:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">parsed</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Deploy</span>::<span class="n">try_from</span><span class="p">(</span><span class="n">req</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">deploy</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">world</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">parsed</span><span class="p">);</span></span></span></code></pre></div></div>
</div>
<p>The operation that changes state takes a <code>Deploy</code>, not a <code>DeployRequest</code>. Raw input therefore has to be parsed before it can be passed to that operation. The compiler can&rsquo;t stop unrelated code from changing <code>World</code>, but it can make this deployment path require parsed input.</p>
<p>So &ldquo;check before you act&rdquo; becomes something the compiler enforces instead of a rule developers have to remember.</p>
<h3 id="why-agents-drift-into-shotgun-parsing">
Why agents drift into shotgun parsing
<a href="#why-agents-drift-into-shotgun-parsing" class="heading-anchor" aria-label="Anchor link for: Why agents drift into shotgun parsing">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Nobody sits down to write a shotgun parser. It happens gradually. A new requirement arrives, and its check gets added where the relevant data is already available, often somewhere in the middle of the processing. Over time, a codebase that relies on separate validation checks can drift into this pattern.</p>
<p>Coding agents often make changes where the relevant data is already available instead of restructuring the whole function. If you ask for &ldquo;production deploys need an approver,&rdquo; an agent may find the branch where the environment is checked and add another <code>if</code> there. That change makes sense on its own, but repeated changes like this are how shotgun parsing grows.</p>
<p>The way this happens hasn&rsquo;t changed. The speed has. Two years of drift can now happen in an afternoon.</p>
<p>The same logic explains why types beat instructions here. A rule in your <code>AGENTS.md</code> costs context on every turn, competes with everything else in the file, and can&rsquo;t be enforced. A type in the signature is in context whenever the agent reads the function, needs no reminding, and fails the build when violated.</p>
<p>Types can enforce structure, but they can&rsquo;t tell whether the structure is a good one. An agent can still make a poor design compile, so you still need to read the diff. The difference is that you no longer have to check whether validation happened before the code changed state. You can focus on whether the types themselves represent the right rules.</p>
<h2 id="parsing-at-the-boundary">
Parsing at the boundary
<a href="#parsing-at-the-boundary" class="heading-anchor" aria-label="Anchor link for: Parsing at the boundary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The raw request type only needs to describe the input format. serde can then convert it into <code>Deploy</code> before the rest of the program sees it.</p>
<p>The <code>TryFrom</code> implementation from earlier already does the conversion. serde&rsquo;s <code>try_from</code> attribute tells it to deserialise a <code>DeployRequest</code> first and then call <code>Deploy::try_from</code>:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[derive(Deserialize)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">DeployRequest</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">service</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">environment</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">replicas</span>: <span class="kt">u32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[serde(try_from = </span><span class="s">&#34;DeployRequest&#34;</span><span class="cp">)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Deploy</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="cm">/* unchanged */</span><span class="w"> </span><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p><code>DeployRequest</code> derives <code>Deserialize</code> and mirrors the JSON, nothing more. <code>Deploy</code> is the domain type. serde first builds a <code>DeployRequest</code>, passes it to your <code>TryFrom</code> implementation, and returns either a <code>Deploy</code> or your own error. <code>ParseError</code> needs a <code>Display</code> impl for that last part, since serde reports the failure as a string:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">json</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sa">r</span><span class="s">#&#34;{
</span></span></span><span class="line"><span class="cl"><span class="s">    &#34;service&#34;: &#34;checkout-api&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s">    &#34;environment&#34;: &#34;production&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s">    &#34;replicas&#34;: 0
</span></span></span><span class="line"><span class="cl"><span class="s">}&#34;#</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">err</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">serde_json</span>::<span class="n">from_str</span>::<span class="o">&lt;</span><span class="n">Deploy</span><span class="o">&gt;</span><span class="p">(</span><span class="n">json</span><span class="p">).</span><span class="n">unwrap_err</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// replicas must be at least 1
</span></span></span></code></pre></div></div>
</div>
<p>After <code>from_str</code> succeeds, the rest of the program only sees <code>Deploy</code>, not <code>DeployRequest</code>. That keeps the raw input type at the boundary and prevents handlers from accepting unparsed requests by accident.</p>
<p>That error message matters for agents too. If parsing fails during a build or test, the agent may see the error directly. A message like <code>replicas must be at least 1</code> tells it what constraint was violated and what needs to change. An error with no useful message gives it much less to work with.</p>
<h3 id="the-derive-that-skips-your-constructor">
The derive that skips your constructor
<a href="#the-derive-that-skips-your-constructor" class="heading-anchor" aria-label="Anchor link for: The derive that skips your constructor">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>This trap is Rust-specific, and it&rsquo;s easy to miss.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[derive(Debug, Deserialize, PartialEq)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Replicas</span><span class="p">(</span><span class="kt">u32</span><span class="p">);</span><span class="w"> </span><span class="c1">// private field
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">Replicas</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">raw</span>: <span class="kt">u32</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="bp">Self</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nb">&#39;static</span><span class="w"> </span><span class="kt">str</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="n">raw</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="s">&#34;replicas must be at least 1&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">Ok</span><span class="p">(</span><span class="bp">Self</span><span class="p">(</span><span class="n">raw</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">get</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">u32</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>It looks safe, but it isn&rsquo;t. The generated <code>Deserialize</code> implementation can construct <code>Replicas</code> directly even though its field is private. That means it can bypass the constructor entirely:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[test]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">derive_bypasses_constructor</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="fm">assert!</span><span class="p">(</span><span class="n">Replicas</span>::<span class="n">new</span><span class="p">(</span><span class="mi">0</span><span class="p">).</span><span class="n">is_err</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ...and yet deserialisation accepts zero.
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">smuggled</span>: <span class="nc">Replicas</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">serde_json</span>::<span class="n">from_str</span><span class="p">(</span><span class="s">&#34;0&#34;</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="fm">assert_eq!</span><span class="p">(</span><span class="n">smuggled</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>serde has now created a <code>Replicas(0)</code> even though the constructor rejects zero. Private fields prevent code in other modules from constructing the type directly, but generated code in the same module can still do it. The fix is to tell serde to deserialise through <code>TryFrom</code> instead:</p>
<p>Note what makes this possible. The inner type is <code>u32</code>, which can hold zero, so the rule lives only in the constructor. The <code>Replicas(NonZeroU32)</code> from earlier has no such hole, because serde deserialises the inner <code>NonZeroU32</code> and that rejects zero on its own. The further you push a rule into the types, the less there is left to bypass.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[derive(Debug, Deserialize, PartialEq)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[serde(try_from = </span><span class="s">&#34;u32&#34;</span><span class="cp">)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Replicas</span><span class="p">(</span><span class="kt">u32</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">TryFrom</span><span class="o">&lt;</span><span class="kt">u32</span><span class="o">&gt;</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">Replicas</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">type</span> <span class="nc">Error</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&amp;</span><span class="nb">&#39;static</span><span class="w"> </span><span class="kt">str</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">try_from</span><span class="p">(</span><span class="n">raw</span>: <span class="kt">u32</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="bp">Self</span><span class="p">,</span><span class="w"> </span><span class="bp">Self</span>::<span class="n">Error</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">Self</span>::<span class="n">new</span><span class="p">(</span><span class="n">raw</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>This is easy to miss in agent-written code. <code>#[derive(Deserialize)]</code> is the usual way to make a newtype deserialisable, so an agent may add it without noticing that it bypasses the constructor. In this case, invalid input never reaches <code>Replicas::new</code>, so the <code>replicas must be at least 1</code> error is never produced.</p>
<p>This is exactly the kind of rule I would enforce rather than leave in <code>AGENTS.md</code>. In a larger codebase, domain newtypes should not be allowed to derive <code>Deserialize</code> directly. A CI check can reject that pattern unless deserialisation goes through <code>TryFrom</code>, and a test can verify that invalid values fail through both the constructor and serde.</p>
<h2 id="tradeoffs-and-limits">
Tradeoffs and limits
<a href="#tradeoffs-and-limits" class="heading-anchor" aria-label="Anchor link for: Tradeoffs and limits">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Using domain types adds more structs and conversion code. That is usually worth it for values that cross module boundaries or can cause real damage when they are wrong. It is probably not worth doing for every field in a small program.</p>
<p>Using <code>?</code> reports the first error and stops, but that is just one implementation choice. If you need to show several validation errors at once, you can collect them first and only construct <code>Deploy</code> when all checks pass.</p>
<p>And <code>Result&lt;(), E&gt;</code> is not always a problem. Functions like <code>fs::write</code> return <code>()</code> because they do not learn anything new about an input value. The problem is specific to validation functions that check a value and then throw away what they learned.</p>
<h2 id="where-to-start">
Where to start
<a href="#where-to-start" class="heading-anchor" aria-label="Anchor link for: Where to start">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Start with places where the code already hints that validation happened earlier:</p>
<ol>
<li>Search for comments, assertions, or panic messages like &ldquo;validated earlier&rdquo;, &ldquo;checked above&rdquo;, or &ldquo;cannot fail here&rdquo;. They often mark a fact the type does not record.</li>
<li>Find functions that return <code>Result&lt;(), E&gt;</code> and only inspect their input. Ask what the function knows after it succeeds, and whether it could return a type that represents that result instead.</li>
<li>Look at functions that accept raw input and also change state. If they can return an error after changing something, move all parsing before the first change.</li>
<li>Look for structs where fields can contradict each other. An enum may represent the valid states more directly.</li>
<li>Check domain newtypes for <code>#[derive(Deserialize)]</code> and <code>#[derive(Default)]</code>. <code>Deserialize</code> may bypass the parser, and <code>Default</code> is unsafe when the default inner value does not satisfy the type&rsquo;s rules.</li>
<li>Consider <code>#[serde(deny_unknown_fields)]</code> for request and config types. serde ignores unknown fields by default, so a typo like &ldquo;replcias&rdquo; can otherwise be silently ignored.</li>
</ol>
<p>Don&rsquo;t try to change everything at once. Moving one value behind a parsed domain type at one boundary is already useful.</p>
<p>For coding agents, keep the rules short and mechanical:</p>
<div class="code-block" data-frame="editor">
        <div class="code-header">
                <span class="code-filename">AGENTS.md</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="k">-</span> Parse external input into domain types at the boundary. Handlers
</span></span><span class="line"><span class="cl">  take parsed types, not raw request or config structs.
</span></span><span class="line"><span class="cl"><span class="k">-</span> A function whose only job is checking input should return what it learned,
</span></span><span class="line"><span class="cl">  not <span class="sb">`Result&lt;(), E&gt;`</span>.
</span></span><span class="line"><span class="cl"><span class="k">-</span> Domain types keep their fields private and are created through functions
</span></span><span class="line"><span class="cl">  that can fail.
</span></span><span class="line"><span class="cl">- Finish parsing before changing state.</span></span></code></pre></div></div>
</div>
<p>I would enforce the serde rule separately in CI. Domain newtypes should not derive <code>Deserialize</code> directly unless deserialisation is explicitly routed through <code>TryFrom</code>.</p>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Parsing forces you to decide what your types mean, and that&rsquo;s real work. It also won&rsquo;t stop an agent from making a poor design compile. What it gives you is a guarantee the compiler checks at every call site, including ones that haven&rsquo;t been written yet.</p>
<p>That is why this matters more when agents are writing more of the code. When code arrives faster than anyone can review it, the best invariants are the ones reviewers don&rsquo;t have to check manually.</p>
]]></content:encoded></item><item><title>Sandboxing Claude Code in a microVM with Docker sbx</title><link>https://rdiachenko.com/til/docker/sbx/</link><pubDate>Thu, 11 Jun 2026 18:00:00 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/til/docker/sbx/</guid><description>Docker&amp;rsquo;s sbx CLI runs Claude Code inside a microVM with its own kernel, an outbound proxy, and credentials held on the host. That makes &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; safer to use.</description><content:encoded><![CDATA[<p>Docker&rsquo;s <code>sbx</code> CLI runs Claude Code inside a microVM. The sandbox has its own kernel, its own filesystem, its own Docker daemon, and the host filesystem is unreachable outside the workspace directory you mount.</p>
<p>So <code>--dangerously-skip-permissions</code> stops being scary, even when the next command Claude Code runs happens to be <code>rm -rf</code> on your home directory.</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew install docker/tap/sbx
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/my-project <span class="o">&amp;&amp;</span> sbx run claude</span></span></code></pre></div></div>
</div>
<h2 id="the-architecture">
The architecture
<a href="#the-architecture" class="heading-anchor" aria-label="Anchor link for: The architecture">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The sandbox has two ways to reach the host. A workspace folder for files, and a proxy for outbound network. Nothing else gets through.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/til/docker/sbx/sbx-architecture_hu_9fbcb34d0270bdc2.webp"
                    media="(max-width: 768px)"
                    width="1120"
                    height="1107">
            <source
                    srcset="https://rdiachenko.com/til/docker/sbx/sbx-architecture_hu_1c2fc6714e22ad5a.webp"
                    media="(min-width: 769px)"
                    width="1600"
                    height="1581">
            <img
                    src="https://rdiachenko.com/til/docker/sbx/sbx-architecture_hu_1c2fc6714e22ad5a.webp"
                    alt="Diagram: each sandbox is a microVM with its own kernel, its own Docker engine, and a workspace mount. All outbound HTTP/HTTPS goes through a host-side proxy"
                    width="1600"
                    height="1581"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Each sandbox is a microVM. All outbound traffic is routed through a host-side proxy</small></figcaption></figure>
<p>Two things make this different from &ldquo;Claude in a Docker container&rdquo;:</p>
<ol>
<li><strong>Kernel isolation.</strong> A container escape gets you the host kernel. A microVM escape gets you nothing.</li>
<li><strong>All outbound traffic goes through a host-side proxy.</strong> HTTP and HTTPS can only reach domains you allow. Raw TCP, UDP, and ICMP are blocked at the network layer.</li>
</ol>
<p>That proxy is where the interesting work happens.</p>
<h2 id="the-proxy-does-two-jobs">
The proxy does two jobs
<a href="#the-proxy-does-two-jobs" class="heading-anchor" aria-label="Anchor link for: The proxy does two jobs">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p><strong>Credential substitution.</strong> You store a secret on the host:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sbx secret <span class="nb">set</span> -g github -t <span class="s2">&#34;</span><span class="k">$(</span>gh auth token<span class="k">)</span><span class="s2">&#34;</span></span></span></code></pre></div></div>
</div>
<p>Inside the sandbox, <code>GITHUB_TOKEN</code> holds a placeholder value like <code>proxy-managed</code>. The AI agent uses that env var as it normally would. On the way out, the proxy substitutes the real token for requests to <code>api.github.com</code>.</p>
<p>Custom services work the same way. Run <code>sbx secret set-custom</code> and the CLI generates a placeholder like <code>sbx-cs-&lt;rand&gt;</code>. The sandbox sees only the placeholder, and the proxy swaps in the real value on requests to the matching domain.</p>
<p>For SSH, <code>SSH_AUTH_SOCK</code> is forwarded into the sandbox, so private keys stay on the host.</p>
<p><strong>Network policy.</strong> Three default modes (Open, Balanced, Locked Down) plus per-domain overrides. Denied requests come back with a clear reason:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">HTTP/1.1 403 Forbidden
</span></span><span class="line"><span class="cl">Blocked by network policy: domain api.mailgun.net:443
</span></span><span class="line"><span class="cl">  detail: no matching allow rule — blocked by default deny policy</span></span></code></pre></div></div>
</div>
<p><code>sbx policy allow network api.mailgun.net</code> adds it. Or put it into a kit.</p>
<h2 id="kits">
Kits
<a href="#kits" class="heading-anchor" aria-label="Anchor link for: Kits">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>A kit is a YAML file that customizes a sandbox. The minimum useful one for a project:</p>
<div class="code-block" data-frame="editor">
        <div class="code-header">
                <span class="code-filename">my-project/.sbx/my-kit/spec.yaml</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">schemaVersion</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">mixin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-project</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="l">My project dev stack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Toolchain + network allowlist for my project.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">install</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Install Rust stable</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        curl --proto &#39;=https&#39; --tlsv1.2 -sSf https://sh.rustup.rs | \
</span></span></span><span class="line"><span class="cl"><span class="sd">          sh -s -- -y --default-toolchain stable --profile default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">network</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">allowedDomains</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;crates.io:443&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;static.crates.io:443&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;index.crates.io:443&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;static.rust-lang.org:443&#34;</span></span></span></code></pre></div></div>
</div>
<p>The <code>install</code> commands run once when the sandbox is created. Here they bootstrap Rust stable as the agent user. The <code>allowedDomains</code> list lets <code>rustup</code> reach the domains it needs. Without it, the install would fail under the default-deny policy.</p>
<p>Load it with <code>sbx run claude --kit ./.sbx/my-kit</code>.</p>
<h2 id="the-dashboard">
The dashboard
<a href="#the-dashboard" class="heading-anchor" aria-label="Anchor link for: The dashboard">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Running <code>sbx</code> with no arguments opens a dashboard listing every sandbox, its state, and resource usage. Useful when you&rsquo;ve left things running.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/til/docker/sbx/sbx-dashboard-cover_hu_6e3c50c61af9dbd1.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="440">
            <source
                    srcset="https://rdiachenko.com/til/docker/sbx/sbx-dashboard-cover_hu_dcce6e08893f6562.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="628">
            <img
                    src="https://rdiachenko.com/til/docker/sbx/sbx-dashboard-cover_hu_dcce6e08893f6562.webp"
                    alt="sbx interactive dashboard listing active sandboxes with their resource usage and workspace paths"
                    width="1200"
                    height="628"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. <code>sbx</code> with no arguments opens the dashboard</small></figcaption></figure>
<p>Notice that even <code>mcp-proxy.anthropic.com</code> shows up as blocked. The proxy makes no exception for the agent&rsquo;s own vendor. Anything not in your allowlist stays out.</p>
<h2 id="syncing-your-claude">
Syncing your <code>~/.claude</code>
<a href="#syncing-your-claude" class="heading-anchor" aria-label="Anchor link for: Syncing your ~/.claude">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Your host&rsquo;s <code>~/.claude</code> is not mounted. Slash commands, skills, hooks, and the global <code>CLAUDE.md</code> won&rsquo;t be there unless you copy them in. A small wrapper handles create-or-reuse, sync, and attach in one go:</p>
<div class="code-block" data-frame="terminal">
        <div class="code-header">
                <span class="code-filename">my-project/run-sandbox.sh</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="nb">set</span> -euo pipefail
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">sandbox</span><span class="o">=</span><span class="s2">&#34;claude-my-project&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sbx ls --quiet <span class="p">|</span> grep -qx <span class="s2">&#34;</span><span class="nv">$sandbox</span><span class="s2">&#34;</span> <span class="o">||</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  sbx create claude --name <span class="s2">&#34;</span><span class="nv">$sandbox</span><span class="s2">&#34;</span> --kit ./.sbx/my-kit .
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> item in commands skills agents hooks CLAUDE.md settings.json<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span> -e <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.claude/</span><span class="nv">$item</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">||</span> <span class="k">continue</span>
</span></span><span class="line"><span class="cl">  sbx cp <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.claude/</span><span class="nv">$item</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$sandbox</span><span class="s2">:/home/agent/.claude/&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sbx run <span class="s2">&#34;</span><span class="nv">$sandbox</span><span class="s2">&#34;</span></span></span></code></pre></div></div>
</div>
<p>The tool is still experimental, and a non-trivial project will need a custom kit. Docker keeps a 
<a href="https://github.com/docker/sbx-kits-contrib" target="_blank" rel="nofollow noopener">community kit repo</a>
 with reusable mixins worth borrowing from before writing your own.</p>
]]></content:encoded></item><item><title>Scripted Recordings with VHS</title><link>https://rdiachenko.com/til/terminal/vhs-scripted-recordings/</link><pubDate>Tue, 14 Apr 2026 21:30:00 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/til/terminal/vhs-scripted-recordings/</guid><description>After re-recording terminal demos too many times because of typos, I found VHS. It takes a script of what to type and renders it to GIF, MP4, or WebM.</description><content:encoded><![CDATA[<p>
<a href="https://github.com/charmbracelet/vhs" target="_blank" rel="nofollow noopener">VHS</a>
 records terminal sessions from a script. Instead of screen recording and trimming, you write a <code>.tape</code> file that describes what to type, and VHS renders it into a GIF, MP4, or WebM with consistent timing and clean output.</p>
<figure>
    <video autoplay loop muted playsinline aria-label="VHS demo: rendering a markdown file with glow in a Catppuccin-themed terminal" src="https://rdiachenko.com/til/terminal/vhs-scripted-recordings/glow-render.mp4">
    </video><figcaption><small>Figure 1. VHS recording of glow rendering a markdown file</small></figcaption></figure>

<h2 id="installation">
Installation
<a href="#installation" class="heading-anchor" aria-label="Anchor link for: Installation">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew install vhs</span></span></code></pre></div></div>
</div>
<p>VHS depends on <code>ttyd</code> and <code>ffmpeg</code>, both installed automatically by Homebrew.</p>
<h2 id="writing-a-tape">
Writing a tape
<a href="#writing-a-tape" class="heading-anchor" aria-label="Anchor link for: Writing a tape">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Here&rsquo;s the tape that produced Figure 1. It configures the terminal look (font, theme, window), hides shell setup from the viewer, then creates a markdown file and renders it with <code>glow</code>:</p>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="66">
        <div class="code-header">
                <span class="code-filename">glow-render.tape</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Output glow-render.mp4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Require glow
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Set Shell &#34;fish&#34;
</span></span><span class="line"><span class="cl">Set FontFamily &#34;JetBrainsMono Nerd Font Mono&#34;
</span></span><span class="line"><span class="cl">Set FontSize 40
</span></span><span class="line"><span class="cl">Set LineHeight 1.0
</span></span><span class="line"><span class="cl">Set Width 2400
</span></span><span class="line"><span class="cl">Set Height 1256
</span></span><span class="line"><span class="cl">Set Theme &#34;Catppuccin Mocha&#34;
</span></span><span class="line"><span class="cl">Set WindowBar Colorful
</span></span><span class="line"><span class="cl">Set WindowBarSize 85
</span></span><span class="line"><span class="cl">Set Padding 20
</span></span><span class="line"><span class="cl">Set Margin 30
</span></span><span class="line"><span class="cl">Set MarginFill &#34;#f3f5f7&#34;
</span></span><span class="line"><span class="cl">Set BorderRadius 8
</span></span><span class="line"><span class="cl">Set CursorBlink false
</span></span><span class="line"><span class="cl">Set Framerate 30
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Hide
</span></span><span class="line"><span class="cl">Type &#34;set fish_autosuggestion_enabled 0 &amp;&amp; clear&#34;
</span></span><span class="line"><span class="cl">Enter
</span></span><span class="line"><span class="cl">Sleep 500ms
</span></span><span class="line"><span class="cl">Show
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Sleep 500ms
</span></span><span class="line"><span class="cl">Set TypingSpeed 35ms
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Type &#39;echo &#34;# VHS&#39;
</span></span><span class="line"><span class="cl">Enter
</span></span><span class="line"><span class="cl">Sleep 150ms
</span></span><span class="line"><span class="cl">Enter
</span></span><span class="line"><span class="cl">Sleep 100ms
</span></span><span class="line"><span class="cl">Type &#34;`brew install vhs`&#34;
</span></span><span class="line"><span class="cl">Enter
</span></span><span class="line"><span class="cl">Sleep 100ms
</span></span><span class="line"><span class="cl">Enter
</span></span><span class="line"><span class="cl">Sleep 100ms
</span></span><span class="line"><span class="cl">Type &#34;Write a **.tape** script. Run **vhs demo.tape**.&#34;
</span></span><span class="line"><span class="cl">Enter
</span></span><span class="line"><span class="cl">Sleep 150ms
</span></span><span class="line"><span class="cl">Enter
</span></span><span class="line"><span class="cl">Sleep 100ms
</span></span><span class="line"><span class="cl">Type &#34;&gt; No screen recording. No trimming. Just code.&#34;
</span></span><span class="line"><span class="cl">Enter
</span></span><span class="line"><span class="cl">Sleep 200ms
</span></span><span class="line"><span class="cl">Type &#39;&#34; &gt; notes.md&#39;
</span></span><span class="line"><span class="cl">Sleep 300ms
</span></span><span class="line"><span class="cl">Enter
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Sleep 1s
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Ctrl+L
</span></span><span class="line"><span class="cl">Sleep 500ms
</span></span><span class="line"><span class="cl">Set TypingSpeed 40ms
</span></span><span class="line"><span class="cl">Type &#34;glow notes.md&#34;
</span></span><span class="line"><span class="cl">Sleep 500ms
</span></span><span class="line"><span class="cl">Enter
</span></span><span class="line"><span class="cl">Sleep 3s
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Hide
</span></span><span class="line"><span class="cl">Type &#34;rm notes.md&#34;
</span></span><span class="line"><span class="cl">Enter
</span></span><span class="line"><span class="cl">Sleep 300ms
</span></span><span class="line"><span class="cl">Show</span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="66">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 66 lines</span>
    </div>
</div>
<p>Run it:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">vhs glow-render.tape</span></span></code></pre></div></div>
</div>
<p>Beyond what&rsquo;s shown here, VHS supports <code>Ctrl+C</code>, arrow keys, and <code>Wait</code> (blocks until expected output appears). <code>Set LoopOffset 60%</code> starts the GIF loop partway through so the viewer sees the result first.</p>
<h2 id="font-gotcha">
Font gotcha
<a href="#font-gotcha" class="heading-anchor" aria-label="Anchor link for: Font gotcha">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>VHS renders in headless Chromium, not your terminal emulator. It can only use system-installed fonts. If the font isn&rsquo;t found, it falls back to a default monospace and produces broken letter-spacing with wide gaps between characters.</p>
<p>The fix: install the font system-wide. For JetBrains Mono Nerd Font:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew install --cask font-jetbrains-mono-nerd-font</span></span></code></pre></div></div>
</div>
<h2 id="output-formats">
Output formats
<a href="#output-formats" class="heading-anchor" aria-label="Anchor link for: Output formats">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>VHS renders GIF, MP4, and WebM. Add multiple <code>Output</code> lines to generate all three in one run:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Output demo.gif
</span></span><span class="line"><span class="cl">Output demo.mp4
</span></span><span class="line"><span class="cl">Output demo.webm</span></span></code></pre></div></div>
</div>
<p>MP4 and WebM support full color and compress well. For this recording, MP4 was 114 KB, WebM 113 KB, and GIF 194 KB, with the gap growing for longer or more colorful recordings. GIF is limited to 256 colors per frame, so rich themes like Catppuccin or Dracula show grainy artifacts.</p>
<p>GIF is the most portable format since it works as a plain image anywhere, but it&rsquo;s heavier. MP4 and WebM are smaller and sharper, with autoplay support via the <code>&lt;video&gt;</code> tag. Use MP4 or WebM for blog posts and GIF for GitHub READMEs where a click to play is unavoidable.</p>
]]></content:encoded></item><item><title>Postgres 18 Docker Silently Ignores Your Named Volume</title><link>https://rdiachenko.com/posts/databases/postgresql/postgres-18-docker-silently-ignores-your-named-volume/</link><pubDate>Fri, 03 Apr 2026 15:33:06 +0000</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/databases/postgresql/postgres-18-docker-silently-ignores-your-named-volume/</guid><description>After upgrading to Postgres 18 in Docker, my named volume sat empty while data went to an anonymous volume. No errors, no warnings, just initdb on every restart.</description><content:encoded><![CDATA[<p>After upgrading from Postgres 16 to 18, every time I recreated my Docker container, the database started fresh. No errors, no warnings. Just <code>initdb</code> running on every startup, as if the named volume didn&rsquo;t exist. The same setup worked fine with Postgres 16.</p>
<p>The volume did exist. <code>docker volume ls</code> confirmed it. The data just wasn&rsquo;t in it.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/databases/postgresql/postgres-18-docker-silently-ignores-your-named-volume/docker-inspect-volume-mismatch-cover_hu_36898ee5e2f65b8d.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="473">
            <source
                    srcset="https://rdiachenko.com/posts/databases/postgresql/postgres-18-docker-silently-ignores-your-named-volume/docker-inspect-volume-mismatch-cover_hu_21616087afd4faee.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="675">
            <img
                    src="https://rdiachenko.com/posts/databases/postgresql/postgres-18-docker-silently-ignores-your-named-volume/docker-inspect-volume-mismatch-cover_hu_21616087afd4faee.webp"
                    alt="Terminal showing docker inspect with two volume mounts and PGDATA pointing to /var/lib/postgresql/18/docker inside the anonymous volume"
                    width="1200"
                    height="675"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. The named volume mounts at /var/lib/postgresql/data, but PGDATA points to /var/lib/postgresql/18/docker inside an anonymous volume</small></figcaption></figure>
<h2 id="what-changed-in-postgres-18">
What changed in Postgres 18
<a href="#what-changed-in-postgres-18" class="heading-anchor" aria-label="Anchor link for: What changed in Postgres 18">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>
<a href="https://github.com/docker-library/postgres/pull/1259" target="_blank" rel="nofollow noopener">PR #1259</a>
 changed two things in the official Postgres Docker image, starting with version 18:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Postgres 16</th>
					<th>Postgres 18</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>PGDATA</code></td>
					<td><code>/var/lib/postgresql/data</code></td>
					<td><code>/var/lib/postgresql/18/docker</code></td>
			</tr>
			<tr>
					<td><code>VOLUME</code></td>
					<td><code>/var/lib/postgresql/data</code></td>
					<td><code>/var/lib/postgresql</code></td>
			</tr>
	</tbody>
</table>
<p>From the PR description:</p>
<blockquote>
<p>Concretely, this changes <code>PGDATA</code> to <code>/var/lib/postgresql/MAJOR/docker</code>, which matches the pre-existing convention/standard of the <code>pg_ctlcluster</code>/<code>postgresql-common</code> set of commands, and frankly is what we should&rsquo;ve done to begin with.</p>
</blockquote>
<blockquote>
<p>This also changes the <code>VOLUME</code> to <code>/var/lib/postgresql</code>, which should be more reasonable, and make the upgrade constraints more obvious.</p>
</blockquote>
<p>The new directory structure enables faster <code>pg_upgrade --link</code> between major versions.</p>
<h2 id="why-it-breaks">
Why it breaks
<a href="#why-it-breaks" class="heading-anchor" aria-label="Anchor link for: Why it breaks">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>With Postgres 16, mounting a named volume at <code>/var/lib/postgresql/data</code> worked because that&rsquo;s where <code>PGDATA</code> pointed. With Postgres 18, <code>PGDATA</code> moved to <code>/var/lib/postgresql/18/docker</code>. The old mount path and the new data path are siblings, not nested:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">/var/lib/postgresql/
</span></span><span class="line"><span class="cl">├── data/              ← named volume mounted here <span class="o">(</span>empty<span class="o">)</span>
</span></span><span class="line"><span class="cl">└── 18/
</span></span><span class="line"><span class="cl">    └── docker/        ← PGDATA lives here <span class="o">(</span>anonymous volume<span class="o">)</span>
</span></span><span class="line"><span class="cl">        ├── base/
</span></span><span class="line"><span class="cl">        ├── global/
</span></span><span class="line"><span class="cl">        ├── pg_wal/
</span></span><span class="line"><span class="cl">        └── ...</span></span></code></pre></div></div>
</div>
<p>The image declares <code>VOLUME /var/lib/postgresql</code>, so Docker creates an anonymous volume there automatically. My database files ended up in that anonymous volume. The named volume sat empty at <code>/var/lib/postgresql/data</code>.</p>
<p>The container logs confirm it. On every startup:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> docker logs pg
</span></span><span class="line"><span class="cl"><span class="go">The files belonging to this database system will be owned by user &#34;postgres&#34;.
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">fixing permissions on existing directory /var/lib/postgresql/18/docker ... ok
</span></span></span><span class="line"><span class="cl"><span class="go">creating subdirectories ... ok
</span></span></span><span class="line"><span class="cl"><span class="go">selecting dynamic shared memory implementation ... posix
</span></span></span><span class="line"><span class="cl"><span class="go">selecting default &#34;max_connections&#34; ... 100
</span></span></span><span class="line"><span class="cl"><span class="go">selecting default &#34;shared_buffers&#34; ... 128MB
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">running bootstrap script ... ok
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">performing post-bootstrap initialization ... ok
</span></span></span><span class="line"><span class="cl"><span class="go">syncing data to disk ... ok
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Success. You can now start the database server using:
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">    pg_ctl -D /var/lib/postgresql/18/docker -l logfile start
</span></span></span></code></pre></div></div>
</div>
<p>That&rsquo;s <code>initdb</code> running from scratch. If data had been persisted, Postgres would skip initialization and just start the server.</p>
<h2 id="reproduce-it">
Reproduce it
<a href="#reproduce-it" class="heading-anchor" aria-label="Anchor link for: Reproduce it">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Start Postgres 18 with the old mount path:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker run -d --name pg <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="nv">POSTGRES_DB</span><span class="o">=</span>myapp -e <span class="nv">POSTGRES_USER</span><span class="o">=</span>myapp -e <span class="nv">POSTGRES_PASSWORD</span><span class="o">=</span>testpass <span class="se">\
</span></span></span><span class="line"><span class="cl">  -v pgdata:/var/lib/postgresql/data <span class="se">\
</span></span></span><span class="line"><span class="cl">  postgres:18-alpine</span></span></code></pre></div></div>
</div>
<p>Insert data and verify:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> docker <span class="nb">exec</span> pg psql -U myapp -c <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="go">  &#34;CREATE TABLE test(id int); INSERT INTO test VALUES(1);&#34;
</span></span></span><span class="line"><span class="cl"><span class="go">CREATE TABLE
</span></span></span><span class="line"><span class="cl"><span class="go">INSERT 0 1
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> docker <span class="nb">exec</span> pg psql -U myapp -c <span class="s2">&#34;SELECT * FROM test;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="go"> id
</span></span></span><span class="line"><span class="cl"><span class="go">----
</span></span></span><span class="line"><span class="cl"><span class="go">  1
</span></span></span><span class="line"><span class="cl"><span class="go">(1 row)
</span></span></span></code></pre></div></div>
</div>
<p>Remove the container and start a new one:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> docker rm -f pg
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> docker run -d --name pg <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="go">  -e POSTGRES_DB=myapp -e POSTGRES_USER=myapp -e POSTGRES_PASSWORD=testpass \
</span></span></span><span class="line"><span class="cl"><span class="go">  -v pgdata:/var/lib/postgresql/data \
</span></span></span><span class="line"><span class="cl"><span class="go">  postgres:18-alpine
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> docker <span class="nb">exec</span> pg psql -U myapp -c <span class="s2">&#34;SELECT * FROM test;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="go">ERROR:  relation &#34;test&#34; does not exist
</span></span></span><span class="line"><span class="cl"><span class="go">LINE 1: SELECT * FROM test;
</span></span></span><span class="line"><span class="cl"><span class="go">                      ^
</span></span></span></code></pre></div></div>
</div>
<p>Data is gone. No error from Docker, no warning in the logs.</p>
<p><code>docker volume ls</code> shows the evidence:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> docker volume ls
</span></span><span class="line"><span class="cl"><span class="go">DRIVER    VOLUME NAME
</span></span></span><span class="line"><span class="cl"><span class="go">local     4dea31d05d27...   ← new anonymous volume (fresh initdb)
</span></span></span><span class="line"><span class="cl"><span class="go">local     be27b5708be1...   ← old anonymous volume (your data is here, orphaned)
</span></span></span><span class="line"><span class="cl"><span class="go">local     pgdata            ← named volume (empty the whole time)
</span></span></span></code></pre></div></div>
</div>
<p>A new anonymous volume appears after each remove/create cycle. The old one stays on disk until you run <code>docker volume prune</code>.</p>
<p>Two mounts confirm the mismatch:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> docker inspect pg <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="go">  --format &#39;{{range .Mounts}}{{.Name}} -&gt; {{.Destination}}{{&#34;\n&#34;}}{{end}}&#39;
</span></span></span><span class="line"><span class="cl"><span class="go">pgdata -&gt; /var/lib/postgresql/data
</span></span></span><span class="line"><span class="cl"><span class="go">4dea31d05d27... -&gt; /var/lib/postgresql
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> docker <span class="nb">exec</span> pg sh -c <span class="s1">&#39;echo &#34;PGDATA=$PGDATA&#34;&#39;</span>
</span></span><span class="line"><span class="cl"><span class="go">PGDATA=/var/lib/postgresql/18/docker
</span></span></span></code></pre></div></div>
</div>
<p><code>PGDATA</code> sits inside the anonymous volume, not the named one.</p>
<h2 id="why-data-sometimes-survives">
Why data sometimes survives
<a href="#why-data-sometimes-survives" class="heading-anchor" aria-label="Anchor link for: Why data sometimes survives">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Restarting the same container preserves data. Removing it doesn&rsquo;t:</p>
<table>
	<thead>
			<tr>
					<th>Action</th>
					<th>Container</th>
					<th>Anonymous volume</th>
					<th>Data?</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>docker stop</code> + <code>docker start</code></td>
					<td>Same container restarted</td>
					<td>Old anonymous volume still attached</td>
					<td>Preserved</td>
			</tr>
			<tr>
					<td><code>docker rm</code> + <code>docker run</code></td>
					<td>New container created</td>
					<td>New anonymous volume attached</td>
					<td>Lost</td>
			</tr>
	</tbody>
</table>
<p><code>docker stop</code> / <code>docker start</code> keeps the container and its anonymous volume in place. <code>docker rm</code> destroys the container, orphaning the anonymous volume. The next <code>docker run</code> creates a fresh one, Postgres sees an empty <code>PGDATA</code>, and runs <code>initdb</code>.</p>
<p>This is the same reason <code>docker compose up</code> (foreground, <code>Ctrl+C</code> to stop) preserves data while <code>docker compose down</code> followed by <code>docker compose up -d</code> doesn&rsquo;t. <code>down</code> removes the container. <code>Ctrl+C</code> just stops it.</p>
<h2 id="the-fix">
The fix
<a href="#the-fix" class="heading-anchor" aria-label="Anchor link for: The fix">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Two things need to happen: back up your Postgres 16 data, then change the volume mount.</p>
<p>Back up first:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> pg pg_dumpall -U myapp &gt; backup.sql</span></span></code></pre></div></div>
</div>
<p>Then recreate the container with the correct mount path at <code>/var/lib/postgresql</code>:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker rm -f pg
</span></span><span class="line"><span class="cl">docker volume rm pgdata
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">docker run -d --name pg <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="nv">POSTGRES_DB</span><span class="o">=</span>myapp -e <span class="nv">POSTGRES_USER</span><span class="o">=</span>myapp -e <span class="nv">POSTGRES_PASSWORD</span><span class="o">=</span>testpass <span class="se">\
</span></span></span><span class="line"><span class="cl">  -v pgdata:/var/lib/postgresql <span class="se">\
</span></span></span><span class="line"><span class="cl">  postgres:18-alpine</span></span></code></pre></div></div>
</div>
<p><code>PGDATA</code> (<code>/var/lib/postgresql/18/docker</code>) is now a subdirectory of the named volume mount. Data persists across container removal because the named volume has a stable name that Docker can find and reattach.</p>
<p>Restore the backup:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> -i pg psql -U myapp &lt; backup.sql</span></span></code></pre></div></div>
</div>
<p>Verify a single mount:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> docker inspect pg <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="go">  --format &#39;{{range .Mounts}}{{.Name}} -&gt; {{.Destination}}{{&#34;\n&#34;}}{{end}}&#39;
</span></span></span><span class="line"><span class="cl"><span class="go">pgdata -&gt; /var/lib/postgresql
</span></span></span></code></pre></div></div>
</div>
<p>One mount, one volume, data persists.</p>
<p>If you use Docker Compose, the equivalent mount path fix:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"> volumes:
</span></span><span class="line"><span class="cl"><span class="gd">-  - postgres_data:/var/lib/postgresql/data
</span></span></span><span class="line"><span class="cl"><span class="gi">+  - postgres_data:/var/lib/postgresql
</span></span></span></code></pre></div></div>
</div>
<h2 id="the-wrong-fix">
The wrong fix
<a href="#the-wrong-fix" class="heading-anchor" aria-label="Anchor link for: The wrong fix">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>My first instinct was to override <code>PGDATA</code> to force Postgres back into the old mount path:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker run -d --name pg <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="nv">POSTGRES_DB</span><span class="o">=</span>myapp -e <span class="nv">POSTGRES_USER</span><span class="o">=</span>myapp -e <span class="nv">POSTGRES_PASSWORD</span><span class="o">=</span>testpass <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="nv">PGDATA</span><span class="o">=</span>/var/lib/postgresql/data/pgdata <span class="se">\
</span></span></span><span class="line"><span class="cl">  -v pgdata:/var/lib/postgresql/data <span class="se">\
</span></span></span><span class="line"><span class="cl">  postgres:18-alpine</span></span></code></pre></div></div>
</div>
<p>This works in the sense that Postgres will store data inside the volume. But it fights the image&rsquo;s intended design and breaks the version-specific directory structure (<code>/var/lib/postgresql/MAJOR/docker</code>) that enables <code>pg_upgrade --link</code> between major versions.</p>
<h2 id="who-else-got-burned">
Who else got burned
<a href="#who-else-got-burned" class="heading-anchor" aria-label="Anchor link for: Who else got burned">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The same issue has been reported across multiple open-source projects after they upgraded to Postgres 18:</p>
<ul>
<li>
<a href="https://github.com/NginxProxyManager/nginx-proxy-manager/issues/4811" target="_blank" rel="nofollow noopener">nginx-proxy-manager #4811</a>
</li>
<li>
<a href="https://github.com/OWASP-BLT/BLT/issues/4717" target="_blank" rel="nofollow noopener">OWASP-BLT #4717</a>
</li>
<li>
<a href="https://github.com/HiEventsDev/Hi.Events/issues/877" target="_blank" rel="nofollow noopener">Hi.Events #877</a>
</li>
</ul>
<p>The 
<a href="https://github.com/docker-library/docs/blob/master/postgres/README.md#pgdata" target="_blank" rel="nofollow noopener">official Docker Hub documentation</a>
 now includes this warning:</p>
<blockquote>
<p><strong>Important Change:</strong> the <code>PGDATA</code> environment variable of the image was changed to be version specific in PostgreSQL 18 and above. For 18 it is <code>/var/lib/postgresql/18/docker</code>. The defined <code>VOLUME</code> was changed in 18 and above to <code>/var/lib/postgresql</code>. Mounts and volumes should be targeted at the updated location.</p>
</blockquote>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>If you&rsquo;re upgrading to Postgres 18 in Docker: back up with <code>pg_dumpall</code>, change your volume mount, and restore.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gd">- -v pgdata:/var/lib/postgresql/data
</span></span></span><span class="line"><span class="cl"><span class="gi">+ -v pgdata:/var/lib/postgresql
</span></span></span></code></pre></div></div>
</div>
<p>Don&rsquo;t override <code>PGDATA</code>. The image default is correct.</p>
<p>The breaking change is 
<a href="https://github.com/docker-library/postgres/pull/1259" target="_blank" rel="nofollow noopener">documented</a>
 but easy to miss. No error message will tell you your data is being written outside the volume. The only clue is <code>initdb</code> running on every startup in the container logs.</p>
]]></content:encoded></item><item><title>Custom Status Line with a Shell Script</title><link>https://rdiachenko.com/til/claude-code/custom-statusline/</link><pubDate>Tue, 31 Mar 2026 21:58:27 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/til/claude-code/custom-statusline/</guid><description>Claude Code has a customizable status bar at the bottom of the terminal. Point it to a shell script that receives session JSON on stdin, and it displays whatever you print.</description><content:encoded><![CDATA[<p>Claude Code supports a customizable status line at the bottom of the terminal. It&rsquo;s empty by default, but you can point it to a shell script.</p>
<p>After each assistant message, Claude Code pipes session state to your script&rsquo;s stdin as JSON: model, context usage, rate limits, cost, workspace path, and more. Whatever the script prints to stdout becomes the status line.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/til/claude-code/custom-statusline/custom-statusline-cover_hu_4ec3856c05c14f9.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="334">
            <source
                    srcset="https://rdiachenko.com/til/claude-code/custom-statusline/custom-statusline-cover_hu_e4eb6312ade74c7e.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="477">
            <img
                    src="https://rdiachenko.com/til/claude-code/custom-statusline/custom-statusline-cover_hu_e4eb6312ade74c7e.webp"
                    alt="Custom two-line Claude Code status bar showing model, effort, directory, context bar, rate limits, and session cost"
                    width="1200"
                    height="477"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Custom status line with Catppuccin Macchiato colors</small></figcaption></figure>
<h2 id="configuration">
Configuration
<a href="#configuration" class="heading-anchor" aria-label="Anchor link for: Configuration">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Add a <code>statusLine</code> field to <code>~/.claude/settings.json</code>:</p>
<div class="code-block" data-frame="editor">
        <div class="code-header">
                <span class="code-filename">~/.claude/settings.json</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsonc" data-lang="jsonc"><span class="line"><span class="cl"><span class="s2">&#34;statusLine&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;~/.claude/statusline.sh&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;padding&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>The script needs to be executable (<code>chmod +x ~/.claude/statusline.sh</code>).</p>
<h2 id="the-script">
The script
<a href="#the-script" class="heading-anchor" aria-label="Anchor link for: The script">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>This is a two-line status bar with Catppuccin Macchiato colors (matched to my Kitty theme) and Nerd Font icons:</p>
<div class="code-block highlight-collapsed" data-frame="terminal" data-collapsible data-lines="125">
        <div class="code-header">
                <span class="code-filename">~/.claude/statusline.sh</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="nv">input</span><span class="o">=</span><span class="k">$(</span>cat<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">NOW</span><span class="o">=</span><span class="k">$(</span>date +%s<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ─── Catppuccin Macchiato (true color) ─────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">MAUVE</span><span class="o">=</span><span class="s1">$&#39;\033[38;2;198;160;246m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">BLUE</span><span class="o">=</span><span class="s1">$&#39;\033[38;2;138;173;244m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">TEAL</span><span class="o">=</span><span class="s1">$&#39;\033[38;2;139;213;202m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">GREEN</span><span class="o">=</span><span class="s1">$&#39;\033[38;2;166;218;149m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">YELLOW</span><span class="o">=</span><span class="s1">$&#39;\033[38;2;238;212;159m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">RED</span><span class="o">=</span><span class="s1">$&#39;\033[38;2;237;135;150m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">PEACH</span><span class="o">=</span><span class="s1">$&#39;\033[38;2;245;169;127m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">FLAMINGO</span><span class="o">=</span><span class="s1">$&#39;\033[38;2;240;198;198m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">LAVENDER</span><span class="o">=</span><span class="s1">$&#39;\033[38;2;183;189;248m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">SKY</span><span class="o">=</span><span class="s1">$&#39;\033[38;2;145;215;227m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">OVERLAY</span><span class="o">=</span><span class="s1">$&#39;\033[38;2;110;115;141m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">SUBTEXT</span><span class="o">=</span><span class="s1">$&#39;\033[38;2;165;173;203m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">RST</span><span class="o">=</span><span class="s1">$&#39;\033[0m&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">SEP</span><span class="o">=</span><span class="s2">&#34; </span><span class="si">${</span><span class="nv">OVERLAY</span><span class="si">}</span><span class="s2">│</span><span class="si">${</span><span class="nv">RST</span><span class="si">}</span><span class="s2"> &#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ─── Parse JSON (single jq call) ──────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">read</span> -r MODEL
</span></span><span class="line"><span class="cl">  <span class="nb">read</span> -r DIR
</span></span><span class="line"><span class="cl">  <span class="nb">read</span> -r PCT_RAW
</span></span><span class="line"><span class="cl">  <span class="nb">read</span> -r CTX_SIZE
</span></span><span class="line"><span class="cl">  <span class="nb">read</span> -r FIVE_PCT
</span></span><span class="line"><span class="cl">  <span class="nb">read</span> -r FIVE_RESET
</span></span><span class="line"><span class="cl">  <span class="nb">read</span> -r SEVEN_PCT
</span></span><span class="line"><span class="cl">  <span class="nb">read</span> -r COST
</span></span><span class="line"><span class="cl"><span class="o">}</span> &lt; &lt;<span class="o">(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$input</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">  (.model.display_name // &#34;—&#34;),
</span></span></span><span class="line"><span class="cl"><span class="s1">  (.workspace.current_dir // &#34;&#34;),
</span></span></span><span class="line"><span class="cl"><span class="s1">  (.context_window.used_percentage // &#34;&#34;),
</span></span></span><span class="line"><span class="cl"><span class="s1">  (.context_window.context_window_size // 200000),
</span></span></span><span class="line"><span class="cl"><span class="s1">  (.rate_limits.five_hour.used_percentage // &#34;&#34;),
</span></span></span><span class="line"><span class="cl"><span class="s1">  (.rate_limits.five_hour.resets_at // &#34;&#34;),
</span></span></span><span class="line"><span class="cl"><span class="s1">  (.rate_limits.seven_day.used_percentage // &#34;&#34;),
</span></span></span><span class="line"><span class="cl"><span class="s1">  (.cost.total_cost_usd // 0)
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ─── Effort level ─────────────────────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">EFFORT</span><span class="o">=</span><span class="k">$(</span>jq -r <span class="s1">&#39;.effortLevel // &#34;medium&#34;&#39;</span> ~/.claude/settings.json 2&gt;/dev/null<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="s2">&#34;</span><span class="nv">$EFFORT</span><span class="s2">&#34;</span> in
</span></span><span class="line"><span class="cl">  low<span class="o">)</span>    <span class="nv">EFFORT_SEG</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">SUBTEXT</span><span class="si">}</span><span class="s2">▽ low</span><span class="si">${</span><span class="nv">RST</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="cl">  high<span class="o">)</span>   <span class="nv">EFFORT_SEG</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PEACH</span><span class="si">}</span><span class="s2">▲ high</span><span class="si">${</span><span class="nv">RST</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="cl">  max<span class="o">)</span>    <span class="nv">EFFORT_SEG</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">FLAMINGO</span><span class="si">}</span><span class="s2">⬆ max</span><span class="si">${</span><span class="nv">RST</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="cl">  *<span class="o">)</span>      <span class="nv">EFFORT_SEG</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span><span class="s2">◆ med</span><span class="si">${</span><span class="nv">RST</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="cl"><span class="k">esac</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ─── Directory (clickable OSC 8) ──────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">DNAME</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">DIR</span><span class="p">##*/</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">DIR_SEG</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">BLUE</span><span class="si">}</span><span class="s2">󰉋 &#34;</span><span class="s1">$&#39;\033]8;;file://&#39;</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">DIR</span><span class="si">}</span><span class="s2">&#34;</span><span class="s1">$&#39;\033\\&#39;</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">DNAME</span><span class="si">}</span><span class="s2">&#34;</span><span class="s1">$&#39;\033]8;;\033\\&#39;</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">RST</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ─── Git (cached 5s, keyed by dir) ────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">GIT</span><span class="o">=</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$DIR</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nv">CF</span><span class="o">=</span><span class="s2">&#34;/tmp/claudeline-</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$DIR</span><span class="s2">&#34;</span> <span class="p">|</span> cksum <span class="p">|</span> cut -d<span class="s1">&#39; &#39;</span> -f1<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">BRANCH</span><span class="o">=</span><span class="s2">&#34;&#34;</span> <span class="nv">STAGED</span><span class="o">=</span><span class="m">0</span> <span class="nv">MODIFIED</span><span class="o">=</span><span class="m">0</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> -f <span class="s2">&#34;</span><span class="nv">$CF</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="o">((</span> NOW - <span class="k">$(</span>stat -f %m <span class="s2">&#34;</span><span class="nv">$CF</span><span class="s2">&#34;</span><span class="k">)</span> &lt; <span class="m">5</span> <span class="o">))</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nv">IFS</span><span class="o">=</span><span class="s1">$&#39;\t&#39;</span> <span class="nb">read</span> -r BRANCH STAGED MODIFIED &lt; <span class="s2">&#34;</span><span class="nv">$CF</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">elif</span> git -C <span class="s2">&#34;</span><span class="nv">$DIR</span><span class="s2">&#34;</span> -c gc.auto<span class="o">=</span><span class="m">0</span> rev-parse --git-dir &gt;/dev/null 2&gt;<span class="p">&amp;</span>1<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nv">BRANCH</span><span class="o">=</span><span class="k">$(</span>git -C <span class="s2">&#34;</span><span class="nv">$DIR</span><span class="s2">&#34;</span> -c gc.auto<span class="o">=</span><span class="m">0</span> branch --show-current 2&gt;/dev/null<span class="k">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="nv">IFS</span><span class="o">=</span> <span class="nb">read</span> -r l<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="o">[[</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">l</span><span class="p">:</span><span class="nv">0</span><span class="p">:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34; &#34;</span> <span class="o">&amp;&amp;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">l</span><span class="p">:</span><span class="nv">0</span><span class="p">:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;?&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="o">((</span>STAGED++<span class="o">))</span>
</span></span><span class="line"><span class="cl">      <span class="o">[[</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">l</span><span class="p">:</span><span class="nv">1</span><span class="p">:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34; &#34;</span> <span class="o">&amp;&amp;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">l</span><span class="p">:</span><span class="nv">1</span><span class="p">:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;?&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="o">((</span>MODIFIED++<span class="o">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">done</span> &lt; &lt;<span class="o">(</span>git -C <span class="s2">&#34;</span><span class="nv">$DIR</span><span class="s2">&#34;</span> -c gc.auto<span class="o">=</span><span class="m">0</span> status --porcelain 2&gt;/dev/null<span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">printf</span> <span class="s1">&#39;%s\t%s\t%s&#39;</span> <span class="s2">&#34;</span><span class="nv">$BRANCH</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$STAGED</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$MODIFIED</span><span class="s2">&#34;</span> &gt; <span class="s2">&#34;</span><span class="nv">$CF</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$BRANCH</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nv">GIT</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">SEP</span><span class="si">}${</span><span class="nv">TEAL</span><span class="si">}</span><span class="s2">󰘬 </span><span class="si">${</span><span class="nv">BRANCH</span><span class="si">}${</span><span class="nv">RST</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">((</span> STAGED &gt; <span class="m">0</span> <span class="o">))</span>   <span class="o">&amp;&amp;</span> <span class="nv">GIT</span><span class="o">+=</span><span class="s2">&#34; </span><span class="si">${</span><span class="nv">GREEN</span><span class="si">}</span><span class="s2">+</span><span class="si">${</span><span class="nv">STAGED</span><span class="si">}${</span><span class="nv">RST</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">((</span> MODIFIED &gt; <span class="m">0</span> <span class="o">))</span> <span class="o">&amp;&amp;</span> <span class="nv">GIT</span><span class="o">+=</span><span class="s2">&#34; </span><span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span><span class="s2">~</span><span class="si">${</span><span class="nv">MODIFIED</span><span class="si">}${</span><span class="nv">RST</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ═══ LINE 1: model │ effort │ dir │ git ═══════════════════════════</span>
</span></span><span class="line"><span class="cl"><span class="nb">printf</span> <span class="s1">&#39;%s\n&#39;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">MAUVE</span><span class="si">}</span><span class="s2">✦ </span><span class="si">${</span><span class="nv">MODEL</span><span class="si">}${</span><span class="nv">RST</span><span class="si">}${</span><span class="nv">SEP</span><span class="si">}${</span><span class="nv">EFFORT_SEG</span><span class="si">}${</span><span class="nv">SEP</span><span class="si">}${</span><span class="nv">DIR_SEG</span><span class="si">}${</span><span class="nv">GIT</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ─── Context bar ──────────────────────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">PCT</span><span class="o">=</span><span class="m">0</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$PCT_RAW</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="s2">&#34;</span><span class="nv">$PCT_RAW</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;null&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nv">PCT</span><span class="o">=</span><span class="si">${</span><span class="nv">PCT_RAW</span><span class="p">%%.*</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">  <span class="o">((</span> PCT &lt; <span class="m">0</span> <span class="o">))</span> <span class="o">&amp;&amp;</span> <span class="nv">PCT</span><span class="o">=</span>0<span class="p">;</span> <span class="o">((</span> PCT &gt; <span class="m">100</span> <span class="o">))</span> <span class="o">&amp;&amp;</span> <span class="nv">PCT</span><span class="o">=</span><span class="m">100</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span>   <span class="o">((</span> PCT &gt;<span class="o">=</span> <span class="m">60</span> <span class="o">))</span><span class="p">;</span> <span class="k">then</span> <span class="nv">BC</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$RED</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="o">((</span> PCT &gt;<span class="o">=</span> <span class="m">40</span> <span class="o">))</span><span class="p">;</span> <span class="k">then</span> <span class="nv">BC</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$YELLOW</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span> <span class="nv">BC</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$GREEN</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">F</span><span class="o">=</span><span class="k">$((</span>PCT <span class="o">/</span> <span class="m">10</span><span class="k">))</span><span class="p">;</span> <span class="nv">E</span><span class="o">=</span><span class="k">$((</span><span class="m">10</span> <span class="o">-</span> F<span class="k">))</span><span class="p">;</span> <span class="nv">BAR</span><span class="o">=</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="o">((</span><span class="nv">i</span><span class="o">=</span>0<span class="p">;</span> i&lt;F<span class="p">;</span> i++<span class="o">))</span><span class="p">;</span> <span class="k">do</span> <span class="nv">BAR</span><span class="o">+=</span><span class="s2">&#34;█&#34;</span><span class="p">;</span> <span class="k">done</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="o">((</span><span class="nv">i</span><span class="o">=</span>0<span class="p">;</span> i&lt;E<span class="p">;</span> i++<span class="o">))</span><span class="p">;</span> <span class="k">do</span> <span class="nv">BAR</span><span class="o">+=</span><span class="s2">&#34;░&#34;</span><span class="p">;</span> <span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ─── 5h rate limit with reset countdown ───────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">RLIM</span><span class="o">=</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$FIVE_PCT</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="s2">&#34;</span><span class="nv">$FIVE_PCT</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;null&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nv">FI</span><span class="o">=</span><span class="k">$(</span><span class="nb">printf</span> <span class="s1">&#39;%.0f&#39;</span> <span class="s2">&#34;</span><span class="nv">$FIVE_PCT</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span>   <span class="o">((</span> FI &gt;<span class="o">=</span> <span class="m">80</span> <span class="o">))</span><span class="p">;</span> <span class="k">then</span> <span class="nv">RC</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$RED</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">elif</span> <span class="o">((</span> FI &gt;<span class="o">=</span> <span class="m">50</span> <span class="o">))</span><span class="p">;</span> <span class="k">then</span> <span class="nv">RC</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$YELLOW</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span> <span class="nv">RC</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$LAVENDER</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">fi</span>
</span></span><span class="line"><span class="cl">  <span class="nv">RLIM</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">SEP</span><span class="si">}${</span><span class="nv">RC</span><span class="si">}</span><span class="s2">󰥔 5h: </span><span class="si">${</span><span class="nv">FI</span><span class="si">}</span><span class="s2">%</span><span class="si">${</span><span class="nv">RST</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$FIVE_RESET</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="s2">&#34;</span><span class="nv">$FIVE_RESET</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;null&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nv">REM</span><span class="o">=</span><span class="k">$((</span>FIVE_RESET <span class="o">-</span> NOW<span class="k">))</span>
</span></span><span class="line"><span class="cl">    <span class="o">((</span> REM &gt; <span class="m">0</span> <span class="o">))</span> <span class="o">&amp;&amp;</span> <span class="nv">RLIM</span><span class="o">+=</span><span class="s2">&#34; </span><span class="si">${</span><span class="nv">SUBTEXT</span><span class="si">}</span><span class="k">$((</span>REM <span class="o">/</span> <span class="m">3600</span><span class="k">))</span><span class="s2">h</span><span class="k">$((</span><span class="o">(</span>REM <span class="o">%</span> <span class="m">3600</span><span class="o">)</span> <span class="o">/</span> <span class="m">60</span><span class="k">))</span><span class="s2">m</span><span class="si">${</span><span class="nv">RST</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ─── 7d rate limit ────────────────────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$SEVEN_PCT</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="s2">&#34;</span><span class="nv">$SEVEN_PCT</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;null&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nv">SI</span><span class="o">=</span><span class="k">$(</span><span class="nb">printf</span> <span class="s1">&#39;%.0f&#39;</span> <span class="s2">&#34;</span><span class="nv">$SEVEN_PCT</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span>   <span class="o">((</span> SI &gt;<span class="o">=</span> <span class="m">80</span> <span class="o">))</span><span class="p">;</span> <span class="k">then</span> <span class="nv">SC</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$RED</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">elif</span> <span class="o">((</span> SI &gt;<span class="o">=</span> <span class="m">50</span> <span class="o">))</span><span class="p">;</span> <span class="k">then</span> <span class="nv">SC</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$YELLOW</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span> <span class="nv">SC</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$SUBTEXT</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">fi</span>
</span></span><span class="line"><span class="cl">  <span class="nv">RLIM</span><span class="o">+=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">SEP</span><span class="si">}${</span><span class="nv">SC</span><span class="si">}</span><span class="s2">7d: </span><span class="si">${</span><span class="nv">SI</span><span class="si">}</span><span class="s2">%</span><span class="si">${</span><span class="nv">RST</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ─── Cost ─────────────────────────────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">COST_SEG</span><span class="o">=</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$COST</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="s2">&#34;</span><span class="nv">$COST</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;null&#34;</span> <span class="o">&amp;&amp;</span> <span class="s2">&#34;</span><span class="nv">$COST</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;0&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nv">COST_FMT</span><span class="o">=</span><span class="k">$(</span><span class="nb">printf</span> <span class="s1">&#39;$%.2f&#39;</span> <span class="s2">&#34;</span><span class="nv">$COST</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">  <span class="nv">COST_SEG</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">SEP</span><span class="si">}${</span><span class="nv">OVERLAY</span><span class="si">}${</span><span class="nv">COST_FMT</span><span class="si">}${</span><span class="nv">RST</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ═══ LINE 2: context │ 5h │ 7d │ cost ════════════════════════════</span>
</span></span><span class="line"><span class="cl"><span class="nb">printf</span> <span class="s1">&#39;%s\n&#39;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BC</span><span class="si">}${</span><span class="nv">BAR</span><span class="si">}${</span><span class="nv">RST</span><span class="si">}</span><span class="s2"> </span><span class="si">${</span><span class="nv">PCT</span><span class="si">}</span><span class="s2">%</span><span class="si">${</span><span class="nv">RLIM</span><span class="si">}${</span><span class="nv">COST_SEG</span><span class="si">}</span><span class="s2">&#34;</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="125">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 125 lines</span>
    </div>
</div>
<p>Things worth noting:</p>
<ul>
<li>Single <code>jq</code> call extracts all fields at once instead of spawning a separate process per field. The script runs after every assistant message, so speed matters.</li>
<li>Git status is cached for 5 seconds in <code>/tmp/</code>, keyed by directory hash. Without this, <code>git status</code> on large repos adds noticeable lag.</li>
<li>Directory name is a clickable 
<a href="https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda" target="_blank" rel="nofollow noopener">OSC 8</a>
 hyperlink that opens the folder in your file manager.</li>
<li>Effort level isn&rsquo;t in the JSON payload, so the script reads it directly from <code>settings.json</code>.</li>
<li>Context thresholds: green under 40%, yellow 40-60%, red above 60%. Same color logic for rate limits at 50% and 80%.</li>
<li>Segments hide when data isn&rsquo;t available. Cost disappears at $0, rate limits disappear before the first API response, git disappears outside a repo.</li>
</ul>
<h2 id="testing-with-mock-data">
Testing with mock data
<a href="#testing-with-mock-data" class="heading-anchor" aria-label="Anchor link for: Testing with mock data">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Pipe a JSON object to the script to test without a running Claude Code session:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">bash -c <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">  RESET=$(($(date +%s) + 7920))
</span></span></span><span class="line"><span class="cl"><span class="s1">  cat &lt;&lt;EOF | ~/.claude/statusline.sh
</span></span></span><span class="line"><span class="cl"><span class="s1">{
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;model&#34;: {&#34;display_name&#34;: &#34;Opus 4.6 (1M context)&#34;},
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;workspace&#34;: {&#34;current_dir&#34;: &#34;/Users/me/projects/myapp&#34;},
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;context_window&#34;: {&#34;used_percentage&#34;: 35, &#34;context_window_size&#34;: 1000000},
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;rate_limits&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;five_hour&#34;: {&#34;used_percentage&#34;: 23.5, &#34;resets_at&#34;: $RESET},
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;seven_day&#34;: {&#34;used_percentage&#34;: 12.8}
</span></span></span><span class="line"><span class="cl"><span class="s1">  },
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;cost&#34;: {&#34;total_cost_usd&#34;: 1.47}
</span></span></span><span class="line"><span class="cl"><span class="s1">}
</span></span></span><span class="line"><span class="cl"><span class="s1">EOF
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div></div>
</div>
<p>This produces two lines:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">✦ Opus 4.6 (1M context) │ ▲ high │ ◫ myapp
</span></span><span class="line"><span class="cl">███░░░░░░░ 35% │ ⏱ 5h: 24% 2h12m │ 7d: 13% │ $1.47</span></span></code></pre></div></div>
</div>
<p>The git segment (branch, staged/modified counts) appears when <code>workspace.current_dir</code> points to a git repository. The reset countdown (<code>2h12m</code>) is calculated live from <code>resets_at</code>.</p>
<p>With minimal data (new session, no rate limits yet):</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">✦ Opus 4.6 (1M context) │ ▲ high │ ◫ tmp
</span></span><span class="line"><span class="cl">░░░░░░░░░░ 0%</span></span></code></pre></div></div>
</div>
]]></content:encoded></item><item><title>Remove Co-Author Attribution from Commits and PRs</title><link>https://rdiachenko.com/til/claude-code/remove-co-author-attribution/</link><pubDate>Sun, 29 Mar 2026 17:22:58 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/til/claude-code/remove-co-author-attribution/</guid><description>Claude Code adds a Co-Authored-By trailer to commits and a note to PR descriptions by default. Set empty strings in settings.json to disable it.</description><content:encoded><![CDATA[<p>Claude Code adds a <code>Co-Authored-By</code> trailer to every commit and attribution text to PR descriptions by default. If you want clean commits without the co-author line, add this to your <code>~/.claude/settings.json</code>:</p>
<div class="code-block" data-frame="editor">
        <div class="code-header">
                <span class="code-filename">~/.claude/settings.json</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsonc" data-lang="jsonc"><span class="line"><span class="cl"><span class="s2">&#34;attribution&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;commit&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;pr&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div></div>
</div>
<p>Setting both to empty strings disables the attribution entirely.</p>
<p>Before:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">feat: add retry logic for failed API calls
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;</span></span></code></pre></div></div>
</div>
<p>After:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">feat: add retry logic for failed API calls</span></span></code></pre></div></div>
</div>
]]></content:encoded></item><item><title>How a Background Task Ate 324 GB of Disk Space</title><link>https://rdiachenko.com/posts/troubleshooting/claude-code-bg-task-disk-bug/</link><pubDate>Mon, 16 Mar 2026 18:15:02 +0000</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/troubleshooting/claude-code-bg-task-disk-bug/</guid><description>A background task in Claude Code silently grew a single .output file to 324 GB in 16 minutes on my Mac. I went through the session logs to understand why. Turns out others have seen up to 740 GB from the same unbounded output files.</description><content:encoded><![CDATA[<p>Claude Code has 
<a href="https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20no%3Atype%20label%3Abug" target="_blank" rel="nofollow noopener">3,617 open bugs</a>
. Today I ran into several of them at once.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/claude-code-bg-task-disk-bug/claude-code-open-bugs-cover_hu_6dac38a3ec7239fb.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="440">
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/claude-code-bg-task-disk-bug/claude-code-open-bugs-cover_hu_603bf08d1fc6b8cb.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="628">
            <img
                    src="https://rdiachenko.com/posts/troubleshooting/claude-code-bg-task-disk-bug/claude-code-open-bugs-cover_hu_603bf08d1fc6b8cb.webp"
                    alt="Claude Code GitHub issue tracker filtered by open bugs"
                    width="1200"
                    height="628"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Claude Code open bugs in the GitHub issue tracker.</small></figcaption></figure>
<p>I asked Claude Code (v2.1.74) to review a research document I was working on. The prompt: &ldquo;deeply check the research.md and address all the notes I left. Do additional research if needed and update the doc.&rdquo;</p>
<p>It found 11 notes, launched 5 background research agents, and got to work. Then it tried to check on their progress. By the time I noticed something was wrong, a single <code>.output</code> file had consumed 324 GB on my Mac.</p>
<h2 id="finding-the-damage">
Finding the damage
<a href="#finding-the-damage" class="heading-anchor" aria-label="Anchor link for: Finding the damage">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>After the session, Disk Utility showed 21 GB free out of ~995 GB. That didn&rsquo;t look right. I opened a new Claude Code session and asked it to help track down whatever was eating my storage. Standard suspects (home directory, Docker volumes, caches) all looked clean. Then I pointed it at the suspicious background task I had killed and asked it to check <code>/private/tmp/</code>:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> du -sh /private/tmp/claude-501/
</span></span><span class="line"><span class="cl"><span class="go">324G
</span></span></span></code></pre></div></div>
</div>
<p>Claude Code stores background task output in <code>.output</code> files under <code>/private/tmp/claude-&lt;uid&gt;/&lt;project-path&gt;/tasks/</code>. The base path is configurable via <code>CLAUDE_CODE_TMPDIR</code>.</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> du -sh .../tasks/*
</span></span><span class="line"><span class="cl"><span class="go">324G    .../tasks/bday3zjvd.output          ← background bash task
</span></span></span><span class="line"><span class="cl"><span class="go">  0B    .../tasks/ab82d716c855dd5ba.output  ← agent (completed normally)
</span></span></span><span class="line"><span class="cl"><span class="go">  0B    .../tasks/a9bffb61f67ddceae.output
</span></span></span><span class="line"><span class="cl"><span class="go">  0B    .../tasks/a4b988afd22aa8db1.output
</span></span></span><span class="line"><span class="cl"><span class="go">  0B    .../tasks/a417efc2a4f409639.output
</span></span></span><span class="line"><span class="cl"><span class="go">  0B    .../tasks/a2ded7593505f4524.output
</span></span></span></code></pre></div></div>
</div>
<p>A single file got all 324 GB. The 5 agent files were 0 bytes.</p>
<p>Claude Code handles bash tasks and agent tasks differently. For bash tasks, stdout and stderr go to the <code>b*.output</code> file with no size limit.</p>
<p>For agents, each subagent writes a JSONL (newline-delimited JSON) session log under <code>~/.claude/projects/</code>, and the <code>a*.output</code> file is a symlink to it. When an agent finishes, Claude Code queues a <code>task-notification</code> message for the main session with the agent&rsquo;s results. The agent files showed as 0 bytes, possibly because the symlink targets had been cleaned up by the time I checked.</p>
<p>A <code>tail -5</code> loop can&rsquo;t produce 324 GB of text. So what was writing to that file?</p>
<h2 id="reconstructing-the-timeline">
Reconstructing the timeline
<a href="#reconstructing-the-timeline" class="heading-anchor" aria-label="Anchor link for: Reconstructing the timeline">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>To find out, I compared timestamps from the main session log at <code>~/.claude/projects/&lt;project-path&gt;/&lt;session-id&gt;.jsonl</code> against the subagent logs:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Reconstructed from session 462e8822-5f7d-428d-a444-33602f3b8a92
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">20:45:26  Claude Code launches 4 research agents
</span></span><span class="line"><span class="cl">20:46:00  5th agent launched (GitHub milestones)
</span></span><span class="line"><span class="cl">20:46:05  ls .../tasks/*.output → 6 files found (5 agents + bash task&#39;s own file)
</span></span><span class="line"><span class="cl">20:46:10  Bash: for loop with tail -5 on each .output file
</span></span><span class="line"><span class="cl">20:46:51  Agent a417efc completes (rig-core)        ← notification queued
</span></span><span class="line"><span class="cl">20:47:17  Agent a9bffb6 completes (Coolify)         ← notification queued
</span></span><span class="line"><span class="cl">20:47:23  Agent ab82d71 completes (GitHub images)   ← notification queued
</span></span><span class="line"><span class="cl">20:47:25  Agent a4b988a completes (milestones)      ← notification queued
</span></span><span class="line"><span class="cl">20:47:48  Agent a2ded75 completes (Telegram)        ← notification queued
</span></span><span class="line"><span class="cl">          All 5 agents done. For loop also likely done.
</span></span><span class="line"><span class="cl">          But the Bash process stays alive for 2 more minutes.
</span></span><span class="line"><span class="cl">20:48:13  Bash command backgrounded → task bday3zjvd
</span></span><span class="line"><span class="cl">          stdout: &#34;&#34;, assistantAutoBackgrounded: false
</span></span><span class="line"><span class="cl">          .output FILE GROWTH STARTS HERE (~340 MB/s)
</span></span><span class="line"><span class="cl">20:48:16  Claude Code fetches TaskOutput tool via ToolSearch
</span></span><span class="line"><span class="cl">20:48:19  TaskOutput(ab82d71) → &#34;No task found&#34;     ← 1 ms response
</span></span><span class="line"><span class="cl">20:48:20  TaskOutput(a2ded75) → &#34;No task found&#34;     ← 1 ms response
</span></span><span class="line"><span class="cl">20:48:21  TaskOutput(a9bffb6) → &#34;No task found&#34;     ← 1 ms response
</span></span><span class="line"><span class="cl">20:48:22  TaskOutput(a417efc) → &#34;No task found&#34;     ← 1 ms response
</span></span><span class="line"><span class="cl">20:48:22  TaskOutput(a4b988a) → &#34;No task found&#34;     ← 1 ms response
</span></span><span class="line"><span class="cl">20:48:25  TaskOutput(bday3zjvd, block=true, timeout=10s)
</span></span><span class="line"><span class="cl">20:48:35  TaskOutput returns: timeout, status: running, ~30K chars
</span></span><span class="line"><span class="cl">20:49:17  &#34;agents running into permission issues with web search...
</span></span><span class="line"><span class="cl">          enough domain knowledge&#34; → starts document rewrite
</span></span><span class="line"><span class="cl">20:57:59  All 5 agent notifications dequeued simultaneously
</span></span><span class="line"><span class="cl">          (~10 min delay: agents finished at 20:47, consumed at 20:58)
</span></span><span class="line"><span class="cl">          Claude Code adds extra findings via targeted edits
</span></span><span class="line"><span class="cl">21:04:23  I killed bday3zjvd → 324 GB over ~16 min of growth</span></span></code></pre></div></div>
</div>
<p>The agents finish within 2 minutes. Each completion queues a <code>task-notification</code>, but those notifications don&rsquo;t arrive for another 10 minutes, not until the model finishes the document rewrite.</p>
<h3 id="the-innocent-tail-command">
The innocent <code>tail</code> command
<a href="#the-innocent-tail-command" class="heading-anchor" aria-label="Anchor link for: The innocent tail command">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>After launching the agents, Claude Code runs a <code>for</code> loop with <code>tail -5</code> on each <code>.output</code> file to check on their progress:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">⏺ Bash<span class="o">(</span><span class="k">for</span> f in /private/tmp/claude-501/.../tasks/*.output<span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">do</span> <span class="nb">echo</span> <span class="s2">&#34;=== </span><span class="k">$(</span>basename <span class="nv">$f</span><span class="k">)</span><span class="s2"> ===&#34;</span><span class="p">;</span> tail -5 <span class="s2">&#34;</span><span class="nv">$f</span><span class="s2">&#34;</span><span class="p">;</span> echo<span class="p">;</span> <span class="k">done</span><span class="o">)</span></span></span></code></pre></div></div>
</div>
<p><code>tail -5</code> reads the last 5 lines and exits. Without the <code>-f</code> flag, it doesn&rsquo;t follow or wait for new data. A <code>for</code> loop over 6 files should finish in under a second. But the Bash process doesn&rsquo;t return for 2 full minutes.</p>
<p>I don&rsquo;t have a good explanation for this. It could be something in Claude Code&rsquo;s bash execution wrapper, or stdout buffering between the shell process and the <code>.output</code> file. The timing roughly lines up with the agents finishing their work (the last one completes at 20:47:48), but I can&rsquo;t draw a causal link.</p>
<p>Then Claude Code backgrounds the command as task <code>bday3zjvd</code>:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;toolUseResult&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;stdout&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;stderr&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;backgroundTaskId&#34;</span><span class="p">:</span> <span class="s2">&#34;bday3zjvd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;assistantAutoBackgrounded&#34;</span><span class="p">:</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>A command 
<a href="https://code.claude.com/docs/en/interactive-mode#background-bash-commands" target="_blank" rel="nofollow noopener">can be backgrounded</a>
 in 3 ways:</p>
<ol>
<li>Claude Code passing <code>run_in_background: true</code> in the Bash tool call</li>
<li>A user pressing <code>Ctrl+B</code></li>
<li>Automatic timeout. Since 
<a href="https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md#2019" target="_blank" rel="nofollow noopener">v2.0.19</a>
, commands exceeding <code>BASH_DEFAULT_TIMEOUT_MS</code> (
<a href="https://github.com/anthropics/claude-code/issues/25881" target="_blank" rel="nofollow noopener">default 120s</a>
) are auto-backgrounded instead of killed</li>
</ol>
<p>Claude Code didn&rsquo;t request it (<code>run_in_background</code> was <code>false</code>). I didn&rsquo;t press <code>Ctrl+B</code>. That leaves auto-timeout, and the ~2-minute runtime matches the 120s default exactly. The <code>assistantAutoBackgrounded</code> flag shows <code>false</code>, which appears to be a logging bug: the command was auto-backgrounded at the timeout, but the flag wasn&rsquo;t set correctly.</p>
<p>Once backgrounded, Claude Code redirects stdout/stderr into the <code>.output</code> file. These files have no size limit, and are 
<a href="https://github.com/anthropics/claude-code/issues/26911" target="_blank" rel="nofollow noopener">never cleaned up after sessions end</a>
.</p>
<p>Could the <code>for</code> loop be reading its own output file? The glob <code>*.output</code> expands in the same <code>tasks/</code> directory where <code>bday3zjvd.output</code> is created. I reproduced the command in a fresh session: Claude Code creates the <code>.output</code> file <em>before</em> starting the bash process, so the glob does include it. <code>tail -5</code> reads back its own earlier echo lines, roughly doubling the output. But it reads at most 5 lines and exits. The entire loop completes in under 30 milliseconds. Self-reference can&rsquo;t explain 324 GB.</p>
<p>Before deleting the file, I checked the first 2000 bytes with <code>head -c 2000</code>. The beginning was normal: agent JSONL entries prefixed by <code>=== filename ===</code> headers from the <code>echo</code> commands. Exactly what the <code>for</code>/<code>tail</code> loop would produce. But 2000 bytes out of 324 GB tells me nothing about what filled the rest.</p>
<p>Since stdout is redirected directly to the file, writes should stop when the bash process exits. Yet the file grew for 16 minutes. Either the bash process itself stayed alive far longer than the <code>for</code> loop, or something else was writing to that file.</p>
<h3 id="growing-in-the-background">
Growing in the background
<a href="#growing-in-the-background" class="heading-anchor" aria-label="Anchor link for: Growing in the background">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>With the command now running in the background, Claude Code tried to check on its agents via <code>TaskOutput</code>, but all five calls returned &ldquo;No task found&rdquo; in 1 ms. It also checked the bash task, calling <code>TaskOutput</code> with <code>block: true</code> and a 10-second timeout, and got back ~30,000 characters of output containing agent JSONL with MCP tool permission denials. Claude Code concluded the agents were &ldquo;running into permission issues with web search&rdquo; and decided it had &ldquo;enough domain knowledge&rdquo; to proceed without them.</p>
<p>It moved on to rewriting the document. The background bash task kept running.</p>
<p>The agents had delivered their results to the notification queue 2 minutes earlier. Those notifications sat for 10 minutes while Claude Code generated a 52 KB rewrite of the document, then were all dequeued simultaneously. It scanned the findings and applied a few extra details via targeted edits.</p>
<p>Meanwhile, <code>bday3zjvd.output</code> kept growing.</p>
<p>16 minutes after backgrounding, I noticed the task was still alive. I selected it in the task list, saw infinite scrolling output, and killed it:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">⏺ Background <span class="nb">command</span> <span class="s2">&#34;Check last lines of each agent output&#34;</span> was stopped</span></span></code></pre></div></div>
</div>
<h3 id="no-task-found">
No task found
<a href="#no-task-found" class="heading-anchor" aria-label="Anchor link for: No task found">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Those five &ldquo;No task found&rdquo; errors deserve a closer look. They left Claude Code unable to poll for agent status, so it read the bash task&rsquo;s output instead and mistakenly concluded the agents had failed:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">⏺ Task Output<span class="o">(</span>non-blocking<span class="o">)</span> ab82d716c855dd5ba
</span></span><span class="line"><span class="cl">  ⎿  Error: No task found with ID: ab82d716c855dd5ba
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">⏺ Task Output<span class="o">(</span>non-blocking<span class="o">)</span> a2ded7593505f4524
</span></span><span class="line"><span class="cl">  ⎿  Error: No task found with ID: a2ded7593505f4524
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">⏺ Task Output<span class="o">(</span>non-blocking<span class="o">)</span> a9bffb61f67ddceae
</span></span><span class="line"><span class="cl">  ⎿  Error: No task found with ID: a9bffb61f67ddceae
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">⏺ Task Output<span class="o">(</span>non-blocking<span class="o">)</span> a417efc2a4f409639
</span></span><span class="line"><span class="cl">  ⎿  Error: No task found with ID: a417efc2a4f409639
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">⏺ Task Output<span class="o">(</span>non-blocking<span class="o">)</span> a4b988afd22aa8db1
</span></span><span class="line"><span class="cl">  ⎿  Error: No task found with ID: a4b988afd22aa8db1</span></span></code></pre></div></div>
</div>
<p>The agents had finished 30 seconds before these calls. There&rsquo;s a 
<a href="https://github.com/anthropics/claude-code/issues/27371" target="_blank" rel="nofollow noopener">known bug</a>
 where Claude Code removes completed tasks from the registry too early, but I think something simpler is going on here. Every call returned in 1 ms, as if the IDs weren&rsquo;t recognized at all.</p>
<p>Compare that to the bash task <code>bday3zjvd</code>, which <code>TaskOutput</code> found instantly. The IDs even look different: agents get an <code>a</code> prefix with long hex, bash tasks get a <code>b</code> prefix with short alphanumeric. <code>TaskOutput</code> doesn&rsquo;t seem to recognize agent IDs. Others have 
<a href="https://github.com/anthropics/claude-code/issues/16667" target="_blank" rel="nofollow noopener">reported a similar issue</a>
.</p>
<h2 id="cleanup-and-prevention">
Cleanup and prevention
<a href="#cleanup-and-prevention" class="heading-anchor" aria-label="Anchor link for: Cleanup and prevention">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>After killing the task, I needed to reclaim the space. Check how much background tasks are using:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> du -sh /private/tmp/claude-*/
</span></span><span class="line"><span class="cl"><span class="go">324G    /private/tmp/claude-501/
</span></span></span></code></pre></div></div>
</div>
<p>Then remove the project subdirectory that&rsquo;s consuming space:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rm -rf /private/tmp/claude-501/&lt;project-path&gt;</span></span></code></pre></div></div>
</div>
<p>Free space went from 21 GB to 344 GB.</p>
<p>On macOS, <code>/private/tmp</code> is not reliably cleaned on reboot. These files can persist indefinitely.</p>
<p>You may set <code>CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1</code> to disable background tasks entirely. This prevents <code>.output</code> files from growing unchecked, but also means Claude Code can&rsquo;t run long commands while continuing other work.</p>
<h2 id="reflections">
Reflections
<a href="#reflections" class="heading-anchor" aria-label="Anchor link for: Reflections">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>This bug is quiet. No warnings, no errors, no disk space alerts. I caught it only because I happened to notice the background task indicator still blinking. If I hadn&rsquo;t, it would have filled the disk.</p>
<p>Other users have reported similar <code>.output</code> file growth: 
<a href="https://github.com/anthropics/claude-code/issues/26911" target="_blank" rel="nofollow noopener">537 GB</a>
, 
<a href="https://github.com/anthropics/claude-code/issues/16130" target="_blank" rel="nofollow noopener">160 GB</a>
, 
<a href="https://github.com/anthropics/claude-code/issues/26911#issuecomment-4038077195" target="_blank" rel="nofollow noopener">740 GB</a>
. The triggers vary (some involve failing commands dumping error output), but the underlying problem is the same: unbounded <code>.output</code> files with no cleanup. As of March 2026, no built-in fix exists. If your Mac is unexpectedly low on space, check <code>/private/tmp/claude-*</code>.</p>
<p>I checked the first 2000 bytes of the file before deleting it, but that only showed the normal <code>for</code>/<code>tail</code> output. I should have checked the middle or end to see what was filling the other 324 GB.</p>
<p>Claude Code ships daily. That pace is impressive, but 3,617 open bugs is the other side of it, and the quiet ones are the most expensive.</p>
]]></content:encoded></item><item><title>My 2025 Year in Books</title><link>https://rdiachenko.com/posts/books/year-in-books-2025/</link><pubDate>Wed, 24 Dec 2025 18:10:15 +0000</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/books/year-in-books-2025/</guid><description>The 12 books that shaped my thinking in 2025, from logic and Rust to Dostoevsky and nutrition.</description><content:encoded><![CDATA[<p>This year I read 24 books. Not all of them were great. Below are the 12 I found most useful and insightful. The full list is on 
<a href="https://www.goodreads.com/user/year_in_books/2025/123910456" target="_blank" rel="nofollow noopener">Goodreads</a>
.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/books/year-in-books-2025/year-in-books-2025-cover_hu_4e1fa2136d59fd0.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="560">
            <source
                    srcset="https://rdiachenko.com/posts/books/year-in-books-2025/year-in-books-2025-cover_hu_2ab65b58f6253c17.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="800">
            <img
                    src="https://rdiachenko.com/posts/books/year-in-books-2025/year-in-books-2025-cover_hu_2ab65b58f6253c17.webp"
                    alt="My 2025 Reading Highlights"
                    width="1200"
                    height="800"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. My 2025 Reading Highlights</small></figcaption></figure>
<h2 id="the-best-of-the-year">
The best of the year
<a href="#the-best-of-the-year" class="heading-anchor" aria-label="Anchor link for: The best of the year">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<h3 id="the-rust-programming-language-by-steve-klabnik-carol-nichols">
The Rust Programming Language by Steve Klabnik, Carol Nichols
<a href="#the-rust-programming-language-by-steve-klabnik-carol-nichols" class="heading-anchor" aria-label="Anchor link for: The Rust Programming Language by Steve Klabnik, Carol Nichols">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>This was my third or fourth attempt to get through it, and this time I made it. If you&rsquo;re switching to Rust from another language, this is the book to start with. It has clear step-by-step explanations, tons of examples, and a few projects for hands-on practice.</p>
<p>Many times, when I had a question after reading a paragraph, the very next one answered it. Not many technical books have this quality. <strong>5/5</strong></p>
<h3 id="how-not-to-die-by-michael-greger-gene-stone">
How Not to Die by Michael Greger, Gene Stone
<a href="#how-not-to-die-by-michael-greger-gene-stone" class="heading-anchor" aria-label="Anchor link for: How Not to Die by Michael Greger, Gene Stone">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>This book is packed with actionable nutrition advice, and it pushed me to change what I eat. I&rsquo;m already seeing positive changes. <strong>5/5</strong></p>
<h3 id="i-may-be-wrong-by-bjørn-natthiko-lindeblad">
I May Be Wrong by Bjørn Natthiko Lindeblad
<a href="#i-may-be-wrong-by-bj%c3%b8rn-natthiko-lindeblad" class="heading-anchor" aria-label="Anchor link for: I May Be Wrong by Bjørn Natthiko Lindeblad">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Deep reflections on a monk&rsquo;s life. It shows how easily we get lost in plans, ideas, and thoughts about the past and future, while forgetting to live in the only moment we truly have: now.</p>
<p>Three ideas that stayed with me:</p>
<ul>
<li>&ldquo;You won&rsquo;t always have what you want, but you&rsquo;ll always have what you need.&rdquo;</li>
<li>&ldquo;This too shall pass.&rdquo;</li>
<li>&ldquo;I may be wrong.&rdquo; <strong>5/5</strong></li>
</ul>
<h3 id="the-wealth-ladder-by-nick-maggiulli">
The Wealth Ladder by Nick Maggiulli
<a href="#the-wealth-ladder-by-nick-maggiulli" class="heading-anchor" aria-label="Anchor link for: The Wealth Ladder by Nick Maggiulli">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The only financial book I read this year, and I enjoyed it a lot. It suggests a clear framework for thinking about wealth as a series of levels. The book shows which strategies work at certain levels but not at others, and explains the &ldquo;why&rdquo; behind them.</p>
<p>Many sources give you strategies without telling you that at certain levels they just don&rsquo;t work, which leads to people getting stuck. This book helped me see wealth from a different angle and gave me clarity on what to stop doing and what to start doing. <strong>5/5</strong></p>
<h3 id="the-insulted-and-humiliated-by-fyodor-dostoevsky">
The Insulted and Humiliated by Fyodor Dostoevsky
<a href="#the-insulted-and-humiliated-by-fyodor-dostoevsky" class="heading-anchor" aria-label="Anchor link for: The Insulted and Humiliated by Fyodor Dostoevsky">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>A treasure. It took me over a year to finish, but I didn&rsquo;t push. I read it occasionally in the evenings.</p>
<p>The story is emotionally deep. It shows how people interact, what they feel, and what they do to each other. Dostoevsky highlights core society problems that remain relevant today. The book was published in 1861, but the human behaviors it captures feel like the author wrote it in 2025. <strong>5/5</strong></p>
<h3 id="doctor-zhivago-by-boris-pasternak">
Doctor Zhivago by Boris Pasternak
<a href="#doctor-zhivago-by-boris-pasternak" class="heading-anchor" aria-label="Anchor link for: Doctor Zhivago by Boris Pasternak">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Another 5-star novel, and one that feels larger than its characters. It&rsquo;s about a whole generation, about a whole country that will never be the same again, about people who couldn&rsquo;t accept and adapt to the changes brought by the 1917 Revolution in Russia. A story about love, identity, and survival. <strong>5/5</strong></p>
<h3 id="writing-for-developers-by-piotr-sarna-cynthia-dunlop">
Writing for Developers by Piotr Sarna, Cynthia Dunlop
<a href="#writing-for-developers-by-piotr-sarna-cynthia-dunlop" class="heading-anchor" aria-label="Anchor link for: Writing for Developers by Piotr Sarna, Cynthia Dunlop">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>A refreshing perspective on blogging. The first part walks through why and how to write a solid post, although I sometimes found it overwhelming, with too many details. The second part is the highlight: real posts, broken down and analysed. It was truly useful, and fun to read. <strong>4/5</strong></p>
<h3 id="docker-deep-dive-by-nigel-poulton">
Docker Deep Dive by Nigel Poulton
<a href="#docker-deep-dive-by-nigel-poulton" class="heading-anchor" aria-label="Anchor link for: Docker Deep Dive by Nigel Poulton">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>I read the May 2025 edition. Despite the title, I&rsquo;d call it a solid overview rather than a true deep dive. It was a great refresher that updated my rusty Docker knowledge and filled a few gaps in my understanding of the core concepts. The newer chapters, like running LLMs in containers and WebAssembly apps, were a nice bonus. <strong>4/5</strong></p>
<h3 id="flatland-by-edwin-a-abbott">
Flatland by Edwin A. Abbott
<a href="#flatland-by-edwin-a-abbott" class="heading-anchor" aria-label="Anchor link for: Flatland by Edwin A. Abbott">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>This book is often framed as a maths story. To me, it&rsquo;s really about society, hierarchy, and what people do when their worldview gets challenged. It&rsquo;s short, strange, and still relevant. <strong>4/5</strong></p>
<h3 id="doom-guy-by-john-romero">
Doom Guy by John Romero
<a href="#doom-guy-by-john-romero" class="heading-anchor" aria-label="Anchor link for: Doom Guy by John Romero">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>I read <em>Masters of Doom</em> by David Kushner last year and enjoyed it a lot. <em>Doom Guy</em> is a good companion, and it gives a different, mainly subjective, perspective on that period. Still, it&rsquo;s full of hard lessons, and Romero&rsquo;s reflections are the best part.</p>
<p>I think I&rsquo;m finally done with this chapter of gaming history. If you enjoyed <em>Masters of Doom</em>, this is worth picking up. <strong>4/5</strong></p>
<h3 id="ai-engineering-by-chip-huyen">
AI Engineering by Chip Huyen
<a href="#ai-engineering-by-chip-huyen" class="heading-anchor" aria-label="Anchor link for: AI Engineering by Chip Huyen">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>This was a tough one and took me a few months to finish. It gives a broad overview of the AI landscape. Some chapters go wide rather than deep, while others, like entropy and fine-tuning, go much deeper. Because of that, the target audience isn&rsquo;t always clear. Some sections are simple enough for non-technical managers, while others may challenge even experienced engineers.</p>
<p>I don&rsquo;t think this is a good first AI book. The more experience you have, the more you&rsquo;ll get out of it. It reminded me of <em>Designing Data-Intensive Applications</em> by Kleppmann. When I first read it early in my career, much of it didn&rsquo;t make sense, but years later it became far more valuable. I may revisit <em>AI Engineering</em> in a few years to close the knowledge gaps. <strong>4/5</strong></p>
<h3 id="the-logic-manual-by-volker-halbach">
The Logic Manual by Volker Halbach
<a href="#the-logic-manual-by-volker-halbach" class="heading-anchor" aria-label="Anchor link for: The Logic Manual by Volker Halbach">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>I started the year with this one, which was a slightly brutal way to begin. It&rsquo;s hard to follow, and I wish it had more examples and practical applications. A few concepts, like the split between propositional and predicate logic, only clicked later when I started reading <em>How to Prove It</em> by Velleman. I&rsquo;ll likely come back to it next year. <strong>3/5</strong></p>
<h2 id="the-worst-of-the-year">
The worst of the year
<a href="#the-worst-of-the-year" class="heading-anchor" aria-label="Anchor link for: The worst of the year">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<h3 id="how-to-write-a-lot-by-paul-j-silvia">
How to Write a Lot by Paul J. Silvia
<a href="#how-to-write-a-lot-by-paul-j-silvia" class="heading-anchor" aria-label="Anchor link for: How to Write a Lot by Paul J. Silvia">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The most disappointing book I read this year. The core message is simple: schedule writing time and protect it. That&rsquo;s good advice, but it&rsquo;s delivered early, and the rest felt like filler, mostly aimed at academic publishing. <strong>1/5</strong></p>
<h2 id="next-year">
Next year
<a href="#next-year" class="heading-anchor" aria-label="Anchor link for: Next year">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>I have a few long reads in progress, some of which I mentioned in my post on 

            
        
    <a href="https://rdiachenko.com/posts/math/50-days-logic-linear-algebra/">How LLMs Sent Me Back to Logic and Linear Algebra</a>
. Next year I plan to focus on fundamentals: system architecture, more maths, and AI foundations rather than trends.</p>
<p>AI might replace some coding tasks. It won&rsquo;t replace clear system thinking.</p>
]]></content:encoded></item><item><title>How LLMs Sent Me Back to Logic and Linear Algebra</title><link>https://rdiachenko.com/posts/math/50-days-logic-linear-algebra/</link><pubDate>Wed, 10 Sep 2025 18:11:39 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/math/50-days-logic-linear-algebra/</guid><description>I went back to basics to rebuild my math foundation, and 50 days in, I&amp;rsquo;ve finally started in the right place.</description><content:encoded><![CDATA[<p>50 days ago this equivalence made no sense to me. Today, I can explain it.</p>
$$
\exists !xP(x) ↔︎ \exists x(P(x) \wedge \neg\exists y(P(y) \wedge y \neq x))
$$<p>I went from <em>&ldquo;what the fck is this&rdquo;</em> to <em>&ldquo;ok, there is exactly one value of \(x\) such that \(P(x)\) is true&rdquo;</em>.</p>
<p>This is the first post in my learning journey series: From Logic to Transformers. I want to track my progress, so I can look back on how far I&rsquo;ve come, and maybe help others on a similar path.</p>
<h2 id="why">
Why
<a href="#why" class="heading-anchor" aria-label="Anchor link for: Why">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>I&rsquo;ve been a software engineer for over a decade, mainly focused on backend systems. With all the recent AI developments, I became curious about Large Language Models (LLMs) and the hype around them. Naturally, I tried LLM-based tools in my pet projects and at work. My productivity went up. It felt like having a clever junior dev by my side, someone who can handle the heavy lifting but still needs guidance and direction.</p>
<p>The more I used these tools, the more I wanted to understand how and why they work under the hood. Understanding the <em>why</em> behind the tools has always led to deeper insights and helped me solve harder problems and design better systems.</p>
<p>But LLMs turned out to be a different beast. My attempts to understand them went nowhere. Books and blog posts only raised more questions. I even tried reading research papers, but the math did not click. Words like <em>vector spaces</em> and <em>eigenvectors</em> meant nothing to me. Matrix multiplications just worked in the code, but I had no idea why. I could follow the implementations line by line, but I could not see the reasoning behind them.</p>
<p>That frustration is what pushed me back to the basics.</p>
<h2 id="back-to-basics">
Back to basics
<a href="#back-to-basics" class="heading-anchor" aria-label="Anchor link for: Back to basics">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>When I turned to math for Machine Learning (ML), most books started with vectors and matrices, then moved on to vector spaces and linear algebra. I had studied linear algebra back in university, but no one explained why things worked. Or maybe I just was not paying attention because I did not see how it connected to real-world problems.</p>
<p>I sometimes envy folks who went into game development. They realized early on that linear algebra was essential for graphics and physics, and they built solid foundations. I skipped that step, which is why the math felt like a wall when I hit it later.</p>
<p>Linear algebra starts simple, with vectors and their geometric meaning. But it gets steep quickly. The deeper you go, the more theorems and proofs you meet, each one building on top of the previous one. That is where I got stuck again: why do these proofs work, and how did the author come up with them?</p>
<p>That search for answers is what led me to the missing piece: logic.</p>
<h2 id="finding-the-real-gap">
Finding the real gap
<a href="#finding-the-real-gap" class="heading-anchor" aria-label="Anchor link for: Finding the real gap">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>At that point I realized something important: all proofs are built on logic. Every &ldquo;therefore&rdquo; in a textbook assumed a foundation I didn&rsquo;t have. I had never studied logic formally, beyond a few AND/OR/NOT gates in a hardware course at university. No wonder proofs didn&rsquo;t make sense.</p>
<p>So I decided to start from scratch. Learn logic, and re-learn linear algebra in parallel. One day logic, the next day linear algebra. Repeat until things start to click.</p>
<h2 id="the-study-routine">
The study routine
<a href="#the-study-routine" class="heading-anchor" aria-label="Anchor link for: The study routine">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>I ended up with two books: 
<a href="https://www.goodreads.com/book/show/44921543-how-to-prove-it" target="_blank" rel="nofollow noopener"><em>How to Prove It</em> (3rd edition)</a>
 by Daniel J. Velleman and 
<a href="https://www.goodreads.com/book/show/860481.Introduction_to_Linear_Algebra" target="_blank" rel="nofollow noopener"><em>Introduction to Linear Algebra</em> (2nd edition)</a>
 by Serge Lang. I tried watching videos and courses, but they distracted me and I lost focus, so I narrowed down to just these two resources.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/math/50-days-logic-linear-algebra/logic-and-linear-algebra-books-on-my-desk-cover_hu_9559cbdedaabbedf.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="630">
            <source
                    srcset="https://rdiachenko.com/posts/math/50-days-logic-linear-algebra/logic-and-linear-algebra-books-on-my-desk-cover_hu_91c83b23b6fee192.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="900">
            <img
                    src="https://rdiachenko.com/posts/math/50-days-logic-linear-algebra/logic-and-linear-algebra-books-on-my-desk-cover_hu_91c83b23b6fee192.webp"
                    alt="The two books on my desk: How to Prove It by Velleman and Introduction to Linear Algebra by Lang"
                    width="1200"
                    height="900"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. The Two Books on My Desk</small></figcaption></figure>
<p>The process is simple. I read a chapter and work through the exercises that have answers to check myself. I also attempt others, using ChatGPT to verify or clarify when needed.</p>
<p>While reading, I take quick notes, highlighting anything that stands out or concepts I don&rsquo;t know. After finishing a chapter, <strong>I read it again</strong>, review my notes, and turn them into 
<a href="https://ankiweb.net/" target="_blank" rel="nofollow noopener">Anki cards</a>
. On the second read I almost always catch things I missed, and my understanding gets deeper.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/math/50-days-logic-linear-algebra/daily-study-routine-diagram_hu_c0538f9455519e21.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="342">
            <source
                    srcset="https://rdiachenko.com/posts/math/50-days-logic-linear-algebra/daily-study-routine-diagram_hu_aa725ae968064069.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="489">
            <img
                    src="https://rdiachenko.com/posts/math/50-days-logic-linear-algebra/daily-study-routine-diagram_hu_aa725ae968064069.webp"
                    alt="The diagram of my daily study routine"
                    width="1200"
                    height="489"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. My Daily Study Routine: Review, Read, Practice, Repeat</small></figcaption></figure>
<p>I study in the mornings, usually for 90 to 120 minutes, when my mind is fresh and math is a good fit for that time. The first 30 minutes go to Anki reviews from both decks, no matter if it is a logic or a math day. If the review is quick, I use the extra time to turn more notes into cards.</p>
<p>The next 60 to 90 minutes are for reading: logic one day, math the next. If a subsection ends with exercises, I solve them right away. At first, I could not solve many, and that was fine. Over time, as I built and reviewed more cards, problems that once seemed impossible started to feel manageable, and my intuition grew.</p>
<h2 id="progress-after-50-days">
Progress after 50 days
<a href="#progress-after-50-days" class="heading-anchor" aria-label="Anchor link for: Progress after 50 days">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>So, where am I after 50 days?</p>
<p>In logic, I have finished the Sentential and Quantificational logic chapters and I am now in the middle of Chapter 3 on Proofs. In linear algebra, I went through the chapters on Vectors, Matrices and Linear Equations, and Vector Spaces, and I am now in the middle of Chapter 4 on Linear Mappings.</p>
<h3 id="logic">
Logic
<a href="#logic" class="heading-anchor" aria-label="Anchor link for: Logic">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The most memorable moment in logic was when I managed to derive \(A \backslash B = A\) from \(A \cap B = \emptyset\) and show they are equivalent.</p>
<p>Here is the full chain of reasoning (skip if you are not into the details):</p>
<ul>
<li>\(A \cap B = \emptyset\) is equivalent to \(\neg\exists x(x \in A \wedge x \in B)\) using the set definition</li>
<li>which is equivalent to \(\forall x\neg(x \in A \wedge x \in B)\) using the quantifier negation law</li>
<li>which is equivalent to \(\forall x(x \notin A ∨ x \notin B)\) using De Morgan&rsquo;s law</li>
<li>which is equivalent to \(\forall x(x \in A \rightarrow x \notin B)\) using the conditional law</li>
<li>which is equivalent to \(\forall x((x \notin B \wedge x \in A) ↔︎ x \in A)\) using the biconditional law</li>
<li>which is equivalent to \(\forall x(x \in A \backslash B ↔︎ x \in A)\) using the set definition</li>
<li>which is equivalent to \(A \backslash B = A\).</li>
</ul>
<h3 id="linear-algebra">
Linear algebra
<a href="#linear-algebra" class="heading-anchor" aria-label="Anchor link for: Linear algebra">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The highlight of linear algebra was this exercise:</p>
<blockquote>
<p>Let \(V\), \(W\) be two vector spaces and let \(F: V \rightarrow W\) be a linear map. Let \(U\) be the subset of \(V\) consisting of all elements \(v\) such that \(F(v) = O\). Prove that \(U\) is a subspace of \(V\).</p>
</blockquote>
<p>Here is my scratch work. We need to prove 3 things:</p>
<ol>
<li>\(O \in U\)</li>
<li>if \(v, w \in U\), then \(v + w \in U\)</li>
<li>if \(v \in U\) and \(c\) is a scalar, then \(c * v \in U\)</li>
</ol>
<p>Proof:</p>
<ul>
<li>Since \(F\) is linear, \(F(O) = O\). So \(O \in U\).</li>
<li>Let \(v\) and \(w\) be arbitrary elements of \(U\). Using properties of linear map we have \(F(v + w) = F(v) + F(w) = O + O = O\). So \(v + w \in U\).</li>
<li>Let \(v\) be an arbitrary element of \(U\) and \(c\) is a scalar. Using properties of linear map we have \(F(c * v) = c * F(v) = c * O = O\). So \(c * v \in U\).</li>
<li>Therefore, \(U\) is a subspace of \(V\).</li>
</ul>
<p>To my surprise, I knew exactly what to do and how to prove it. That was the best feeling in the world, and it motivates me to keep going.</p>
<h2 id="reflections-so-far">
Reflections so far
<a href="#reflections-so-far" class="heading-anchor" aria-label="Anchor link for: Reflections so far">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>At first, it felt like a rabbit hole. I did not understand my gaps or where to begin building the foundation. I jumped from LLM blog posts and papers to ML, then to linear algebra, and finally to logic. Now, 50 days in, I feel like I have finally found the right starting point.</p>
<p>The path has not been easy. Some days I spend an hour wrestling with a single logic problem. Other days I manage 6 pages of linear algebra in 2 hours. Progress is uneven, but consistency wins.</p>
<p>Right now, my pace is about 5–6 pages in 90 minutes. That may sound slow, but the goal is not speed. It is building understanding that lasts. Focusing on the process instead of the result makes the work sustainable and even enjoyable.</p>
<p>This journey from Logic to Transformers will take time, but the connections are already becoming clearer. Logic helps me understand the structure of arguments and follow proofs step by step. With that foundation, concepts in linear algebra that once felt like rules to memorize, such as why a set of vectors forms a subspace, now start to make sense. Little by little, the pieces are coming together, and I can see how each layer of understanding builds on top of the previous one.</p>
<h2 id="whats-next">
What&rsquo;s next?
<a href="#whats-next" class="heading-anchor" aria-label="Anchor link for: What&rsquo;s next?">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Over the next 50 days, the plan is to keep going with the books I am reading. I will probably finish linear algebra by then, but if not, that is fine. What matters is showing up every day and moving forward.</p>
<p>I will share updates along the way, including progress, lessons, and next steps. Maybe it will help someone else who feels lost in the <em>why</em>, like I was. I have found my starting point, and I hope you find yours too.</p>
<p>Sometimes the fastest way forward is realizing where you should have started all along. See you in the next post.</p>
]]></content:encoded></item><item><title>The Morning My Synology NAS Went Silent</title><link>https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/</link><pubDate>Thu, 27 Feb 2025 20:53:34 +0000</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/</guid><description>How I brought my Synology NAS back to life. Starting with quick checks, discovering a hidden debug tool, and finally fixing the network issue.</description><content:encoded><![CDATA[<p>It&rsquo;s early morning, and I can&rsquo;t connect to my Synology NAS DS1522+ over the local network. After running without any issues for two months, it suddenly became undiscoverable. Data synchronization jobs stopped on my laptop. All SMB connections dropped. I can&rsquo;t access the NAS via QuickConnect or Tailscale either.</p>
<p>This is the story of my journey to debug a networking issue with my Synology NAS and bring it back to life. I&rsquo;ll walk you through my troubleshooting process, starting with quick checks and gradually digging deeper to find the root cause. Even if you don&rsquo;t own a NAS, I encourage you to read on. The approach I took and the tools I discovered along the way might give you a fresh perspective and help you solve entirely different technical challenges.</p>
<h2 id="basic-troubleshooting-no-quick-fix-in-sight">
Basic troubleshooting: no quick fix in sight
<a href="#basic-troubleshooting-no-quick-fix-in-sight" class="heading-anchor" aria-label="Anchor link for: Basic troubleshooting: no quick fix in sight">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>I restart the NAS. No connection. My router shows the NAS in the list of disconnected clients but provides no additional details in the logs. I restart the router, try a new Ethernet cable, and use different LAN ports on both the NAS and the router. Same issue. The NAS power indicator is solid green. The LAN LEDs are green, indicating no issues with the physical connection, as shown below.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/nas-green-lan-leds-cover_hu_ad9b8334cf754556.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="630">
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/nas-green-lan-leds-cover_hu_adf26cce73572528.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="900">
            <img
                    src="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/nas-green-lan-leds-cover_hu_adf26cce73572528.webp"
                    alt="Green LAN LEDs Indicating Network Connection on Synology NAS"
                    width="1200"
                    height="900"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Green LAN LEDs Indicating Network Connection on Synology NAS</small></figcaption></figure>
<p>I didn&rsquo;t set a static IP address when initially configuring my NAS. Typically, reconnecting to a different LAN port should trigger a 
<a href="https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol" target="_blank" rel="nofollow noopener">DHCP request</a>
 because the network interface initializes a new connection, potentially requiring a new IP address. But that doesn&rsquo;t happen.</p>
<p>In rare cases, a NAS might fall back to a static IP configuration if the DHCP client fails to initialize. I&rsquo;m wondering if the NAS switched to using a static IP. In this case, the router would have detected the expired lease and returned the IP to the pool of available addresses. Another client might have taken it, potentially causing an IP conflict.</p>
<p>I check all connected clients but don&rsquo;t see the NAS IP assigned to any device. This makes me think that the NAS might be using a completely different IP, possibly outside the expected range on my local network. But I still want to be sure it&rsquo;s not a router issue. Let&rsquo;s connect the NAS directly to my MacBook.</p>
<h2 id="narrowing-it-down-direct-connection-and-network-scanning">
Narrowing it down: direct connection and network scanning
<a href="#narrowing-it-down-direct-connection-and-network-scanning" class="heading-anchor" aria-label="Anchor link for: Narrowing it down: direct connection and network scanning">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>I disable Wi-Fi and use a direct Ethernet connection between the NAS and my MacBook to isolate the issue. I want to be sure that no other network components are interfering with the connection. On the MacBook, I open the network settings and configure the Ethernet-to-Type-C adapter to use manual IPv4 settings, as shown below. This is the same range where the NAS IP was last seen.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/manual-macbook-network-settings_hu_abf4db1574dc2260.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="473">
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/manual-macbook-network-settings_hu_b3248f21a2663fca.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="675">
            <img
                    src="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/manual-macbook-network-settings_hu_b3248f21a2663fca.webp"
                    alt="MacBook Manual IPv4 Network Settings for Direct Connection"
                    width="1200"
                    height="675"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. MacBook Manual IPv4 Network Settings for Direct Connection</small></figcaption></figure>
<p>Synology provides a tool called 
<a href="https://kb.synology.com/en-uk/DSM/tutorial/Unable_to_Locate_NAS" target="_blank" rel="nofollow noopener">Web Assistant</a>
 that helps discover NAS devices on the local network. I run the Web Assistant, but it detects nothing. Next, I decide to scan all available IPs in this isolated network, which consists of just the NAS and the MacBook.</p>
<p>I use a network scanning tool called <code>nmap</code>, which checks each IP in a specified range to see if it is reachable. The <code>-sn</code> flag skips port scanning, which speeds up the process since I only need to check for active hosts. I run the following command:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> nmap -sn 192.168.4.0/24
</span></span><span class="line"><span class="cl"><span class="go">Nmap scan report for 192.168.4.1 # MacBook IP address
</span></span></span><span class="line"><span class="cl"><span class="go">Host is up (0.0013s latency).
</span></span></span><span class="line"><span class="cl"><span class="go">Nmap done: 256 IP addresses (1 host up) scanned in 0.01 seconds
</span></span></span></code></pre></div></div>
</div>
<p>The output shows only the IP of my MacBook. For comparison, here&rsquo;s the expected output when the NAS is visible on the network:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> nmap -sn 192.168.4.0/24
</span></span><span class="line"><span class="cl"><span class="go">Nmap scan report for 192.168.4.1 # MacBook IP address
</span></span></span><span class="line"><span class="cl"><span class="go">Host is up (0.00056s latency).
</span></span></span><span class="line"><span class="cl"><span class="go">Nmap scan report for 192.168.4.231 # NAS IP address
</span></span></span><span class="line"><span class="cl"><span class="go">Host is up (0.0010s latency).
</span></span></span><span class="line"><span class="cl"><span class="go">Nmap done: 256 IP addresses (2 hosts up) scanned in 21.58 seconds
</span></span></span></code></pre></div></div>
</div>
<p>This confirms that the router is not causing the issue. Something is wrong with the NAS itself. It&rsquo;s time to reset it.</p>
<h2 id="resetting-the-nas-soft-vs-hard-modes">
Resetting the NAS: soft vs. hard modes
<a href="#resetting-the-nas-soft-vs-hard-modes" class="heading-anchor" aria-label="Anchor link for: Resetting the NAS: soft vs. hard modes">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Synology gives you 
<a href="https://kb.synology.com/en-us/DSM/tutorial/How_to_reset_my_Synology_NAS_7" target="_blank" rel="nofollow noopener">two ways to reset a NAS</a>
:</p>
<ul>
<li><strong>Soft reset</strong> restores the admin account to default and resets the network interfaces to use DHCP.</li>
<li><strong>Hard reset</strong> performs everything a soft reset does but also removes the Synology DiskStation Manager (DSM), the Linux-based operating system that the NAS runs on.</li>
</ul>
<p>I press and hold the <code>RESET</code> button for about 4 seconds until I hear a beep. I release the button and launch the Web Assistant. Nothing happens. I restart the NAS, but the issue persists. The NAS remains undiscoverable. At this point, I suspect a problem with the DHCP client. If the DHCP client isn&rsquo;t initializing correctly, a soft reset won&rsquo;t resolve the issue.</p>
<p>I proceed with a hard reset. This action completely wipes out all system configurations but keeps my data on the hard drives intact. I restart the NAS. The hard drives spin up. The power LED starts blinking, indicating that the DSM is not installed. I launch the Synology Web Assistant again, but it fails to find the NAS. I repeat the same steps using a direct connection to my MacBook. Same result.</p>
<p>So here I am, sitting in front of this black box. It&rsquo;s up and running, but it seems to live a life of its own. I recall that I have a travel router that might provide more detailed logs for connected clients.</p>
<h2 id="seeking-more-clarity-using-a-different-router">
Seeking more clarity: using a different router
<a href="#seeking-more-clarity-using-a-different-router" class="heading-anchor" aria-label="Anchor link for: Seeking more clarity: using a different router">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>I unpack my travel router, connect it to the WLAN, and then connect both the NAS and the MacBook to the router. I check the logs and see some activity from the NAS:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">daemon.notice netifd: Network device &#39;eth2&#39; link is up
</span></span><span class="line"><span class="cl">kern.info kernel: [  219.955222] nss-dp 3a001400.dp3 eth2: PHY Link up speed: 1000
</span></span><span class="line"><span class="cl">kern.info kernel: [  219.955300] br-lan: port 2(eth2) entered forwarding state</span></span></code></pre></div></div>
</div>
<p>And here are the log entries from the MacBook:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">daemon.notice netifd: Network device &#39;eth1&#39; link is up
</span></span><span class="line"><span class="cl">kern.info kernel: [   85.954650] nss-dp 3a001200.dp2 eth1: PHY Link up speed: 1000
</span></span><span class="line"><span class="cl">kern.info kernel: [   85.954811] br-lan: port 1(eth1) entered forwarding state
</span></span><span class="line"><span class="cl">daemon.info dnsmasq-dhcp[4707]: DHCPDISCOVER(br-lan) c8:a3:62:07:1b:37
</span></span><span class="line"><span class="cl">daemon.info dnsmasq-dhcp[4707]: DHCPOFFER(br-lan) 192.168.8.202 c8:a3:62:07:1b:37
</span></span><span class="line"><span class="cl">daemon.info dnsmasq-dhcp[4707]: DHCPREQUEST(br-lan) 192.168.8.202 c8:a3:62:07:1b:37
</span></span><span class="line"><span class="cl">daemon.info dnsmasq-dhcp[4707]: DHCPACK(br-lan) 192.168.8.202 c8:a3:62:07:1b:37 MBP</span></span></code></pre></div></div>
</div>
<p>The MacBook establishes a physical connection over the Ethernet cable and requests an IP address from the router. The router responds with a DHCP offer. The full DHCP handshake completes successfully. As a result, my MacBook successfully gets a new IP address. But there&rsquo;s nothing similar for the NAS. The physical connection works fine (1 Gbps link from the logs), but the NAS isn&rsquo;t making any DHCP requests.</p>
<p>I reset the NAS one more time, switch to a different LAN port on the NAS, and review the router logs:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">daemon.notice netifd: Network device &#39;eth2&#39; link is down
</span></span><span class="line"><span class="cl">kern.info kernel: [  824.964258] nss-dp 3a001400.dp3 eth2: PHY Link is down
</span></span><span class="line"><span class="cl">kern.info kernel: [  824.964680] br-lan: port 2(eth2) entered disabled state
</span></span><span class="line"><span class="cl">daemon.notice netifd: Network device &#39;eth2&#39; link is up
</span></span><span class="line"><span class="cl">kern.info kernel: [  826.964295] nss-dp 3a001400.dp3 eth2: PHY Link up speed: 1000
</span></span><span class="line"><span class="cl">kern.info kernel: [  826.964382] br-lan: port 2(eth2) entered forwarding state</span></span></code></pre></div></div>
</div>
<p>The pattern remains consistent:</p>
<ol>
<li>Link goes down (unplugging)</li>
<li>Link comes up (plugging in)</li>
<li>Port enters forwarding state</li>
<li>But no DHCP or higher-level network activity</li>
</ol>
<p>NAS might be trying to use a static IP outside the local network range due to a DHCP client failure. Or its network stack isn&rsquo;t initializing properly. But how could this happen after a hard reset?</p>
<h2 id="a-game-changer-discovering-a-hidden-debug-tool">
A game-changer: discovering a hidden debug tool
<a href="#a-game-changer-discovering-a-hidden-debug-tool" class="heading-anchor" aria-label="Anchor link for: A game-changer: discovering a hidden debug tool">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>I remove all the hard drives and the SSD cache from my NAS and power it up. Same issue. While removing the SSD cache modules, which are located at the bottom of the NAS, I notice a small cover that reveals part of the motherboard. I remove the cover and see a 6-pin connector. After researching online, I discover that this is a serial console port, also known as a 
<a href="https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter" target="_blank" rel="nofollow noopener">UART debug port</a>
, as shown below.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/uart-debug-port_hu_2cc2856cabb6a40d.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="630">
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/uart-debug-port_hu_6da117dea6b0fe6c.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="900">
            <img
                    src="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/uart-debug-port_hu_6da117dea6b0fe6c.webp"
                    alt="UART Debug Port on Synology NAS Motherboard"
                    width="1200"
                    height="900"
                    loading="lazy">
        </picture><figcaption><small>Figure 3. UART Debug Port on Synology NAS Motherboard</small></figcaption></figure>
<p>The 6 pins are:</p>
<ul>
<li><strong>TX</strong> (Transmit): Sends data from NAS to computer</li>
<li><strong>RX</strong> (Receive): Takes data from computer to NAS</li>
<li><strong>GND</strong> (Ground): Common reference point for electrical signals</li>
<li><strong>VCC</strong> (Power): Power supply (3.3V or 5V), not used as NAS has its own power source</li>
<li><strong>CTS</strong> (Clear to Send): Flow control pin, usually not needed</li>
<li><strong>RTS</strong> (Request to Send): Flow control pin, usually not needed</li>
</ul>
<p>This port is commonly used in embedded systems and electronic devices for debugging, monitoring, and communication. In theory, I can use a 
<a href="https://en.wikipedia.org/wiki/USB-to-serial_adapter" target="_blank" rel="nofollow noopener">USB-to-TTL adapter</a>
 to connect my MacBook to the NAS via this port and access system logs.</p>
<p>The MacBook has the <code>screen</code> command, which can open a connection to the serial console and capture the output from the connected device. The command I run is <code>screen -L /dev/tty.usbserial-* 115200</code>, where:</p>
<ul>
<li><code>-L</code>: Creates <code>screenlog.0</code> in the current directory and logs all output to the file</li>
<li><code>/dev/tty.usbserial-*</code>: Path to the USB-serial device (<code>*</code> matches any serial device name). macOS creates this device when I plug in the USB-TTL adapter</li>
<li><code>115200</code>: Baud rate, the speed of data transmission over the serial connection, which is a standard value for serial devices</li>
</ul>
<p>Strangely, I find no reference to the serial console port in the Synology documentation. And I have no idea which pin is responsible for which function. I need to figure out the pinout.</p>
<p>Out of the 6 pins, I only need to locate 3: <code>GND</code>, <code>RX</code>, and <code>TX</code>. I take a multimeter and set it to continuity mode. I connect the black probe to the metal part on the NAS motherboard and touch the red probe to each pin one by one. Finally, I hear a beep. The ground pin is found.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/multimeter-probes-touching-ground-pin_hu_5495d4e06d0e741f.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="630">
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/multimeter-probes-touching-ground-pin_hu_8d8e2b849a03c2d5.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="900">
            <img
                    src="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/multimeter-probes-touching-ground-pin_hu_8d8e2b849a03c2d5.webp"
                    alt="Identifying Ground Pin Using Multimeter on UART Debug Port"
                    width="1200"
                    height="900"
                    loading="lazy">
        </picture><figcaption><small>Figure 4. Identifying Ground Pin Using Multimeter on UART Debug Port</small></figcaption></figure>
<p>I connect the <code>GND</code> pin to the USB-TTL adapter using a black jumper wire. Next, I need to find the <code>TX</code> pin. I connect the white jumper wire to the adapter&rsquo;s <code>RX</code> pin and test each pin on the NAS UART port until I see messages appear on the MacBook. This is the best feeling I&rsquo;ve had in over a decade in software engineering. I can finally see what&rsquo;s happening inside the NAS:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Synology ready to boot Event() Start
</span></span><span class="line"><span class="cl">Synology ready to boot Event() end
</span></span><span class="line"><span class="cl">Initialize secure boot and secure flash related variables
</span></span><span class="line"><span class="cl">Verify the signature of the image
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">GNU GRUB  version 2.02~beta3
</span></span><span class="line"><span class="cl">   Press &#34;CTRL-C&#34; for boot menu or it boots automatically in 3s.
</span></span><span class="line"><span class="cl">   Press &#34;CTRL-C&#34; for boot menu or it boots automatically in 2s.
</span></span><span class="line"><span class="cl">   Press &#34;CTRL-C&#34; for boot menu or it boots automatically in 1s.
</span></span><span class="line"><span class="cl">   Press &#34;CTRL-C&#34; for boot menu or it boots automatically in 0s.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Booting &#39;SYNOLOGY_2&#39;
</span></span><span class="line"><span class="cl">  Checking file [/zImage]...Passed.
</span></span><span class="line"><span class="cl">  Checking file [/rd.gz]...Passed.
</span></span><span class="line"><span class="cl">...</span></span></code></pre></div></div>
</div>
<p>Since the <code>RX</code> pin is typically located near the <code>TX</code> pin, I test the adjacent pins. After a few attempts, I find the <code>RX</code> pin and connect it to the adapter&rsquo;s <code>TX</code> pin using a red jumper wire, as shown below.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/macbook-to-nas-via-usb-ttl_hu_dde160cc551371b0.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="630">
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/macbook-to-nas-via-usb-ttl_hu_e35d853348d3c9ba.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="900">
            <img
                    src="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/macbook-to-nas-via-usb-ttl_hu_e35d853348d3c9ba.webp"
                    alt="MacBook Connected to Synology NAS via USB-TTL Adapter for Debugging"
                    width="1200"
                    height="900"
                    loading="lazy">
        </picture><figcaption><small>Figure 5. MacBook Connected to Synology NAS via USB-TTL Adapter for Debugging</small></figcaption></figure>
<p>I now have low-level access to the NAS&rsquo;s system logs and can control the device.</p>
<h2 id="digging-deeper-why-are-dhcp-requests-not-sent">
Digging deeper: why are DHCP requests not sent?
<a href="#digging-deeper-why-are-dhcp-requests-not-sent" class="heading-anchor" aria-label="Anchor link for: Digging deeper: why are DHCP requests not sent?">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Synology&rsquo;s DSM is based on Linux. At the end of the boot process, I see a login prompt that says <code>SynologyNAS login:</code>. I try different combinations of <code>root</code> and <code>admin</code> passwords but can&rsquo;t get in. I restart the NAS and access the GRUB bootloader, which shows the following settings:</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/grub-bootloader-parameters_hu_edcfd6a0186e2b67.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="473">
            <source
                    srcset="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/grub-bootloader-parameters_hu_9099fc17bf0b562e.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="675">
            <img
                    src="https://rdiachenko.com/posts/troubleshooting/synology-nas-network-issue/grub-bootloader-parameters_hu_9099fc17bf0b562e.webp"
                    alt="GRUB Bootloader Parameters on Synology NAS"
                    width="1200"
                    height="675"
                    loading="lazy">
        </picture><figcaption><small>Figure 6. GRUB Bootloader Parameters on Synology NAS</small></figcaption></figure>
<p>I add <code>init=/bin/bash</code> to the end of the line that starts with <code>linux</code>. This parameter tells the kernel to execute <code>/bin/bash</code> as the initial process instead of the default init system. This should allow me to bypass the usual user authentication. But it doesn&rsquo;t work. I still see the Synology login prompt. I try a few other values for <code>init</code>, but without success.</p>
<p>It seems that something is either ignoring or overriding this parameter. I notice <code>initrd /rd.gz</code> in the GRUB bootloader, which could be the cause. But when I remove it, the system fails to boot. It turns out that <code>/rd.gz</code> is a compressed initial RAM disk image containing a minimal root filesystem and essential drivers needed to boot the system. GRUB loads this image into memory for the kernel&rsquo;s use during the early stages of the boot process. So, this leaves me with limited options.</p>
<p>How come I haven&rsquo;t checked the logs yet?</p>
<p>I go through the boot logs and find something strange. At the end of the boot process, only the loopback network interface is up. All the Ethernet interfaces are down. This explains why the NAS doesn&rsquo;t send DHCP requests when connected to the router and is not visible on the local network.</p>
<p>I return to the GRUB and enable more detailed logging via <code>debug loglevel=7 ignore_loglevel</code> parameters. This provides more information but nothing directly relevant to the issue. I see that the network module is loaded, but no Ethernet interfaces are initialized:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">:: Loading module r8168 ... [  OK  ]
</span></span><span class="line"><span class="cl">... [  OK  ]
</span></span><span class="line"><span class="cl">lo        Link encap:Local Loopback
</span></span><span class="line"><span class="cl">          inet addr:127.0.0.1  Mask:255.0.0.0
</span></span><span class="line"><span class="cl">          UP LOOPBACK RUNNING  MTU:65536  Metric:1
</span></span><span class="line"><span class="cl">          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
</span></span><span class="line"><span class="cl">          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
</span></span><span class="line"><span class="cl">          collisions:0 txqueuelen:1
</span></span><span class="line"><span class="cl">          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)</span></span></code></pre></div></div>
</div>
<p>I wonder if there&rsquo;s a way to force the system to bring up the network interfaces. The <code>ip=::::::dhcp</code> parameter tells the kernel to use DHCP for all network interfaces. I update the GRUB configuration, press <code>Ctrl-x</code> to continue booting, and guess what, the Ethernet interfaces come up. The NAS successfully gets a new IP address from the router:</p>
<div class="code-block highlight-collapsed" data-frame="terminal" data-collapsible data-lines="44">
        <div class="code-header">
                <span class="code-filename">boot.log</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">:: Loading module r8168<span class="o">[</span>   12.555232<span class="o">]</span> r8168 Gigabit Ethernet driver 8.045.12-NAPI loaded
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">udhcpc: started, v1.30.1
</span></span><span class="line"><span class="cl">eth0      Link encap:Ethernet  HWaddr 90:09:D0:56:D7:2E  
</span></span><span class="line"><span class="cl">          inet addr:169.254.183.221  Bcast:169.254.255.255  Mask:255.255.0.0
</span></span><span class="line"><span class="cl">          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
</span></span><span class="line"><span class="cl">          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
</span></span><span class="line"><span class="cl">          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
</span></span><span class="line"><span class="cl">          collisions:0 txqueuelen:1000 
</span></span><span class="line"><span class="cl">          RX bytes:0 <span class="o">(</span>0.0 B<span class="o">)</span>  TX bytes:642 <span class="o">(</span>642.0 B<span class="o">)</span>
</span></span><span class="line"><span class="cl">          Interrupt:77 Base address:0x8000 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">eth1      Link encap:Ethernet  HWaddr 90:09:D0:56:D7:11  
</span></span><span class="line"><span class="cl">          UP BROADCAST MULTICAST  MTU:1500  Metric:1
</span></span><span class="line"><span class="cl">          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
</span></span><span class="line"><span class="cl">          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
</span></span><span class="line"><span class="cl">          collisions:0 txqueuelen:1000 
</span></span><span class="line"><span class="cl">          RX bytes:0 <span class="o">(</span>0.0 B<span class="o">)</span>  TX bytes:0 <span class="o">(</span>0.0 B<span class="o">)</span>
</span></span><span class="line"><span class="cl">          Interrupt:76 Base address:0xe000 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">eth2      Link encap:Ethernet  HWaddr 90:09:D0:56:D7:32  
</span></span><span class="line"><span class="cl">          UP BROADCAST MULTICAST  MTU:1500  Metric:1
</span></span><span class="line"><span class="cl">          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
</span></span><span class="line"><span class="cl">          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
</span></span><span class="line"><span class="cl">          collisions:0 txqueuelen:1000 
</span></span><span class="line"><span class="cl">          RX bytes:0 <span class="o">(</span>0.0 B<span class="o">)</span>  TX bytes:0 <span class="o">(</span>0.0 B<span class="o">)</span>
</span></span><span class="line"><span class="cl">          Interrupt:75 Base address:0xa000 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">eth3      Link encap:Ethernet  HWaddr 90:09:D0:56:D7:44  
</span></span><span class="line"><span class="cl">          UP BROADCAST MULTICAST  MTU:1500  Metric:1
</span></span><span class="line"><span class="cl">          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
</span></span><span class="line"><span class="cl">          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
</span></span><span class="line"><span class="cl">          collisions:0 txqueuelen:1000 
</span></span><span class="line"><span class="cl">          RX bytes:0 <span class="o">(</span>0.0 B<span class="o">)</span>  TX bytes:0 <span class="o">(</span>0.0 B<span class="o">)</span>
</span></span><span class="line"><span class="cl">          Interrupt:78 Base address:0xa000 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">lo        Link encap:Local Loopback  
</span></span><span class="line"><span class="cl">          inet addr:127.0.0.1  Mask:255.0.0.0
</span></span><span class="line"><span class="cl">          UP LOOPBACK RUNNING  MTU:65536  Metric:1
</span></span><span class="line"><span class="cl">          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
</span></span><span class="line"><span class="cl">          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
</span></span><span class="line"><span class="cl">          collisions:0 txqueuelen:1 
</span></span><span class="line"><span class="cl">          RX bytes:0 <span class="o">(</span>0.0 B<span class="o">)</span>  TX bytes:0 <span class="o">(</span>0.0 B<span class="o">)</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="44">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 44 lines</span>
    </div>
</div>
<p>I open the Synology Web Assistant, and it finds the NAS. It prompts me to reinstall the DSM, saying that I need to insert the hard drives.</p>
<h2 id="uncovering-the-root-cause-pcie-initialization-behavior">
Uncovering the root cause: PCIe initialization behavior
<a href="#uncovering-the-root-cause-pcie-initialization-behavior" class="heading-anchor" aria-label="Anchor link for: Uncovering the root cause: PCIe initialization behavior">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>I put the hard drives back, start the NAS, and have the same issue. The NAS is no longer available on the local network, even when I use the <code>ip=::::::dhcp</code> kernel parameter. I remove the hard drives and use the same configuration that led to the previously successful run. But the issue persists.</p>
<p>Luckily, I saved the log from the successful run. I put the hard drives back and get a new log from the failed run. I compare both logs and notice a minor difference in the network module loading:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># Successful run
</span></span><span class="line"><span class="cl">:: Loading module r8168[ 12.559868] r8168 Gigabit Ethernet driver 8.045.12-NAPI loaded
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Failed run
</span></span><span class="line"><span class="cl">:: Loading module r8168 ... [  OK  ]</span></span></code></pre></div></div>
</div>
<p>The failed run uses the default <code>r8168</code> network driver, while the successful run loads the <code>r8168 NAPI</code> Ethernet driver, an improved version of the default one. I have no idea why it happens. I try to force the use of the NAPI driver by setting the <code>r8168.use_napi=1</code> kernel parameter, but it has no effect.</p>
<p>I continue comparing the logs and notice a difference in PCI bridge initialization early in the boot process. During the successful run, the network interfaces are detected through PCIe enumeration as follows:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[    1.089159] pci 0000:00:01.2: PCI bridge to [bus 01]
</span></span><span class="line"><span class="cl">[    1.096159] pci 0000:00:01.3: PCI bridge to [bus 02]
</span></span><span class="line"><span class="cl">[    1.103160] pci 0000:00:01.4: PCI bridge to [bus 03-08]
</span></span><span class="line"><span class="cl">[    1.108880] pci 0000:03:00.0: PCI bridge to [bus 04-08]
</span></span><span class="line"><span class="cl">[    1.116168] pci 0000:04:00.0: PCI bridge to [bus 05]
</span></span><span class="line"><span class="cl">[    1.123169] pci 0000:04:02.0: PCI bridge to [bus 06]
</span></span><span class="line"><span class="cl">[    1.130170] pci 0000:04:06.0: PCI bridge to [bus 07]
</span></span><span class="line"><span class="cl">[    1.137171] pci 0000:04:0e.0: PCI bridge to [bus 08]
</span></span><span class="line"><span class="cl">[    1.144167] pci 0000:00:01.5: PCI bridge to [bus 09]
</span></span><span class="line"><span class="cl">[    1.155454] pci 0000:00:08.1: PCI bridge to [bus 0a]
</span></span><span class="line"><span class="cl">[    1.160473] pci 0000:00:08.2: PCI bridge to [bus 0b]</span></span></code></pre></div></div>
</div>
<p>There is a 
<a href="https://en.wikipedia.org/wiki/Root_complex" target="_blank" rel="nofollow noopener">PCIe switch</a>
 at <code>0000:03:00.0</code>, which serves as a root for the 4 LAN ports connected beneath it:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0000:00:01.4 (Root Bridge)
</span></span><span class="line"><span class="cl">    └── 0000:03:00.0 (PCIe Switch)
</span></span><span class="line"><span class="cl">        ├── 0000:04:00.0 (LAN Port)
</span></span><span class="line"><span class="cl">        ├── 0000:04:02.0 (LAN Port)
</span></span><span class="line"><span class="cl">        ├── 0000:04:06.0 (LAN Port)
</span></span><span class="line"><span class="cl">        └── 0000:04:0e.0 (LAN Port)</span></span></code></pre></div></div>
</div>
<p>However, in the failed run, the PCIe switch is missing:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[    1.086587] pci 0000:00:01.2: PCI bridge to [bus 01]
</span></span><span class="line"><span class="cl">[    1.093587] pci 0000:00:01.3: PCI bridge to [bus 02]
</span></span><span class="line"><span class="cl">[    1.098421] pci 0000:00:01.4: PCI bridge to [bus 03]
</span></span><span class="line"><span class="cl">[    1.105589] pci 0000:00:01.5: PCI bridge to [bus 04]
</span></span><span class="line"><span class="cl">[    1.116882] pci 0000:00:08.1: PCI bridge to [bus 05]
</span></span><span class="line"><span class="cl">[    1.121898] pci 0000:00:08.2: PCI bridge to [bus 06]</span></span></code></pre></div></div>
</div>
<p>When the switch fails to enumerate properly, all the network ports behind it become inaccessible. This explains why the default network module is loaded and no Ethernet interfaces are brought up.</p>
<p>There could be few reasons for PCIe switch initialization issues, such as Synology firmware problems, incorrect BIOS settings, or even power supply instability. I check the DSM release notes and see that an update was released about a week before the issue began. This leads me to think about the system as the following layers:</p>
<ul>
<li><strong>Layer 1:</strong> DSM (Operating System &amp; Settings)</li>
<li><strong>Layer 2:</strong> Firmware (Controls hardware initialization)</li>
<li><strong>Layer 3:</strong> Hardware (Physical components)</li>
</ul>
<p>When I reset the NAS, it only affected Layer 1 (DSM), while the recent update could have modified Layer 2 (Firmware). Since firmware settings are stored in non-volatile memory, they persist through normal system resets until explicitly changed by another firmware update. This explains why the PCIe switch initialization issues continued even after resetting the system.</p>
<p>I restart the NAS few more times. During the last restart, the network PCIe bridges initialize correctly. This could be due to a timing issue or the proper initialization sequence. But I take advantage of this random event to run the Synology Web Assistant, discover the NAS, and install the DSM.</p>
<h2 id="reinstalling-dsm-and-fixing-network-connectivity">
Reinstalling DSM and fixing network connectivity
<a href="#reinstalling-dsm-and-fixing-network-connectivity" class="heading-anchor" aria-label="Anchor link for: Reinstalling DSM and fixing network connectivity">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>After reinstalling DSM, the NAS works fine. I check the logs during the installation and see this:</p>
<div class="code-block highlight-collapsed" data-frame="terminal" data-collapsible data-lines="35">
        <div class="code-header">
                <span class="code-filename">upgrade.log</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">------------upgrade
</span></span><span class="line"><span class="cl">Begin upgrade procedure
</span></span><span class="line"><span class="cl">Found an upgrade file on data volume. Begin upgrade
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">Found /tmpData/SynoUpgrade.tar.gz. Hide it.
</span></span><span class="line"><span class="cl"><span class="s1">&#39;/tmpData/SynoUpgrade.tar.gz&#39;</span> -&gt; <span class="s1">&#39;/tmpData/.SynoUpgrade.tar.gz&#39;</span>
</span></span><span class="line"><span class="cl">Found /tmpData/SynoUpgradeIndexdb.txz. Hide it.
</span></span><span class="line"><span class="cl"><span class="s1">&#39;/tmpData/SynoUpgradeIndexdb.txz&#39;</span> -&gt; <span class="s1">&#39;/tmpData/.SynoUpgradeIndexdb.txz&#39;</span>
</span></span><span class="line"><span class="cl">Found /tmpData/SynoUpgradeSynohdpackImg.txz. Hide it.
</span></span><span class="line"><span class="cl"><span class="s1">&#39;/tmpData/SynoUpgradeSynohdpackImg.txz&#39;</span> -&gt; <span class="s1">&#39;/tmpData/.SynoUpgradeSynohdpackImg.txz&#39;</span>
</span></span><span class="line"><span class="cl">Do <span class="o">[</span>/bin/tar xf /tmpData/.SynoUpgrade.tar.gz -C /tmpRoot<span class="o">]</span>... <span class="nv">try</span><span class="o">=</span>1/2
</span></span><span class="line"><span class="cl">Success to untar main tarball
</span></span><span class="line"><span class="cl">Deploying default configurations
</span></span><span class="line"><span class="cl">Untaring .SynoUpgradeIndexdb.txz
</span></span><span class="line"><span class="cl">Untaring .SynoUpgradeSynohdpackImg.txz
</span></span><span class="line"><span class="cl">DataMnt and RootMnt are same devices. No need to move packages
</span></span><span class="line"><span class="cl">Touching /tmpRoot/var/.UpgradeBootup
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl"><span class="o">[</span>   78.624775<span class="o">]</span> synobios open /dev/ttyS1 success
</span></span><span class="line"><span class="cl"><span class="o">[</span>   78.638434<span class="o">]</span> correction with 0x00
</span></span><span class="line"><span class="cl"><span class="o">[</span>   78.648209<span class="o">]</span> synobios: load, major number <span class="m">201</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>   78.652478<span class="o">]</span> Brand: Synology
</span></span><span class="line"><span class="cl"><span class="o">[</span>   78.655273<span class="o">]</span> Model: DS-1522+
</span></span><span class="line"><span class="cl"><span class="o">[</span>   78.658066<span class="o">]</span> This is default settings: <span class="nb">set</span> group disks wakeup number to 1, spinup <span class="nb">time</span> deno <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>   78.666517<span class="o">]</span> synobios cpu_arch proc entry initialized
</span></span><span class="line"><span class="cl"><span class="o">[</span>   78.671478<span class="o">]</span> synobios crypto_hw proc entry initialized
</span></span><span class="line"><span class="cl"><span class="o">[</span>   78.676524<span class="o">]</span> synobios syno_platform proc entry initialized
</span></span><span class="line"><span class="cl">mknod: /dev/synobios: File exists
</span></span><span class="line"><span class="cl">Starting /usr/syno/bin/synohdcfgen...
</span></span><span class="line"><span class="cl">/usr/syno/bin/synohdcfgen returns <span class="m">0</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>   78.685588<span class="o">]</span> Module <span class="o">[</span>r1000_synobios<span class="o">]</span> is removed. 
</span></span><span class="line"><span class="cl"><span class="o">[</span>   78.696717<span class="o">]</span> synobios: unload
</span></span><span class="line"><span class="cl">Release upgrade preserved space temporarily in upgrade bootup.
</span></span><span class="line"><span class="cl">End upgrade <span class="nv">procedure</span>
</span></span><span class="line"><span class="cl"><span class="o">============</span>upgrade</span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="35">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 35 lines</span>
    </div>
</div>
<p>I restart the NAS few times. No issues. The update process extracts and applies firmware-related components, potentially overwriting any corrupted firmware settings with the correct ones. This could explain the resolution of the PCIe initialization problem.</p>
<p>I configure the NAS to use a static IP from a reserved range outside the DHCP pool. I do this to ensure consistent, reliable access to my storage and services regardless of network changes or reboots. Everything works as expected.</p>
<h2 id="reflections-and-takeaways">
Reflections and takeaways
<a href="#reflections-and-takeaways" class="heading-anchor" aria-label="Anchor link for: Reflections and takeaways">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p><strong>Low-Level Access is a Game-Changer</strong></p>
<p>Accessing the serial console port showed me exactly what was going on during boot, turning the NAS from a black box into an open book. It let me see bootloader logs and kernel initialization, which helped me figure out why the network interfaces weren&rsquo;t coming up. I learned that low-level access is essential when standard troubleshooting methods fall short.</p>
<p><strong>Systematic Troubleshooting is Key</strong></p>
<p>I started with the basics. Checked cables, ports, and DHCP settings before diving deeper. Isolating variables one by one, such as using direct connections and minimal network setups, helped me narrow down the cause. Documenting each step allowed me to save the log from a successful run, which played a key role in finding the core issue. This showed me the power of a structured and systematic approach.</p>
<p><strong>Persistence and Curiosity Lead to Breakthroughs</strong></p>
<p>I tested countless ideas, compared logs line by line, and kept digging even when nothing made sense. When I first found the UART port, I didn&rsquo;t even know how use it. But I kept learning and experimenting until I uncovered the root cause. This reminded me that breakthroughs often result from persistence, curiosity, and a willingness to push beyond your current skill level.</p>
<h2 id="final-thoughts-and-looking-ahead">
Final thoughts and looking ahead
<a href="#final-thoughts-and-looking-ahead" class="heading-anchor" aria-label="Anchor link for: Final thoughts and looking ahead">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>I still don&rsquo;t know exactly what happened that morning when the NAS disappeared from my local network. I lost all system logs when I did the hard reset and only discovered the serial console port afterward. Based on the timing, I can only speculate that the latest Synology update corrupted the system configuration.</p>
<p>But I learned a great deal during this investigation, and I added a powerful tool under my belt—the UART debug port. If I&rsquo;m lucky (or unlucky) and the issue occurs again, I won&rsquo;t need to reset anything. I&rsquo;ll just unwrap the USB-TTL adapter, connect my MacBook to the NAS, log in with my admin password, and check the system logs to get the answers I couldn&rsquo;t get this time.</p>
<p>Happy debugging!</p>
]]></content:encoded></item><item><title>K-Means Image Compression in Rust</title><link>https://rdiachenko.com/posts/ml/k-means-image-compression/</link><pubDate>Mon, 02 Sep 2024 18:27:39 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/ml/k-means-image-compression/</guid><description>Using K-Means clustering for image compression in Rust. A visual and practical way to explore this classic machine learning algorithm.</description><content:encoded><![CDATA[<p>After learning about k-means 

            
        
            
        
    <a href="https://rdiachenko.com/posts/ml/machine-learning-concepts/#clustering-grouping-similar-items">clustering</a>
, I thought I understood it until I tried implementing it myself. That&rsquo;s when I realized just how many details I had missed and how incomplete my theoretical understanding really was.</p>
<p>Implementing the core algorithm alone didn&rsquo;t capture my interest. I wanted to see the algorithm&rsquo;s results in action. Although I&rsquo;ve spent over a decade building backend systems, there&rsquo;s something satisfying about visual feedback. This led me to image compression.</p>
<p>While k-means clustering can be used to reduce image sizes by averaging similar colors, I&rsquo;m aware there are more efficient algorithms for this purpose. However, for educational reasons, it&rsquo;s an excellent way to see k-means in action.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/k-means-image-compression/original-vs-k-means-compressed-image-16c-cover_hu_617369164d3b31b.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="473">
            <source
                    srcset="https://rdiachenko.com/posts/ml/k-means-image-compression/original-vs-k-means-compressed-image-16c-cover_hu_7de53cc99081000f.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="675">
            <img
                    src="https://rdiachenko.com/posts/ml/k-means-image-compression/original-vs-k-means-compressed-image-16c-cover_hu_7de53cc99081000f.webp"
                    alt="Original vs. K-Means Compressed Image Using 16 Colors and Greedy K-means&#43;&#43;"
                    width="1200"
                    height="675"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Original vs. K-Means Compressed Image Using 16 Colors and Greedy K-means++</small></figcaption></figure>
<p>Above is a comparison of an original image (left) and its compressed version (right), which uses only 16 colors and the Greedy K-means++ initialization method for k-means.</p>
<p>In the sections that follow, I&rsquo;ll dive deeper into other initialization methods, their implementation, and their comparative effectiveness. But first, let&rsquo;s explore the fundamental components of the k-means clustering.</p>
<h2 id="k-means-clustering-algorithm">
K-Means clustering algorithm
<a href="#k-means-clustering-algorithm" class="heading-anchor" aria-label="Anchor link for: K-Means clustering algorithm">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>K-means clustering consists of these 5 steps:</p>
<ol>
<li><strong>Initialization:</strong> Centroids are initialized using a chosen strategy. Common strategies include Forgy, MacQueen, Maximin, Bradley-Fayyad, and K-means++.</li>
<li><strong>Main Loop:</strong> The algorithm iterates until it converges or reaches the maximum number of iterations.</li>
<li><strong>Cluster Assignment:</strong> Each data point is assigned to its nearest centroid.</li>
<li><strong>Centroid Update:</strong> New centroids are calculated as the mean of the points assigned to each cluster.</li>
<li><strong>Convergence Check:</strong> The algorithm checks if the change in SSE (Sum of Squared Errors) is below a specified threshold.</li>
</ol>
<div class="code-block" data-frame="editor">
        <div class="code-header">
                <span class="code-filename">pseudocode</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">function k_means_clustering(data, k, max_iterations, epsilon):
</span></span><span class="line"><span class="cl">    // Initialize centroids using chosen strategy (e.g., k-means++)
</span></span><span class="line"><span class="cl">    centroids = initialize_centroids(data, k)
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    for iteration = 1 to max_iterations:
</span></span><span class="line"><span class="cl">        // Assign each point to the nearest centroid
</span></span><span class="line"><span class="cl">        clusters = assign_clusters(data, centroids)
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        // Recompute centroids as the mean of assigned points
</span></span><span class="line"><span class="cl">        centroids = compute_centroids(data, clusters, k)
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        // Calculate the sum of squared errors (SSE)
</span></span><span class="line"><span class="cl">        old_sse = sse
</span></span><span class="line"><span class="cl">        sse = calculate_sse(data, clusters, centroids)
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        // Check for convergence
</span></span><span class="line"><span class="cl">        if (old_sse - sse) / sse &lt; epsilon:
</span></span><span class="line"><span class="cl">            break
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    return centroids, clusters, sse, iteration</span></span></code></pre></div></div>
</div>
<p>The pseudocode above captures the iterative process of refining cluster assignments and updating centroids to minimize the SSE, thus optimizing the grouping of data points.</p>
<h3 id="choosing-initial-centroids">
Choosing initial centroids
<a href="#choosing-initial-centroids" class="heading-anchor" aria-label="Anchor link for: Choosing initial centroids">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Selecting initial centroids is a crucial step in the k-means algorithm because a good choice can lead to faster convergence and more effective clustering.</p>
<p>There has been significant research in this area, resulting in 
<a href="https://arxiv.org/abs/1209.1960" target="_blank" rel="nofollow noopener">various methods for initialization</a>
. Here are some of the methods I explored for image compression, each characterized by its non-deterministic nature, meaning results may vary with each run due to their probabilistic approaches:</p>
<ul>
<li><strong>Forgy:</strong> Randomly assigns each data point to one of the <code>k</code> clusters, using these assignments to compute initial centroids.</li>
<li><strong>MacQueen:</strong> Selects <code>k</code> unique data points randomly as initial centroids.</li>
<li><strong>Maximin:</strong> Iteratively selects the points that are farthest from existing centroids to ensure diversity.</li>
<li><strong>Bradley-Fayyad:</strong> Runs k-means on subsets of the data to identify effective initial centroids.</li>
<li><strong>K-means++:</strong> Chooses initial centroids with a probability proportional to their squared distance from the nearest existing centroid, enhancing cluster quality.</li>
<li><strong>Greedy K-means++:</strong> An improved version of K-means++ that evaluates multiple candidates at each step to optimize the selection.</li>
</ul>
<p>Let&rsquo;s wrap up the main k-means loop before we dive into these initialization methods in detail.</p>
<h3 id="assigning-points-to-clusters">
Assigning points to clusters
<a href="#assigning-points-to-clusters" class="heading-anchor" aria-label="Anchor link for: Assigning points to clusters">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>During each iteration of the k-means loop, the algorithm calculates the squared Euclidean distance between each point and every centroid. Each point is then assigned to the centroid it is closest to.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">squared_euclidean_dist</span><span class="p">(</span><span class="n">a</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="kt">f64</span><span class="p">],</span><span class="w"> </span><span class="n">b</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="kt">f64</span><span class="p">])</span><span class="w"> </span>-&gt; <span class="kt">f64</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">a</span><span class="p">.</span><span class="n">iter</span><span class="p">().</span><span class="n">zip</span><span class="p">(</span><span class="n">b</span><span class="p">.</span><span class="n">iter</span><span class="p">()).</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="p">(</span><span class="n">x</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">y</span><span class="p">).</span><span class="n">powi</span><span class="p">(</span><span class="mi">2</span><span class="p">)).</span><span class="n">sum</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">compute_clusters</span><span class="p">(</span><span class="n">data</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">],</span><span class="w"> </span><span class="n">centroids</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">])</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">usize</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">result</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">usize</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">()];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">point</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">min_dist</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">f64</span>::<span class="no">MAX</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">idx</span><span class="p">,</span><span class="w"> </span><span class="n">centroid</span><span class="p">)</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">centroids</span><span class="p">.</span><span class="n">iter</span><span class="p">().</span><span class="n">enumerate</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="n">dist</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">squared_euclidean_dist</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="p">[</span><span class="n">point</span><span class="p">],</span><span class="w"> </span><span class="n">centroid</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="n">dist</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">min_dist</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">min_dist</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">dist</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">result</span><span class="p">[</span><span class="n">point</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">idx</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">result</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<h3 id="updating-centroids">
Updating centroids
<a href="#updating-centroids" class="heading-anchor" aria-label="Anchor link for: Updating centroids">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>After assigning points to clusters, the centroids are recalculated. This is achieved by computing the average of all points within each cluster.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">compute_centroids</span><span class="p">(</span><span class="n">data</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">],</span><span class="w"> </span><span class="n">clusters</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="kt">usize</span><span class="p">],</span><span class="w"> </span><span class="n">k</span>: <span class="kt">usize</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">feature_len</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">data</span><span class="p">.</span><span class="n">first</span><span class="p">().</span><span class="n">unwrap_or</span><span class="p">(</span><span class="o">&amp;</span><span class="fm">vec!</span><span class="p">[]).</span><span class="n">len</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">cluster</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">k</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">centroid</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="mi">0</span><span class="k">f64</span><span class="p">;</span><span class="w"> </span><span class="n">feature_len</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">cnt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">point</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="n">clusters</span><span class="p">[</span><span class="n">point</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">cluster</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">idx</span><span class="p">,</span><span class="w"> </span><span class="n">feature</span><span class="p">)</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">centroid</span><span class="p">.</span><span class="n">iter_mut</span><span class="p">().</span><span class="n">enumerate</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="o">*</span><span class="n">feature</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">data</span><span class="p">[</span><span class="n">point</span><span class="p">][</span><span class="n">idx</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">cnt</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="n">cnt</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"> </span><span class="n">feature</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">centroid</span><span class="p">.</span><span class="n">iter_mut</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="o">*</span><span class="n">feature</span><span class="w"> </span><span class="o">/=</span><span class="w"> </span><span class="n">cnt</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">f64</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">result</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">centroid</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">result</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<h3 id="measuring-progress-with-sse">
Measuring progress with SSE
<a href="#measuring-progress-with-sse" class="heading-anchor" aria-label="Anchor link for: Measuring progress with SSE">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The algorithm measures its progress using the Sum of Squared Errors (SSE), which it aims to minimize. This sum is the total of the minimum squared distances between points and their respective centroids.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">compute_sse</span><span class="p">(</span><span class="n">data</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">],</span><span class="w"> </span><span class="n">clusters</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="kt">usize</span><span class="p">],</span><span class="w"> </span><span class="n">centroids</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>-&gt; <span class="kt">f64</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">clusters</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">zip</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">iter</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">cluster</span><span class="p">,</span><span class="w"> </span><span class="n">point</span><span class="p">)</span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">squared_euclidean_dist</span><span class="p">(</span><span class="n">point</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">centroids</span><span class="p">[</span><span class="o">*</span><span class="n">cluster</span><span class="p">]))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">sum</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Convergence is reached when changes in SSE are zero or so small that further iterations do not significantly improve the clusters. This condition indicates that the centroids have stabilized, and the clusters are optimally formed.</p>
<p>Setting a maximum number of iterations prevents the algorithm from running too long, especially when changes in SSE are negligible to make a significant difference.</p>
<h3 id="putting-it-all-together">
Putting it all together
<a href="#putting-it-all-together" class="heading-anchor" aria-label="Anchor link for: Putting it all together">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Here is how the main k-means loop is implemented in Rust:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">run_k_means</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">data</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">],</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">initial_centroids</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">],</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">max_iters</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">eps</span>: <span class="kt">f64</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span>-&gt; <span class="p">(</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;&gt;</span><span class="p">,</span><span class="w"> </span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">usize</span><span class="o">&gt;</span><span class="p">,</span><span class="w"> </span><span class="kt">f64</span><span class="p">,</span><span class="w"> </span><span class="kt">usize</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">initial_centroids</span><span class="p">.</span><span class="n">len</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">centroids</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">initial_centroids</span><span class="p">.</span><span class="n">to_vec</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">clusters</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">()];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">sse</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">f64</span>::<span class="no">MAX</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">iters</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">_i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">max_iters</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">clusters</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">compute_clusters</span><span class="p">(</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">centroids</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">centroids</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">compute_centroids</span><span class="p">(</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">clusters</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">prev_sse</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sse</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">sse</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">compute_sse</span><span class="p">(</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">clusters</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">centroids</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">iters</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">prev_sse</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">sse</span><span class="p">)</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">sse</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">eps</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">break</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">(</span><span class="n">centroids</span><span class="p">,</span><span class="w"> </span><span class="n">clusters</span><span class="p">,</span><span class="w"> </span><span class="n">sse</span><span class="p">,</span><span class="w"> </span><span class="n">iters</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Now, let&rsquo;s revisit the various initialization methods mentioned earlier.</p>
<h2 id="k-means-initialization-methods">
K-Means initialization methods
<a href="#k-means-initialization-methods" class="heading-anchor" aria-label="Anchor link for: K-Means initialization methods">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>There&rsquo;s a lot of confusion around k-means initialization methods in the literature and practice. For instance, some sources refer to K-means++ when they&rsquo;re actually implementing the Maximin method. Others incorrectly describe MacQueen&rsquo;s method, which uses random initial centers, as Forgy&rsquo;s.</p>
<p>Let&rsquo;s clear up these misconceptions and explore how each method functions and how to implement them.</p>
<h3 id="forgy-random-assignment">
Forgy: random assignment
<a href="#forgy-random-assignment" class="heading-anchor" aria-label="Anchor link for: Forgy: random assignment">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The Forgy method randomly assigns each data point to one of <code>k</code> clusters. It then calculates the centroids of these initial clusters.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">init_centroids</span><span class="p">(</span><span class="n">data</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">],</span><span class="w"> </span><span class="n">k</span>: <span class="kt">usize</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">range</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Uniform</span>::<span class="n">from</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="n">k</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">rng</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rand</span>::<span class="n">thread_rng</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">clusters</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">usize</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">data</span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">_</span><span class="o">|</span><span class="w"> </span><span class="n">range</span><span class="p">.</span><span class="n">sample</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">rng</span><span class="p">)).</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">compute_centroids</span><span class="p">(</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">clusters</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<h3 id="macqueen-random-sampling">
MacQueen: random sampling
<a href="#macqueen-random-sampling" class="heading-anchor" aria-label="Anchor link for: MacQueen: random sampling">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The MacQueen method, also known as &ldquo;Random Sampling&rdquo;, selects <code>k</code> random data points from the dataset to serve as initial centroids.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">init_centroids</span><span class="p">(</span><span class="n">data</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">],</span><span class="w"> </span><span class="n">k</span>: <span class="kt">usize</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">rng</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rand</span>::<span class="n">thread_rng</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">data</span><span class="p">.</span><span class="n">choose_multiple</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">rng</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="p">).</span><span class="n">cloned</span><span class="p">().</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>While this method is quick and straightforward, it can sometimes result in less optimal initial centroids due to random chance.</p>
<h3 id="maximin-spreading-centroids-apart">
Maximin: spreading centroids apart
<a href="#maximin-spreading-centroids-apart" class="heading-anchor" aria-label="Anchor link for: Maximin: spreading centroids apart">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The Maximin method aims to spread out the initial centroids by iteratively selecting points that are farthest from the already chosen centroids. This approach tends to provide a good spread of initial centroids across the dataset.</p>
<p><strong>Algorithm Steps:</strong></p>
<ol>
<li>Randomly select the first centroid from the dataset.</li>
<li>For each subsequent centroid:
<ol>
<li>For each data point not yet chosen as a centroid, find its distance to the nearest existing centroid.</li>
<li>Choose the data point with the maximum distance as the next centroid.</li>
</ol>
</li>
<li>Repeat step 2 until all <code>k</code> centroids are selected.</li>
</ol>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="44">
        <div class="code-header">
                <span class="code-filename">maximin.rs</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">init_centroids</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">data</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">],</span><span class="w"> </span><span class="n">k</span>: <span class="kt">usize</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">centroids</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">HashSet</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">rng</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rand</span>::<span class="n">thread_rng</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Choose the first centroid randomly
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">centroids</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">rng</span><span class="p">.</span><span class="n">gen_range</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">()));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Choose remaining k-1 centroids
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">while</span><span class="w"> </span><span class="n">centroids</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">max_dist</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">f64</span>::<span class="no">MIN</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">centroid</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="k">usize</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Find the point with maximum distance from existing centroids
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="n">centroids</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">min_dist</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">f64</span>::<span class="no">MAX</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">min_dist_point</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">p</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// Find the nearest centroid to this point
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">for</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&amp;</span><span class="n">centroids</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="kd">let</span><span class="w"> </span><span class="n">dist</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">squared_euclidean_dist</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="p">[</span><span class="n">p</span><span class="p">],</span><span class="w"> </span><span class="o">&amp;</span><span class="n">data</span><span class="p">[</span><span class="o">*</span><span class="n">c</span><span class="p">]);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="k">if</span><span class="w"> </span><span class="n">dist</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">min_dist</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                        </span><span class="n">min_dist</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">dist</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                        </span><span class="n">min_dist_point</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">p</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// Update the max distance if this point is farther
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">if</span><span class="w"> </span><span class="n">min_dist</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">max_dist</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">max_dist</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">min_dist</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">centroid</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">min_dist_point</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Add the point with max distance as the next centroid
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">centroids</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">centroid</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Convert centroid indices to actual data points
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">centroids</span><span class="p">.</span><span class="n">iter</span><span class="p">().</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">c</span><span class="o">|</span><span class="w"> </span><span class="n">data</span><span class="p">[</span><span class="o">*</span><span class="n">c</span><span class="p">].</span><span class="n">clone</span><span class="p">()).</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="44">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 44 lines</span>
    </div>
</div>
<p>While this method generally provides a good spread of initial centroids, it can be sensitive to outliers in the dataset. In datasets with significant outliers, this method might choose some of those outliers as initial centroids, potentially skewing the clustering results.</p>
<h3 id="bradley-fayyad-refined-start">
Bradley-Fayyad: refined start
<a href="#bradley-fayyad-refined-start" class="heading-anchor" aria-label="Anchor link for: Bradley-Fayyad: refined start">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The Bradley-Fayyad method, also known as the &ldquo;Refined Start&rdquo; algorithm, aims to find a good initial set of centroids by running k-means on multiple subsets of the data and then finding the best set of centroids among these results.</p>
<p><strong>Algorithm Steps:</strong></p>
<ol>
<li>Randomly partition the data into <code>j</code> subsets.</li>
<li>Run k-means (using MacQueen&rsquo;s method for initialization) on each subset to get <code>j</code> sets of <code>k</code> centroids.</li>
<li>Combine all these centroids into a superset.</li>
<li>Run k-means <code>j</code> times on this superset, each time initialized with a different set of centroids from step 2.</li>
<li>Return the set of centroids that resulted in the lowest Sum of Squared Errors (SSE).</li>
</ol>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="53">
        <div class="code-header">
                <span class="code-filename">bradley_fayyad.rs</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">init_centroids</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">data</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">],</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">k</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">j</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">max_iters</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">eps</span>: <span class="kt">f64</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Randomly partition data into j subsets
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">rng</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rand</span>::<span class="n">thread_rng</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">points</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">usize</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">()).</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">points</span><span class="p">.</span><span class="n">shuffle</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">rng</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">partitions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w"> </span><span class="n">j</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">partition_size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">j</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">partition_index</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="k">usize</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">points</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">feature</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">data</span><span class="p">[</span><span class="n">p</span><span class="p">].</span><span class="n">clone</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">partitions</span><span class="p">[</span><span class="n">partition_index</span><span class="p">].</span><span class="n">push</span><span class="p">(</span><span class="n">feature</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="n">partitions</span><span class="p">[</span><span class="n">partition_index</span><span class="p">].</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">partition_size</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">partition_index</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Cluster each j subset using k-means with MacQueen,
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// giving j sets of intermediate centers each with k points
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">centroids_per_partition</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">all_centroids</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">partition</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">partitions</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">initial_centroids</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">macqueen</span>::<span class="n">init_centroids</span><span class="p">(</span><span class="o">&amp;</span><span class="n">partition</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="p">(</span><span class="n">centroids</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">run_k_means</span><span class="p">(</span><span class="o">&amp;</span><span class="n">partition</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">initial_centroids</span><span class="p">,</span><span class="w"> </span><span class="n">max_iters</span><span class="p">,</span><span class="w"> </span><span class="n">eps</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Combine all j center sets into a single superset
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">centroid</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&amp;</span><span class="n">centroids</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">all_centroids</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">centroid</span><span class="p">.</span><span class="n">clone</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">centroids_per_partition</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">centroids</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Cluster this superset using k-means j times,
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// each time initialized with a different center set j.
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Return the initialization subset j that gives the least SSE
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">min_sse</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">f64</span>::<span class="no">MAX</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">initial_centroids</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">centroids_per_partition</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="p">(</span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">sse</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">run_k_means</span><span class="p">(</span><span class="o">&amp;</span><span class="n">all_centroids</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">initial_centroids</span><span class="p">,</span><span class="w"> </span><span class="n">max_iters</span><span class="p">,</span><span class="w"> </span><span class="n">eps</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="n">sse</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">min_sse</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">min_sse</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sse</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">initial_centroids</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">result</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="53">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 53 lines</span>
    </div>
</div>
<h3 id="k-means-and-greedy-k-means-smarter-seeding">
K-Means++ and greedy K-Means++: smarter seeding
<a href="#k-means-and-greedy-k-means-smarter-seeding" class="heading-anchor" aria-label="Anchor link for: K-Means&#43;&#43; and greedy K-Means&#43;&#43;: smarter seeding">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>This method implements 
<a href="https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf" target="_blank" rel="nofollow noopener">the k-means++ algorithm</a>
 for initializing centroids, which aims to choose centroids that are both spread out and representative of the data distribution. It also includes a greedy approach to potentially improve the quality of the chosen centroids.</p>
<p><strong>Algorithm Steps:</strong></p>
<ol>
<li>Start by choosing the first centroid at random from the dataset.</li>
<li>For each subsequent centroid:
<ol>
<li>Calculate the distance from each point to its nearest existing centroid.</li>
<li>Choose <code>samples_per_iter</code> candidate points, with probability proportional to their squared distance.</li>
<li>For each candidate, compute the Sum of Squared Distances if it were chosen as the next centroid.</li>
<li>Select the candidate that minimizes this sum.</li>
</ol>
</li>
<li>Repeat step 2 until <code>k</code> centroids are chosen.</li>
</ol>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="91">
        <div class="code-header">
                <span class="code-filename">kmeanspp.rs</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">init_centroids</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">data</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">],</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">k</span>: <span class="kt">usize</span><span class="p">,</span><span class="w"> </span><span class="n">samples_per_iter</span>: <span class="kt">usize</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Determine the number of local centers to consider in each iteration
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">local_centers_n</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">samples_per_iter</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">samples_per_iter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">(</span><span class="mf">2.0</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="n">k</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">f64</span><span class="p">).</span><span class="n">log2</span><span class="p">()).</span><span class="n">floor</span><span class="p">()</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">usize</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">rng</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rand</span>::<span class="n">thread_rng</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Choose the first centroid randomly
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">initial_centroid</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rng</span><span class="p">.</span><span class="n">gen_range</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="n">data</span><span class="p">[</span><span class="n">initial_centroid</span><span class="p">].</span><span class="n">clone</span><span class="p">()];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Initialize distances and sum of squared errors
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">min_dists</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="kt">f64</span>::<span class="no">MAX</span><span class="p">;</span><span class="w"> </span><span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">()];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">sse</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="k">f64</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Calculate initial distances
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">min_dists</span><span class="p">[</span><span class="n">p</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">squared_euclidean_dist</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="p">[</span><span class="n">p</span><span class="p">],</span><span class="w"> </span><span class="o">&amp;</span><span class="n">data</span><span class="p">[</span><span class="n">initial_centroid</span><span class="p">]);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">sse</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">min_dists</span><span class="p">[</span><span class="n">p</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">range</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Uniform</span>::<span class="n">new</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span><span class="w"> </span><span class="mf">1.0</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Choose remaining k-1 centroids
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">1</span><span class="o">..</span><span class="n">k</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Calculate cumulative sum of distances
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">dists_cumsum</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">min_dists</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">scan</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span><span class="w"> </span><span class="o">|</span><span class="n">acc</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">dist</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="o">*</span><span class="n">acc</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">dist</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nb">Some</span><span class="p">(</span><span class="o">*</span><span class="n">acc</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Choose candidate centroids
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">centroid_candidates</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">usize</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="n">local_centers_n</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">_</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="kd">let</span><span class="w"> </span><span class="n">rand_val</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rng</span><span class="p">.</span><span class="n">sample</span><span class="p">(</span><span class="n">range</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">sse</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">dists_cumsum</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">.</span><span class="n">position</span><span class="p">(</span><span class="o">|&amp;</span><span class="n">dist</span><span class="o">|</span><span class="w"> </span><span class="n">dist</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">rand_val</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">.</span><span class="n">unwrap_or</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Calculate distances to candidate centroids
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">dist_to_candidates</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">candidate_index</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">centroid_candidates</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">dist_to_candidates</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">centroid_candidates</span><span class="p">[</span><span class="n">candidate_index</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="kd">let</span><span class="w"> </span><span class="n">dist</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">squared_euclidean_dist</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="p">[</span><span class="n">p</span><span class="p">],</span><span class="w"> </span><span class="o">&amp;</span><span class="n">data</span><span class="p">[</span><span class="n">c</span><span class="p">]);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">dist_to_candidates</span><span class="p">[</span><span class="n">candidate_index</span><span class="p">].</span><span class="n">push</span><span class="p">(</span><span class="n">dist</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Find the best candidate centroid
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">best_centroid_candidate</span>: <span class="kt">usize</span> <span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">best_sse</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">f64</span>::<span class="no">MAX</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">best_min_dists</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">candidate_index</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">centroid_candidates</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">new_min_dists</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">new_sse</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0.0</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">dist_index</span><span class="p">,</span><span class="w"> </span><span class="n">min_dist</span><span class="p">)</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">min_dists</span><span class="p">.</span><span class="n">iter</span><span class="p">().</span><span class="n">enumerate</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="kd">let</span><span class="w"> </span><span class="n">dist</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">f64</span>::<span class="n">min</span><span class="p">(</span><span class="o">*</span><span class="n">min_dist</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                                    </span><span class="n">dist_to_candidates</span><span class="p">[</span><span class="n">candidate_index</span><span class="p">][</span><span class="n">dist_index</span><span class="p">]);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">new_min_dists</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">dist</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">new_sse</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">dist</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="n">new_sse</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">best_sse</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">best_sse</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new_sse</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">best_min_dists</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new_min_dists</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">best_centroid_candidate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">centroid_candidates</span><span class="p">[</span><span class="n">candidate_index</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Add the best candidate to the result
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">result</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">best_centroid_candidate</span><span class="p">].</span><span class="n">clone</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">sse</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">best_sse</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">min_dists</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">best_min_dists</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">result</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="91">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 91 lines</span>
    </div>
</div>
<p>A potential issue with k-means++ is the small but real possibility of selecting two centroids that are very close to each other due to its probabilistic nature.</p>
<p>The solution, often referred to as Greedy K-means++, involves selecting <code>log(k)</code> candidates each round for centroid selection, then choosing the candidate that minimizes the SSE. This helps to prevent the unlikely but problematic scenario of closely located centers.</p>
<p>I used a single function for both methods by using the <code>samples_per_iter</code> parameter to control the number of candidate points per iteration. For K-means++, it&rsquo;s set to <code>1</code>. For Greedy K-means++, it&rsquo;s set to <code>0</code>, and the number of candidates is determined by the formula <code>floor(2 + log2(k))</code>.</p>
<h2 id="k-means-for-image-compression">
K-Means for image compression
<a href="#k-means-for-image-compression" class="heading-anchor" aria-label="Anchor link for: K-Means for image compression">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The k-means image compression algorithm consists of 5 steps.</p>
<h3 id="step-1-transforming-image-into-rgb-data">
Step 1: transforming image into RGB data
<a href="#step-1-transforming-image-into-rgb-data" class="heading-anchor" aria-label="Anchor link for: Step 1: transforming image into RGB data">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The input image is transformed into a list of RGB values, where each pixel is represented as a point in 3D space.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">transform</span><span class="p">(</span><span class="n">image</span>: <span class="kp">&amp;</span><span class="nc">DynamicImage</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">image</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">pixels</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">pixel</span><span class="o">|</span><span class="w"> </span><span class="n">pixel</span><span class="p">.</span><span class="mf">2.0</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">rgba</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">rgb</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"> </span><span class="n">val</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">rgba</span><span class="p">.</span><span class="n">iter</span><span class="p">().</span><span class="n">take</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">rgb</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">normalize</span><span class="p">(</span><span class="o">*</span><span class="n">val</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">rgb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<h3 id="step-2-normalizing-pixel-values">
Step 2: normalizing pixel values
<a href="#step-2-normalizing-pixel-values" class="heading-anchor" aria-label="Anchor link for: Step 2: normalizing pixel values">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Normalizing pixel values (typically to a range of <code>[0..1]</code>) ensures all color channels are on the same scale. This prevents any channel from dominating the clustering process due to larger numerical values.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">normalize</span><span class="p">(</span><span class="n">val</span>: <span class="kt">u8</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">f64</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">val</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">f64</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">255.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Additionally, many machine learning algorithms, including k-means, perform better with normalized data as it can lead to faster convergence.</p>
<h3 id="step-3-running-k-means-clustering">
Step 3: running K-Means clustering
<a href="#step-3-running-k-means-clustering" class="heading-anchor" aria-label="Anchor link for: Step 3: running K-Means clustering">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The k-means algorithm described in the previous section is applied as follows:</p>
<ol>
<li>Each pixel is assigned to the nearest centroid.</li>
<li>Centroids are recalculated based on the mean of all pixels assigned to them.</li>
<li>This process repeats until convergence or a maximum number of iterations is reached.</li>
</ol>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="p">(</span><span class="n">centroids</span><span class="p">,</span><span class="w"> </span><span class="n">clusters</span><span class="p">,</span><span class="w"> </span><span class="n">sse</span><span class="p">,</span><span class="w"> </span><span class="n">iters</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">k_means</span>::<span class="n">cluster</span><span class="p">(</span><span class="o">&amp;</span><span class="n">img_data</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">strategy</span><span class="p">);</span></span></span></code></pre></div></div>
</div>
<h3 id="step-4-denormalizing-colors">
Step 4: denormalizing colors
<a href="#step-4-denormalizing-colors" class="heading-anchor" aria-label="Anchor link for: Step 4: denormalizing colors">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>After clustering, we need to convert the compressed color values back to the original <code>[0..255]</code> range for standard image formats.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">denormalize</span><span class="p">(</span><span class="n">val</span>: <span class="kt">f64</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">u8</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">(</span><span class="n">val</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mf">255.0</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">u8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<h3 id="step-5-rebuilding-the-compressed-image">
Step 5: rebuilding the compressed image
<a href="#step-5-rebuilding-the-compressed-image" class="heading-anchor" aria-label="Anchor link for: Step 5: rebuilding the compressed image">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Each pixel in the image is replaced with the color of its assigned centroid. That leads to the compressed image using only <code>k</code> colors.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">compress</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">image</span>: <span class="kp">&amp;</span><span class="nc">DynamicImage</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">centroids</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">f64</span><span class="o">&gt;</span><span class="p">],</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">clusters</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="kt">usize</span><span class="p">],</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">image</span>::<span class="n">ImageBuffer</span><span class="o">&lt;</span><span class="n">Rgb</span><span class="o">&lt;</span><span class="kt">u8</span><span class="o">&gt;</span><span class="p">,</span><span class="w"> </span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">u8</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">image</span>::<span class="n">ImageBuffer</span>::<span class="n">new</span><span class="p">(</span><span class="n">image</span><span class="p">.</span><span class="n">width</span><span class="p">(),</span><span class="w"> </span><span class="n">image</span><span class="p">.</span><span class="n">height</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">j</span><span class="p">,</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">pixel</span><span class="p">)</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">result</span><span class="p">.</span><span class="n">enumerate_pixels_mut</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">point</span>: <span class="kt">usize</span> <span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">image</span><span class="p">.</span><span class="n">width</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">j</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">usize</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">centroid</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&amp;</span><span class="n">centroids</span><span class="p">[</span><span class="n">clusters</span><span class="p">[</span><span class="n">point</span><span class="p">]];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">r</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">denormalize</span><span class="p">(</span><span class="n">centroid</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">g</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">denormalize</span><span class="p">(</span><span class="n">centroid</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">denormalize</span><span class="p">(</span><span class="n">centroid</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">*</span><span class="n">pixel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Rgb</span><span class="p">([</span><span class="n">r</span><span class="p">,</span><span class="w"> </span><span class="n">g</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">]);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">result</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>The image compression algorithm reduces the image&rsquo;s color palette while trying to maintain overall visual similarity to the original image. Let&rsquo;s apply it to a few photos and check the results.</p>
<h2 id="running-the-program-and-observing-compressed-images">
Running the program and observing compressed images
<a href="#running-the-program-and-observing-compressed-images" class="heading-anchor" aria-label="Anchor link for: Running the program and observing compressed images">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>I created a binary crate that allows you to run the image compression with the following command:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cargo run --package k_means --release -- IMAGE K STRATEGY</span></span></code></pre></div></div>
</div>
<p><em>Where:</em></p>
<ul>
<li><code>IMAGE</code> is the path to your input image file.</li>
<li><code>K</code> is the number of colors to use in the compressed image.</li>
<li><code>STRATEGY</code> is a single letter code for the centroid initialization strategy:
<ul>
<li><code>f</code> Forgy</li>
<li><code>m</code> MacQueen</li>
<li><code>x</code> Maximin</li>
<li><code>b</code> Bradley-Fayyad</li>
<li><code>k</code> K-means++</li>
<li><code>g</code> Greedy K-means++</li>
</ul>
</li>
</ul>
<p><em>Example:</em></p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cargo run --package k_means --release -- /path/to/photo.png <span class="m">16</span> g</span></span></code></pre></div></div>
</div>
<p>I compressed three different images using each initialization method with <code>k=16</code>. Below are results and observations.</p>
<h3 id="compression-results-for-a-color-rich-photo">
Compression results for a color-rich photo
<a href="#compression-results-for-a-color-rich-photo" class="heading-anchor" aria-label="Anchor link for: Compression results for a color-rich photo">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The compression reduced the image size from <code>4.65 MB</code> to <code>1.05 MB</code>, a <code>77.42%</code> reduction.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/k-means-image-compression/k-means-compressed-image-16c-color-rich_hu_76679c959c249b4a.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="473">
            <source
                    srcset="https://rdiachenko.com/posts/ml/k-means-image-compression/k-means-compressed-image-16c-color-rich_hu_7815de1f9bad8b3b.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="675">
            <img
                    src="https://rdiachenko.com/posts/ml/k-means-image-compression/k-means-compressed-image-16c-color-rich_hu_7815de1f9bad8b3b.webp"
                    alt="Compressed Image Using 16 Colors on a Color-Rich Photo"
                    width="1200"
                    height="675"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. Compressed Image Using 16 Colors on a Color-Rich Photo</small></figcaption></figure>
<table>
	<thead>
			<tr>
					<th>Initialization Method</th>
					<th style="text-align: right">Execution Time (s)</th>
					<th style="text-align: right">Iterations</th>
					<th style="text-align: right">SSE</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Forgy</td>
					<td style="text-align: right">63.91</td>
					<td style="text-align: right">100</td>
					<td style="text-align: right">59,622</td>
			</tr>
			<tr>
					<td>MacQueen</td>
					<td style="text-align: right">41.52</td>
					<td style="text-align: right">65</td>
					<td style="text-align: right">42,087</td>
			</tr>
			<tr>
					<td>Maximin</td>
					<td style="text-align: right">69.70</td>
					<td style="text-align: right">100</td>
					<td style="text-align: right">39,074</td>
			</tr>
			<tr>
					<td>Bradley-Fayyad</td>
					<td style="text-align: right">97.47</td>
					<td style="text-align: right">8</td>
					<td style="text-align: right">37,709</td>
			</tr>
			<tr>
					<td>K-means++</td>
					<td style="text-align: right">51.57</td>
					<td style="text-align: right">77</td>
					<td style="text-align: right">38,160</td>
			</tr>
			<tr>
					<td>Greedy K-means++</td>
					<td style="text-align: right">34.47</td>
					<td style="text-align: right">41</td>
					<td style="text-align: right">38,164</td>
			</tr>
	</tbody>
</table>
<p>The table shows a trade-off between speed and clustering quality, as measured by SSE, with different methods optimizing for one or the other. Bradley-Fayyad offers the highest quality but is the slowest compared to the other methods.</p>
<h3 id="compression-results-for-a-dominantly-colored-photo">
Compression results for a dominantly colored photo
<a href="#compression-results-for-a-dominantly-colored-photo" class="heading-anchor" aria-label="Anchor link for: Compression results for a dominantly colored photo">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The compression reduced the image size from <code>1.84 MB</code> to <code>1.12 MB</code>, a <code>39.13%</code> reduction.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/k-means-image-compression/k-means-compressed-image-16c-dominantly-colored_hu_686b58faebc9ee71.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="473">
            <source
                    srcset="https://rdiachenko.com/posts/ml/k-means-image-compression/k-means-compressed-image-16c-dominantly-colored_hu_1339a30f06107ffe.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="675">
            <img
                    src="https://rdiachenko.com/posts/ml/k-means-image-compression/k-means-compressed-image-16c-dominantly-colored_hu_1339a30f06107ffe.webp"
                    alt="Compressed Image Using 16 Colors on a Dominantly Colored Photo"
                    width="1200"
                    height="675"
                    loading="lazy">
        </picture><figcaption><small>Figure 3. Compressed Image Using 16 Colors on a Dominantly Colored Photo</small></figcaption></figure>
<table>
	<thead>
			<tr>
					<th>Initialization Method</th>
					<th style="text-align: right">Execution Time (s)</th>
					<th style="text-align: right">Iterations</th>
					<th style="text-align: right">SSE</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Forgy</td>
					<td style="text-align: right">66.87</td>
					<td style="text-align: right">100</td>
					<td style="text-align: right">25,311</td>
			</tr>
			<tr>
					<td>MacQueen</td>
					<td style="text-align: right">37.33</td>
					<td style="text-align: right">53</td>
					<td style="text-align: right">18,861</td>
			</tr>
			<tr>
					<td>Maximin</td>
					<td style="text-align: right">44.94</td>
					<td style="text-align: right">56</td>
					<td style="text-align: right">19,132</td>
			</tr>
			<tr>
					<td>Bradley-Fayyad</td>
					<td style="text-align: right">67.93</td>
					<td style="text-align: right">4</td>
					<td style="text-align: right">18,392</td>
			</tr>
			<tr>
					<td>K-means++</td>
					<td style="text-align: right">16.81</td>
					<td style="text-align: right">21</td>
					<td style="text-align: right">18,336</td>
			</tr>
			<tr>
					<td>Greedy K-means++</td>
					<td style="text-align: right">26.60</td>
					<td style="text-align: right">26</td>
					<td style="text-align: right">18,369</td>
			</tr>
	</tbody>
</table>
<p>Again, there is a trade-off between execution time and clustering quality for each method. Notably, K-means++ excelled in both speed and SSE, particularly standing out in this scenario.</p>
<h3 id="compression-results-for-a-black--white-photo">
Compression results for a black &amp; white photo
<a href="#compression-results-for-a-black--white-photo" class="heading-anchor" aria-label="Anchor link for: Compression results for a black &amp; white photo">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The compression reduced the image size from <code>3.03 MB</code> to <code>1.41 MB</code>, a <code>53.47%</code> reduction.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/k-means-image-compression/k-means-compressed-image-16c-black-white_hu_9212d24d6670cd1c.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="473">
            <source
                    srcset="https://rdiachenko.com/posts/ml/k-means-image-compression/k-means-compressed-image-16c-black-white_hu_b433d1ce90465b7f.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="675">
            <img
                    src="https://rdiachenko.com/posts/ml/k-means-image-compression/k-means-compressed-image-16c-black-white_hu_b433d1ce90465b7f.webp"
                    alt="Compressed Image Using 16 Colors on a Black &amp; White Photo"
                    width="1200"
                    height="675"
                    loading="lazy">
        </picture><figcaption><small>Figure 4. Compressed Image Using 16 Colors on a Black &amp; White Photo</small></figcaption></figure>
<table>
	<thead>
			<tr>
					<th>Initialization Method</th>
					<th style="text-align: right">Execution Time (s)</th>
					<th style="text-align: right">Iterations</th>
					<th style="text-align: right">SSE</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Forgy</td>
					<td style="text-align: right">67.99</td>
					<td style="text-align: right">100</td>
					<td style="text-align: right">16,023</td>
			</tr>
			<tr>
					<td>MacQueen</td>
					<td style="text-align: right">11.65</td>
					<td style="text-align: right">15</td>
					<td style="text-align: right">9,337</td>
			</tr>
			<tr>
					<td>Maximin</td>
					<td style="text-align: right">25.29</td>
					<td style="text-align: right">27</td>
					<td style="text-align: right">9,488</td>
			</tr>
			<tr>
					<td>Bradley-Fayyad</td>
					<td style="text-align: right">35.03</td>
					<td style="text-align: right">2</td>
					<td style="text-align: right">11,950</td>
			</tr>
			<tr>
					<td>K-means++</td>
					<td style="text-align: right">9.97</td>
					<td style="text-align: right">11</td>
					<td style="text-align: right">8,825</td>
			</tr>
			<tr>
					<td>Greedy K-means++</td>
					<td style="text-align: right">14.87</td>
					<td style="text-align: right">9</td>
					<td style="text-align: right">8,182</td>
			</tr>
	</tbody>
</table>
<p>There are some interesting variations compared to the previous results. Greedy K-means++ performed well in terms of SSE, while K-means++ maintained its efficiency. The relative performance of MacQueen improved significantly in this case, showing how the effectiveness of different methods can vary depending on the specific image and randomness.</p>
<p>Across all tests, the Forgy method consistently underperformed, while K-means++ and Greedy K-means++ demonstrated robust performance.</p>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>When you try implementing k-means clustering yourself, you really get to understand it better and fill in any gaps in your knowledge.</p>
<p>Don&rsquo;t just believe everything you read online, even on Wikipedia. Always check the original research papers and adjust your understanding accordingly.</p>
<p>K-means clustering is versatile and 

            
        
            
        
    <a href="https://rdiachenko.com/posts/ml/machine-learning-concepts/#clustering">can be used for many other things</a>
 not covered here. For image compression, the choice of initialization method doesn&rsquo;t make a huge difference. Using MacQueen might result in lower quality, and Bradley-Fayyad might give a slight quality boost, but it might not be worth the extra time.</p>
<p>The initialization methods I used here are non-deterministic, but there are deterministic ones like <strong>PCA-Part</strong> and <strong>Var-Part</strong>. These methods require a good grasp of linear algebra, so I&rsquo;ll cover them in another post once I&rsquo;ve brushed up on those concepts.</p>
<p>Working with Rust, which isn&rsquo;t my primary programming language, added to the fun. I&rsquo;m open to improvements and suggestions, so feel free to contribute to the 
<a href="https://github.com/rdiachenko/rd-blog/tree/main/k-means-image-compression" target="_blank" rel="nofollow noopener">project on GitHub</a>
.</p>
]]></content:encoded></item><item><title>Exploring Zig by Building and Plotting the Mandelbrot Set</title><link>https://rdiachenko.com/posts/zig/exploring-ziglang-with-mandelbrot-set/</link><pubDate>Sat, 25 May 2024 19:13:05 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/zig/exploring-ziglang-with-mandelbrot-set/</guid><description>Getting familiar with the Zig programming language by implementing and visualizing the Mandelbrot Set from scratch.</description><content:encoded><![CDATA[<p>Zig is a general-purpose, statically typed, compiled system programming language designed by Andrew Kelley back in 2016. It aims to succeed C by being simpler and smaller, yet more functional, making it an appealing choice for system-level programming.</p>
<p>The Mandelbrot set, on the other hand, is a complex fractal shape. It consists of points in the complex plane that remain within certain limits under repeated application of a simple mathematical formula.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/zig/exploring-ziglang-with-mandelbrot-set/mandelbrot-set-cover_hu_52cb662e9dc22256.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="630">
            <source
                    srcset="https://rdiachenko.com/posts/zig/exploring-ziglang-with-mandelbrot-set/mandelbrot-set-cover_hu_6d8a0c2f2293b45.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="900">
            <img
                    src="https://rdiachenko.com/posts/zig/exploring-ziglang-with-mandelbrot-set/mandelbrot-set-cover_hu_6d8a0c2f2293b45.webp"
                    alt="Mandelbrot Set Generated by Zig Program"
                    width="1200"
                    height="900"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Mandelbrot Set Generated by Zig Program</small></figcaption></figure>
<p>The idea for this post came after reading <em>Programming Rust: Fast, Safe Systems Development</em> by Jim Blandy, Jason Orendorff, and Leonora Tindall, which included a Mandelbrot implementation in Rust. I liked the concept and thought it would be a fun way to explore Zig by building something visual and interactive. So here we are.</p>
<p>Let’s explore Zig by building a program that plots the Mandelbrot set, as shown in the figure above. The task involves parsing command-line arguments, iterating over image pixels, coloring pixels based on their inclusion in the Mandelbrot set, writing the results into a PNG file, and enhancing rendering performance by leveraging Zig’s concurrency capabilities.</p>
<h2 id="installing-zig">
Installing Zig
<a href="#installing-zig" class="heading-anchor" aria-label="Anchor link for: Installing Zig">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>You can install Zig using Homebrew as follows:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> brew install zig
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> zig version
</span></span><span class="line"><span class="cl"><span class="go">0.16.0
</span></span></span></code></pre></div></div>
</div>
<p>I first wrote this post against Zig 0.12.0 and updated it in September 2026 for 0.16.0. The build system and the standard library changed enough in between that the code below needs 0.16.0 or later.</p>
<p>For non-Mac users, visit the official Zig GitHub page: 
<a href="https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager" target="_blank" rel="nofollow noopener">Install Zig from a Package Manager</a>
.</p>
<p>There’s a 
<a href="https://github.com/ziglang/vscode-zig" target="_blank" rel="nofollow noopener">VS Code extension</a>
 available for Zig. It supports syntax highlighting and auto-compilation but requires the Zig language server, which can be installed as follows:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew install zls</span></span></code></pre></div></div>
</div>
<p>For alternative Zig tools and utilities, visit the official 
<a href="https://ziglang.org/learn/tools/" target="_blank" rel="nofollow noopener">Zig Tools resource</a>
.</p>
<h2 id="creating-a-new-project-with-zig-init">
Creating a new project with ZIG INIT
<a href="#creating-a-new-project-with-zig-init" class="heading-anchor" aria-label="Anchor link for: Creating a new project with ZIG INIT">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Before starting your project, it’s a good idea to familiarize yourself with what Zig has to offer. Run the following command to see a list of available options:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zig -h</span></span></code></pre></div></div>
</div>
<p>To create a basic project structure, use the <code>init</code> command. Here’s how to set up a new project named <code>mandelbrot</code>:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir mandelbrot
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> mandelbrot
</span></span><span class="line"><span class="cl">zig init</span></span></code></pre></div></div>
</div>
<p>This will generate several files:</p>
<ul>
<li><code>build.zig</code>: A build script that declaratively constructs a build graph.</li>
<li><code>build.zig.zon</code>: Contains project metadata and a list of external dependencies.</li>
<li><code>src/main.zig</code>: The entry point of your project, initially filled with some <code>Hello World</code> code.</li>
<li><code>src/root.zig</code>: A small library module that <code>src/main.zig</code> imports, included to show how modules fit together.</li>
</ul>
<p>To build, test, and run your new project, execute:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zig build <span class="nb">test</span> run --summary all</span></span></code></pre></div></div>
</div>
<p>Here’s what you should expect:</p>
<ul>
<li>Build Process: Zig compiles and links your code, reporting progress and success.</li>
<li>Test Outputs: Displays results for each test, including execution time and resource usage.</li>
<li>Run Outputs: Shows the execution time and memory usage for the running application.</li>
</ul>
<p>You should see messages indicating that all steps have succeeded, all tests have passed, and your application ran successfully:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">All your codebase are belong to us.
</span></span><span class="line"><span class="cl">info: arg: .../mandelbrot/zig-out/bin/mandelbrot
</span></span><span class="line"><span class="cl">Run `zig build test` to run the tests.
</span></span><span class="line"><span class="cl">Build Summary: 10/10 steps succeeded; 3/3 tests passed
</span></span><span class="line"><span class="cl">test success
</span></span><span class="line"><span class="cl">+- run test 1 pass (1 total) 479ms MaxRSS:2M
</span></span><span class="line"><span class="cl">|  +- compile test Debug native success 1s MaxRSS:263M
</span></span><span class="line"><span class="cl">+- run test 2 pass (2 total) 666ms MaxRSS:2M
</span></span><span class="line"><span class="cl">   +- compile test Debug native success 1s MaxRSS:269M
</span></span><span class="line"><span class="cl">run success
</span></span><span class="line"><span class="cl">+- run exe mandelbrot success 290ms
</span></span><span class="line"><span class="cl">   +- compile exe mandelbrot Debug native success 1s MaxRSS:260M
</span></span><span class="line"><span class="cl">   +- install success
</span></span><span class="line"><span class="cl">      +- install mandelbrot success
</span></span><span class="line"><span class="cl">         +- compile exe mandelbrot Debug native (reused)</span></span></code></pre></div></div>
</div>
<p>Congrats! You’ve just built and run the basic Zig program. Now let&rsquo;s dive deeper into the Mandelbrot set and start implementing specific functions.</p>
<h2 id="implementing-the-mandelbrot-set">
Implementing the Mandelbrot set
<a href="#implementing-the-mandelbrot-set" class="heading-anchor" aria-label="Anchor link for: Implementing the Mandelbrot set">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Consider this function to explore the behavior of the sequence:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">fn</span><span class="w"> </span><span class="nf">squareAdd</span><span class="p">(</span><span class="n">c</span><span class="p">:</span><span class="w"> </span><span class="kt">f64</span><span class="p">,</span><span class="w"> </span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">u32</span><span class="p">)</span><span class="w"> </span><span class="kt">f64</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">var</span><span class="w"> </span><span class="n">z</span><span class="p">:</span><span class="w"> </span><span class="kt">f64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="n">_</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">z</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">c</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">z</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Here, <code>z</code> starts at zero and updates each iteration by squaring itself and then adding <code>c</code>. Experimentation shows that if <code>c</code> is greater than 0.25 or less than -2.0, then <code>z</code> eventually becomes infinitely large, otherwise, it stays around zero.</p>
<p>If you plot values for different <code>c</code> (e.g., -5, -1, 0.2, 1) with a limit of 8 iterations, you’ll see <code>z</code>’s distribution depending on <code>c</code> values. Those outside the range [-2.0, 0.25] result in a significantly larger <code>z</code> after 8 iterations compared to values within this range, as shown in Figure 2.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/zig/exploring-ziglang-with-mandelbrot-set/function-value-distributions_hu_32f16d946fb40d23.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="489">
            <source
                    srcset="https://rdiachenko.com/posts/zig/exploring-ziglang-with-mandelbrot-set/function-value-distributions_hu_588edede33a98140.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="698">
            <img
                    src="https://rdiachenko.com/posts/zig/exploring-ziglang-with-mandelbrot-set/function-value-distributions_hu_588edede33a98140.webp"
                    alt="Function Value Distributions Depending on C for 8 Iterations"
                    width="1200"
                    height="698"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. Function Value Distributions Depending on C for 8 Iterations</small></figcaption></figure>
<p>To get the actual Mandelbrot set, replace <code>z</code> and <code>c</code> with complex numbers. This shift produces the beautiful patterns of the Mandelbrot set. It is defined by complex numbers <code>c</code> for which the sequence \(z_{n+1} = z_n^2 + c\) remains bounded when iterated from \(z_0=0\).</p>
<h3 id="building-a-simple-complex-number-type">
Building a simple complex number type
<a href="#building-a-simple-complex-number-type" class="heading-anchor" aria-label="Anchor link for: Building a simple complex number type">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Zig doesn’t have a built-in type for complex numbers, so let&rsquo;s define one. Here’s how you can create a generic type for complex numbers using Zig’s compile-time capabilities:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">fn</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kr">comptime</span><span class="w"> </span><span class="n">T</span><span class="p">:</span><span class="w"> </span><span class="kt">type</span><span class="p">)</span><span class="w"> </span><span class="kt">type</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kr">const</span><span class="w"> </span><span class="n">Self</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">@This</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">re</span><span class="p">:</span><span class="w"> </span><span class="n">T</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">im</span><span class="p">:</span><span class="w"> </span><span class="n">T</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="cs">/// Adds two complex numbers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">fn</span><span class="w"> </span><span class="nf">add</span><span class="p">(</span><span class="n">self</span><span class="p">:</span><span class="w"> </span><span class="n">Self</span><span class="p">,</span><span class="w"> </span><span class="n">other</span><span class="p">:</span><span class="w"> </span><span class="n">Self</span><span class="p">)</span><span class="w"> </span><span class="n">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="n">T</span><span class="p">){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">other</span><span class="p">.</span><span class="n">re</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">other</span><span class="p">.</span><span class="n">im</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="cs">/// Multiplies two complex numbers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">fn</span><span class="w"> </span><span class="nf">multiply</span><span class="p">(</span><span class="n">self</span><span class="p">:</span><span class="w"> </span><span class="n">Self</span><span class="p">,</span><span class="w"> </span><span class="n">other</span><span class="p">:</span><span class="w"> </span><span class="n">Self</span><span class="p">)</span><span class="w"> </span><span class="n">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="n">T</span><span class="p">){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">other</span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">other</span><span class="p">.</span><span class="n">im</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">other</span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">other</span><span class="p">.</span><span class="n">re</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="cs">/// Computes the squared norm of the complex number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">fn</span><span class="w"> </span><span class="nf">normSqr</span><span class="p">(</span><span class="n">self</span><span class="p">:</span><span class="w"> </span><span class="n">Self</span><span class="p">)</span><span class="w"> </span><span class="n">T</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">im</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>The function returns an anonymous struct representing a complex number. The <code>comptime</code> keyword on a parameter means that the parameter must be known at compile time.</p>
<p>Built-in functions are provided by the compiler and are prefixed with <code>@</code>. <code>@This()</code> returns the innermost struct, enum, or union that this function call is inside. This is useful for an anonymous struct that needs to refer to itself like in the case above.</p>
<p>Zig supports 3 types of comments. Normal comments <code>//</code> are ignored, but doc comments <code>///</code> (used in the code above) and top-level doc comments <code>//!</code> are used by the compiler to generate the package documentation.</p>
<p>A complex number <code>c</code> has both real (<code>c.re</code>) and imaginary (<code>c.im</code>) components, which can be treated as the <code>x</code> and <code>y</code> coordinates on a Cartesian plane. To visualize the Mandelbrot set, you&rsquo;re going to color each point based on whether the corresponding complex number belongs to the set. Points in the set are typically colored black, while those not in the set are colored in lighter shades.</p>
<h3 id="figuring-out-when-a-point-escapes">
Figuring out when a point escapes
<a href="#figuring-out-when-a-point-escapes" class="heading-anchor" aria-label="Anchor link for: Figuring out when a point escapes">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Experimental observations show that if a complex number <code>z</code> ever exits the circle of radius 2 centered at the origin, it will inevitably escape to infinity. Based on this, you can determine whether a complex number is part of the Mandelbrot set by tracking its trajectory.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">fn</span><span class="w"> </span><span class="nf">escapeTime</span><span class="p">(</span><span class="n">c</span><span class="p">:</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">),</span><span class="w"> </span><span class="n">limit</span><span class="p">:</span><span class="w"> </span><span class="kt">usize</span><span class="p">)</span><span class="w"> </span><span class="p">?</span><span class="kt">usize</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">var</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">){</span><span class="w"> </span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0.0</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="n">limit</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="n">i</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">z</span><span class="p">.</span><span class="nf">normSqr</span><span class="p">()</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mf">4.0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="n">i</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">z</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">z</span><span class="p">.</span><span class="nf">multiply</span><span class="p">(</span><span class="n">z</span><span class="p">).</span><span class="nf">add</span><span class="p">(</span><span class="n">c</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>The function <code>escapeTime</code> calculates how many iterations it takes for a complex number to escape the radius of 2 around the origin, marking it as not part of the Mandelbrot set.</p>
<p>The <code>?T</code> syntax denotes an optional type, where <code>T</code> can be any type (e.g., <code>?usize</code>). This means the value can either be of type <code>T</code> or it can be <code>null</code>, indicating the absence of a value.</p>
<p>The <code>for</code> loop syntax <code>for (0..limit) |i| { ... }</code> is a way to iterate over a range of integers from <code>0</code> to <code>limit - 1</code>, and capture each integer in <code>i</code> to be used inside the loop.</p>
<h3 id="adding-a-few-quick-tests">
Adding a few quick tests
<a href="#adding-a-few-quick-tests" class="heading-anchor" aria-label="Anchor link for: Adding a few quick tests">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The Zig standard library contains functions for testing your code. This is super useful because in languages like Java you bring external dependencies like JUnit to write basic unit tests. Let&rsquo;s add few tests for the <code>escapeTime</code> function:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">test</span><span class="w"> </span><span class="s">&#34;expect point escapes the Mandelbrot set&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">limit</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1000</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">){</span><span class="w"> </span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">1.0</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">escapeTime</span><span class="p">(</span><span class="n">c</span><span class="p">,</span><span class="w"> </span><span class="n">limit</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expect</span><span class="p">(</span><span class="n">result</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">null</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">test</span><span class="w"> </span><span class="s">&#34;expect point stays within the Mandelbrot set&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">limit</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1000</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">){</span><span class="w"> </span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0.0</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">escapeTime</span><span class="p">(</span><span class="n">c</span><span class="p">,</span><span class="w"> </span><span class="n">limit</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expect</span><span class="p">(</span><span class="n">result</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">null</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>The tests check whether points escape or stay within the Mandelbrot set under the iteration limit.</p>
<p>To run these tests, use the command:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zig build <span class="nb">test</span> --summary all</span></span></code></pre></div></div>
</div>
<h2 id="rendering-the-mandelbrot-set">
Rendering the Mandelbrot set
<a href="#rendering-the-mandelbrot-set" class="heading-anchor" aria-label="Anchor link for: Rendering the Mandelbrot set">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Rendering the Mandelbrot set involves translating between two coordinate systems: the pixel coordinates of the output image and the corresponding points on the complex plane. The mapping between these systems is determined by the section of the Mandelbrot set you want to plot and the resolution of the image. These parameters will be specified through command-line arguments.</p>
<h3 id="mapping-pixels-to-the-complex-plane">
Mapping pixels to the complex plane
<a href="#mapping-pixels-to-the-complex-plane" class="heading-anchor" aria-label="Anchor link for: Mapping pixels to the complex plane">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The function below translates pixel coordinates from the image to their corresponding points on the complex plane.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">fn</span><span class="w"> </span><span class="nf">pixelToPoint</span><span class="p">(</span><span class="n">imgSize</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="kt">usize</span><span class="p">,</span><span class="w"> </span><span class="n">pixel</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">pointTopLeft</span><span class="p">:</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">),</span><span class="w"> </span><span class="n">pointBottomRight</span><span class="p">:</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">))</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pointBottomRight</span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">pointTopLeft</span><span class="p">.</span><span class="n">re</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pointTopLeft</span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">pointBottomRight</span><span class="p">.</span><span class="n">im</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Calculate the real part of the complex number.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pointTopLeft</span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nb">@as</span><span class="p">(</span><span class="kt">f64</span><span class="p">,</span><span class="w"> </span><span class="nb">@floatFromInt</span><span class="p">(</span><span class="n">pixel</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">width</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="o">/</span><span class="w"> </span><span class="nb">@as</span><span class="p">(</span><span class="kt">f64</span><span class="p">,</span><span class="w"> </span><span class="nb">@floatFromInt</span><span class="p">(</span><span class="n">imgSize</span><span class="p">[</span><span class="mi">0</span><span class="p">])),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Calculate the imaginary part of the complex number.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Subtract here because in image coordinates, y increases as you go down,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// but in the complex plane, the imaginary part increases as you go up.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pointTopLeft</span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nb">@as</span><span class="p">(</span><span class="kt">f64</span><span class="p">,</span><span class="w"> </span><span class="nb">@floatFromInt</span><span class="p">(</span><span class="n">pixel</span><span class="p">[</span><span class="mi">1</span><span class="p">]))</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">height</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="o">/</span><span class="w"> </span><span class="nb">@as</span><span class="p">(</span><span class="kt">f64</span><span class="p">,</span><span class="w"> </span><span class="nb">@floatFromInt</span><span class="p">(</span><span class="n">imgSize</span><span class="p">[</span><span class="mi">1</span><span class="p">])),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">test</span><span class="w"> </span><span class="s">&#34;expect pixel maps to point on the complex plane&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">topLeft</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">){</span><span class="w"> </span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">-</span><span class="mf">1.0</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">bottomRight</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">){</span><span class="w"> </span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">1.0</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">-</span><span class="mf">1.0</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">pixelToPoint</span><span class="p">(.{</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="mi">200</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">.{</span><span class="w"> </span><span class="mi">25</span><span class="p">,</span><span class="w"> </span><span class="mi">175</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="n">topLeft</span><span class="p">,</span><span class="w"> </span><span class="n">bottomRight</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expectEqual</span><span class="p">(</span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">){</span><span class="w"> </span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">-</span><span class="mf">0.5</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">-</span><span class="mf">0.75</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="n">result</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>In the test above, given an image of <code>100x200</code> pixels and a complex plane area from <code>-1.0, 1.0</code> to <code>1.0, -1.0</code>, the pixel <code>25, 175</code> should map to the complex number <code>-0.5, -0.75</code>.</p>
<h3 id="plotting-each-point-based-on-escape-time">
Plotting each point based on escape time
<a href="#plotting-each-point-based-on-escape-time" class="heading-anchor" aria-label="Anchor link for: Plotting each point based on escape time">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>To visualize the Mandelbrot set, each pixel’s corresponding point on the complex plane is evaluated using the <code>escapeTime</code> function. The color of each pixel is then determined by how quickly the point escapes to infinity. Points that are part of the Mandelbrot set are colored black, while those that escape are shaded darker the longer they take to escape.</p>
<p>Here’s how you can render a section of the Mandelbrot set into a buffer of pixels, where each pixel’s grayscale value represents how quickly points escape:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">fn</span><span class="w"> </span><span class="nf">render</span><span class="p">(</span><span class="n">pixels</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kt">u8</span><span class="p">,</span><span class="w"> </span><span class="n">imgSize</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="kt">usize</span><span class="p">,</span><span class="w"> </span><span class="n">pointTopLeft</span><span class="p">:</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">pointBottomRight</span><span class="p">:</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">))</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="n">imgSize</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span><span class="w"> </span><span class="o">|</span><span class="n">row</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="n">imgSize</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span><span class="w"> </span><span class="o">|</span><span class="n">col</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kr">const</span><span class="w"> </span><span class="n">point</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">pixelToPoint</span><span class="p">(</span><span class="n">imgSize</span><span class="p">,</span><span class="w"> </span><span class="p">.{</span><span class="w"> </span><span class="n">col</span><span class="p">,</span><span class="w"> </span><span class="n">row</span><span class="w"> </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">pointTopLeft</span><span class="p">,</span><span class="w"> </span><span class="n">pointBottomRight</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kr">const</span><span class="w"> </span><span class="n">escapeCount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">escapeTime</span><span class="p">(</span><span class="n">point</span><span class="p">,</span><span class="w"> </span><span class="mi">255</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">pixels</span><span class="p">[</span><span class="n">row</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">imgSize</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">col</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="n">escapeCount</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="kc">null</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">else</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="mi">255</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">escapeCount</span><span class="o">.?</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p><code>[]u8</code> denotes a slice of unsigned 8-bit integers. In the context of the <code>render</code> function, this slice represents the buffer holding the grayscale pixel values for the image, where each pixel is one byte.</p>
<p>The <code>.?</code> operator is used to get the value inside an optional type and ensure that it is not <code>null</code>. If the value is <code>null</code>, the program will raise an error.</p>
<p>The <code>switch</code> statement is used for pattern matching. It evaluates an expression and executes the code associated with the matching pattern. However, Zig 
<a href="https://github.com/ziglang/zig/issues/1545" target="_blank" rel="nofollow noopener">doesn&rsquo;t support optionals as a switch expression</a>
. The compiler rejects the program as soon as you build it with the above <code>switch</code> block:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">run
</span></span><span class="line"><span class="cl">+- run exe mandelbrot
</span></span><span class="line"><span class="cl">   +- compile exe mandelbrot Debug native 1 errors
</span></span><span class="line"><span class="cl">src/main.zig:215:54: error: switch on type &#39;?usize&#39;
</span></span><span class="line"><span class="cl">            pixels[row * imgSize[0] + col] = switch (escapeCount) {
</span></span><span class="line"><span class="cl">                                                     ^~~~~~~~~~~</span></span></code></pre></div></div>
</div>
<p>Looks like the rationale behind this is that when you handle optionals you have only two cases which can be handled with <code>if</code>-<code>else</code>. Thus, Zig developers refused to complicate the switch.</p>
<p>To fix the error replace the <code>switch</code> block with an <code>if</code> statement that captures the value <code>count</code> from the optional:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="n">pixels</span><span class="p">[</span><span class="n">row</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">imgSize</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">col</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">escapeCount</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="n">count</span><span class="o">|</span><span class="w"> </span><span class="mi">255</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nb">@as</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span><span class="w"> </span><span class="nb">@intCast</span><span class="p">(</span><span class="n">count</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">else</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span></span></span></code></pre></div></div>
</div>
<h2 id="writing-the-output-as-a-png-image">
Writing the output as a PNG image
<a href="#writing-the-output-as-a-png-image" class="heading-anchor" aria-label="Anchor link for: Writing the output as a PNG image">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>It&rsquo;s time to explore how to use external dependencies in Zig. Zig provides 
<a href="https://ziglang.org/learn/build-system/" target="_blank" rel="nofollow noopener">a build system</a>
 that can be leveraged to manage these dependencies.</p>
<p>I found 
<a href="https://github.com/zigimg/zigimg" target="_blank" rel="nofollow noopener">Zigimg library</a>
, which supports reading and writing various image formats. Let&rsquo;s use it to save the Mandelbrot set as a PNG image.</p>
<p>First, add Zigimg to your project with the package manager. The command below fetches the release built for Zig 0.16.0 and records it in <code>build.zig.zon</code>:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zig fetch --save <span class="s2">&#34;git+https://github.com/zigimg/zigimg.git#zigimg_zig_0.16.0&#34;</span></span></span></code></pre></div></div>
</div>
<p>It adds a <code>.dependencies</code> entry with the resolved commit and a hash of the package contents, so every later build gets the same code:</p>
<div class="code-block" data-frame="editor">
        <div class="code-header">
                <span class="code-filename">build.zig.zon</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="p">.</span><span class="n">dependencies</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">.{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">zigimg</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">.{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">url</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;git+https://github.com/zigimg/zigimg.git?ref=zigimg_zig_0.16.0#d695acd97c02e57bb151e8f659d1280f5cd6ca70&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">hash</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;zigimg-0.1.0-8_eo2oyaFwBZwJpmqPkCfVXWBrHcqbYwmrp1I6bTD3lI&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">},</span></span></span></code></pre></div></div>
</div>
<p>Then, update <code>build.zig</code> file to expose the package as a module just before installing the main artifact <code>b.installArtifact(exe);</code> line:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="kr">const</span><span class="w"> </span><span class="n">zigimg</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">b</span><span class="p">.</span><span class="nf">dependency</span><span class="p">(</span><span class="s">&#34;zigimg&#34;</span><span class="p">,</span><span class="w"> </span><span class="p">.{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">target</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">target</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">optimize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">optimize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">exe</span><span class="p">.</span><span class="n">root_module</span><span class="p">.</span><span class="nf">addImport</span><span class="p">(</span><span class="s">&#34;zigimg&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">zigimg</span><span class="p">.</span><span class="nf">module</span><span class="p">(</span><span class="s">&#34;zigimg&#34;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">b</span><span class="p">.</span><span class="nf">installArtifact</span><span class="p">(</span><span class="n">exe</span><span class="p">);</span></span></span></code></pre></div></div>
</div>
<p>Update <code>src/main.zig</code> file to import Zigimg:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="kr">const</span><span class="w"> </span><span class="n">zigimg</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">@import</span><span class="p">(</span><span class="s">&#34;zigimg&#34;</span><span class="p">);</span></span></span></code></pre></div></div>
</div>
<p>Now you’re ready to write the PNG image. Here&rsquo;s a function which does this:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">fn</span><span class="w"> </span><span class="nf">writeImage</span><span class="p">(</span><span class="n">io</span><span class="p">:</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">Io</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="p">:</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">mem</span><span class="p">.</span><span class="n">Allocator</span><span class="p">,</span><span class="w"> </span><span class="n">filename</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kr">const</span><span class="w"> </span><span class="kt">u8</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">pixels</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kr">const</span><span class="w"> </span><span class="kt">u8</span><span class="p">,</span><span class="w"> </span><span class="n">imgSize</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="kt">usize</span><span class="p">)</span><span class="w"> </span><span class="o">!</span><span class="kt">void</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Copy the grayscale bytes into an image object that knows</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// their width, height and pixel format.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">var</span><span class="w"> </span><span class="n">image</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">zigimg</span><span class="p">.</span><span class="n">Image</span><span class="p">.</span><span class="nf">fromRawPixels</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">allocator</span><span class="p">,</span><span class="w"> </span><span class="n">imgSize</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="w"> </span><span class="n">imgSize</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span><span class="w"> </span><span class="n">pixels</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">grayscale8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="n">image</span><span class="p">.</span><span class="nf">deinit</span><span class="p">(</span><span class="n">allocator</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// The PNG encoder writes through this buffer.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">var</span><span class="w"> </span><span class="n">writeBuffer</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="n">zigimg</span><span class="p">.</span><span class="n">io</span><span class="p">.</span><span class="n">DEFAULT_BUFFER_SIZE</span><span class="p">]</span><span class="kt">u8</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">undefined</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">image</span><span class="p">.</span><span class="nf">writeToFilePath</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">allocator</span><span class="p">,</span><span class="w"> </span><span class="n">io</span><span class="p">,</span><span class="w"> </span><span class="n">filename</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">writeBuffer</span><span class="p">,</span><span class="w"> </span><span class="p">.{</span><span class="w"> </span><span class="p">.</span><span class="n">png</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">.{}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Zig doesn’t have garbage collection or automatic runtime memory management. Instead, memory is managed explicitly using allocators, which handle memory allocation and deallocation. This allows for choosing the appropriate memory management strategy for the context.</p>
<p>Here the function takes whichever allocator the caller passes in. Writing a file works the same way: since Zig 0.16 every I/O operation goes through an explicit <code>std.Io</code> instance instead of calling into the operating system behind the scenes. Both the allocator and the <code>Io</code> instance come from <code>main</code>, which you&rsquo;ll see in a moment.</p>
<p>In Zig, errors are treated as values. An error set is similar to an enum, where each possible error is a distinct value. Functions can return an error union type, indicated by the <code>!</code> operator, which combines an error set with another type. For example, <code>!void</code> in a function signature means the function could fail with an error but does not return a value.</p>
<p>The <code>try</code> keyword is used to handle expressions that might return an error. It automatically propagates the error up the call stack if one occurs, or continues execution normally if no error arises.</p>
<p>The <code>defer</code> keyword postpones the execution of a statement until the block exits, regardless of how the exit occurs. This is useful for ensuring resources are cleaned up. The <code>errdefer</code> variant specifically runs its deferred action only if exiting the block due to an error.</p>
<h2 id="parsing-command-line-arguments">
Parsing command-line arguments
<a href="#parsing-command-line-arguments" class="heading-anchor" aria-label="Anchor link for: Parsing command-line arguments">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>To make the Mandelbrot set rendering more flexible, you’ll want to dynamically specify the image size and the section of the Mandelbrot set to be visualized. To achieve this, let’s create a function to parse command-line arguments that determine the resolution of the image and the portion of the Mandelbrot set the image will display.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">fn</span><span class="w"> </span><span class="nf">parseArg</span><span class="p">(</span><span class="kr">comptime</span><span class="w"> </span><span class="n">T</span><span class="p">:</span><span class="w"> </span><span class="kt">type</span><span class="p">,</span><span class="w"> </span><span class="n">str</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kr">const</span><span class="w"> </span><span class="kt">u8</span><span class="p">,</span><span class="w"> </span><span class="n">separator</span><span class="p">:</span><span class="w"> </span><span class="kt">u8</span><span class="p">)</span><span class="w"> </span><span class="p">?[</span><span class="mi">2</span><span class="p">]</span><span class="n">T</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">str</span><span class="p">.</span><span class="n">len</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">index</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">mem</span><span class="p">.</span><span class="nf">findScalar</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span><span class="w"> </span><span class="n">str</span><span class="p">,</span><span class="w"> </span><span class="n">separator</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">index</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="n">i</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kr">const</span><span class="w"> </span><span class="n">leftStr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">str</span><span class="p">[</span><span class="mi">0</span><span class="o">..</span><span class="n">i</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kr">const</span><span class="w"> </span><span class="n">rightStr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">str</span><span class="p">[</span><span class="n">i</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="o">..</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kr">const</span><span class="w"> </span><span class="n">left</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">parseT</span><span class="p">(</span><span class="n">T</span><span class="p">,</span><span class="w"> </span><span class="n">leftStr</span><span class="p">)</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kr">const</span><span class="w"> </span><span class="n">right</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">parseT</span><span class="p">(</span><span class="n">T</span><span class="p">,</span><span class="w"> </span><span class="n">rightStr</span><span class="p">)</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="n">T</span><span class="p">{</span><span class="w"> </span><span class="n">left</span><span class="p">,</span><span class="w"> </span><span class="n">right</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Making the <code>parseArg</code> function generic for any type <code>T</code> is challenging in Zig because it lacks runtime reflection and dynamic type interpretation common in more dynamically-typed languages. To parse different types effectively, you’ll need to explicitly handle each type’s parsing logic:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">fn</span><span class="w"> </span><span class="nf">parseT</span><span class="p">(</span><span class="kr">comptime</span><span class="w"> </span><span class="n">T</span><span class="p">:</span><span class="w"> </span><span class="kt">type</span><span class="p">,</span><span class="w"> </span><span class="n">str</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kr">const</span><span class="w"> </span><span class="kt">u8</span><span class="p">)</span><span class="w"> </span><span class="o">!</span><span class="n">T</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="n">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">f32</span><span class="p">,</span><span class="w"> </span><span class="kt">f64</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">fmt</span><span class="p">.</span><span class="nf">parseFloat</span><span class="p">(</span><span class="n">T</span><span class="p">,</span><span class="w"> </span><span class="n">str</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">i32</span><span class="p">,</span><span class="w"> </span><span class="kt">i64</span><span class="p">,</span><span class="w"> </span><span class="kt">usize</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">fmt</span><span class="p">.</span><span class="nf">parseInt</span><span class="p">(</span><span class="n">T</span><span class="p">,</span><span class="w"> </span><span class="n">str</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">else</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nb">@compileError</span><span class="p">(</span><span class="s">&#34;Unsupported type for parseT&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Add a test to check various combinations of types and values:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">test</span><span class="w"> </span><span class="s">&#34;expect argument parsed as proper type&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expectEqual</span><span class="p">([</span><span class="mi">2</span><span class="p">]</span><span class="kt">i32</span><span class="p">{</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="mi">200</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nf">parseArg</span><span class="p">(</span><span class="kt">i32</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;100x200&#34;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;x&#39;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expectEqual</span><span class="p">([</span><span class="mi">2</span><span class="p">]</span><span class="kt">f64</span><span class="p">{</span><span class="w"> </span><span class="o">-</span><span class="mf">2.0</span><span class="p">,</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nf">parseArg</span><span class="p">(</span><span class="kt">f64</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;-2.0,0.5&#34;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;,&#39;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expectEqual</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="nf">parseArg</span><span class="p">(</span><span class="kt">f64</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;x0.2&#34;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;x&#39;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expectEqual</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="nf">parseArg</span><span class="p">(</span><span class="kt">i32</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;ab10,7c&#34;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;,&#39;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expectEqual</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="nf">parseArg</span><span class="p">(</span><span class="kt">i32</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;7,&#34;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;,&#39;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expectEqual</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="nf">parseArg</span><span class="p">(</span><span class="kt">i32</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;,7&#34;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;,&#39;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expectEqual</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="nf">parseArg</span><span class="p">(</span><span class="kt">i32</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;,&#39;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Run tests to verify everything works as expected:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> zig build <span class="nb">test</span> --summary all
</span></span><span class="line"><span class="cl"><span class="go">Build Summary: 3/3 steps succeeded; 4/4 tests passed
</span></span></span><span class="line"><span class="cl"><span class="go">test success
</span></span></span><span class="line"><span class="cl"><span class="go">+- run test 4 pass (4 total) 4ms MaxRSS:2M
</span></span></span><span class="line"><span class="cl"><span class="go">   +- compile test Debug native cached 62ms MaxRSS:35M
</span></span></span></code></pre></div></div>
</div>
<p>To further enhance the functionality, implement a helper function to parse strings directly into complex numbers:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">fn</span><span class="w"> </span><span class="nf">parseComplex</span><span class="p">(</span><span class="kr">comptime</span><span class="w"> </span><span class="n">T</span><span class="p">:</span><span class="w"> </span><span class="kt">type</span><span class="p">,</span><span class="w"> </span><span class="n">str</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kr">const</span><span class="w"> </span><span class="kt">u8</span><span class="p">)</span><span class="w"> </span><span class="p">?</span><span class="nf">Complex</span><span class="p">(</span><span class="n">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">maybePair</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">parseArg</span><span class="p">(</span><span class="n">T</span><span class="p">,</span><span class="w"> </span><span class="n">str</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;,&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">maybePair</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="n">pair</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nf">Complex</span><span class="p">(</span><span class="n">T</span><span class="p">){</span><span class="w"> </span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pair</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="w"> </span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pair</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">test</span><span class="w"> </span><span class="s">&#34;expect string is parsed into complex number&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expectEqual</span><span class="p">(</span><span class="nf">Complex</span><span class="p">(</span><span class="kt">f64</span><span class="p">){</span><span class="w"> </span><span class="p">.</span><span class="n">re</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">-</span><span class="mf">2.0</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">im</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">parseComplex</span><span class="p">(</span><span class="kt">f64</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;-2.0,0.5&#34;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expectEqual</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="nf">parseComplex</span><span class="p">(</span><span class="kt">f64</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;-2.0,&#34;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Great progress so far. You have everything ready to connect all the dots.</p>
<h2 id="putting-it-all-together-and-running-the-program">
Putting it all together and running the program
<a href="#putting-it-all-together-and-running-the-program" class="heading-anchor" aria-label="Anchor link for: Putting it all together and running the program">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>With all the pieces in place, here&rsquo;s the main function which, for given arguments, plots the Mandelbrot set and stores it into a PNG file.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="kr">pub</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">init</span><span class="p">:</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">process</span><span class="p">.</span><span class="n">Init</span><span class="p">)</span><span class="w"> </span><span class="o">!</span><span class="kt">void</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">allocator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">init</span><span class="p">.</span><span class="n">gpa</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">args</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">init</span><span class="p">.</span><span class="n">minimal</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="nf">toSlice</span><span class="p">(</span><span class="n">init</span><span class="p">.</span><span class="n">arena</span><span class="p">.</span><span class="nf">allocator</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">len</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">std</span><span class="p">.</span><span class="n">log</span><span class="p">.</span><span class="nf">err</span><span class="p">(</span><span class="s">&#34;Usage: {s} [IMAGE_FILE] [IMAGE_RESOLUTION] [MANDELBROT_TOP_LEFT] [MANDELBROT_BOTTOM_RIGHT]</span><span class="se">\n</span><span class="s">Example: {s} mandelbrot.png 1000x750 -1.20,0.35 -1,0.20&#34;</span><span class="p">,</span><span class="w"> </span><span class="p">.{</span><span class="w"> </span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="w"> </span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">std</span><span class="p">.</span><span class="n">process</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">imgSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nf">parseArg</span><span class="p">(</span><span class="kt">usize</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span><span class="w"> </span><span class="sc">&#39;x&#39;</span><span class="p">))</span><span class="w"> </span><span class="o">|</span><span class="n">s</span><span class="o">|</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">std</span><span class="p">.</span><span class="n">log</span><span class="p">.</span><span class="nf">err</span><span class="p">(</span><span class="s">&#34;Failed to parse image resolution&#34;</span><span class="p">,</span><span class="w"> </span><span class="p">.{});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">topLeft</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nf">parseComplex</span><span class="p">(</span><span class="kt">f64</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">[</span><span class="mi">3</span><span class="p">]))</span><span class="w"> </span><span class="o">|</span><span class="n">p</span><span class="o">|</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">std</span><span class="p">.</span><span class="n">log</span><span class="p">.</span><span class="nf">err</span><span class="p">(</span><span class="s">&#34;Failed to parse top left point&#34;</span><span class="p">,</span><span class="w"> </span><span class="p">.{});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">bottomRight</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nf">parseComplex</span><span class="p">(</span><span class="kt">f64</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">[</span><span class="mi">4</span><span class="p">]))</span><span class="w"> </span><span class="o">|</span><span class="n">p</span><span class="o">|</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">std</span><span class="p">.</span><span class="n">log</span><span class="p">.</span><span class="nf">err</span><span class="p">(</span><span class="s">&#34;Failed to parse bottom right point&#34;</span><span class="p">,</span><span class="w"> </span><span class="p">.{});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">pixels</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">allocator</span><span class="p">.</span><span class="nf">alloc</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span><span class="w"> </span><span class="n">imgSize</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">imgSize</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="n">allocator</span><span class="p">.</span><span class="nf">free</span><span class="p">(</span><span class="n">pixels</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">render</span><span class="p">(</span><span class="n">pixels</span><span class="p">,</span><span class="w"> </span><span class="n">imgSize</span><span class="p">,</span><span class="w"> </span><span class="n">topLeft</span><span class="p">,</span><span class="w"> </span><span class="n">bottomRight</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="nf">writeImage</span><span class="p">(</span><span class="n">init</span><span class="p">.</span><span class="n">io</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span><span class="w"> </span><span class="n">pixels</span><span class="p">,</span><span class="w"> </span><span class="n">imgSize</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>Since Zig 0.16, <code>main</code> can take a <code>std.process.Init</code> argument. It carries the command-line arguments, a general-purpose allocator <code>init.gpa</code>, an arena <code>init.arena</code> that lives as long as the process, and the <code>std.Io</code> instance that file operations need. The function reads the arguments into the arena, because they&rsquo;re needed until the program exits, and verifies their number. It displays usage information if the inputs are incorrect.</p>
<p>Next, it attempts to parse the image size and coordinates for the Mandelbrot set from the arguments, handling errors if parsing fails. If parsing is successful, it allocates a buffer for pixel data, calls the rendering function to generate the image, and then writes the image to a specified file.</p>
<p>Execute the following command to build, test, and run the program:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zig build <span class="nb">test</span> run --summary all <span class="se">\
</span></span></span><span class="line"><span class="cl">  -- mandelbrot.png 1000x750 -1.20,0.35 -1,0.20</span></span></code></pre></div></div>
</div>
<p>Upon successful execution, you should see:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Build Summary: 8/8 steps succeeded; 5/5 tests passed
</span></span><span class="line"><span class="cl">test success
</span></span><span class="line"><span class="cl">+- run test 5 pass (5 total) 280ms MaxRSS:2M
</span></span><span class="line"><span class="cl">   +- compile test Debug native success 1s MaxRSS:288M
</span></span><span class="line"><span class="cl">run success
</span></span><span class="line"><span class="cl">+- run exe mandelbrot success 405ms
</span></span><span class="line"><span class="cl">   +- compile exe mandelbrot Debug native success 3s MaxRSS:479M
</span></span><span class="line"><span class="cl">   +- install success
</span></span><span class="line"><span class="cl">      +- install mandelbrot success
</span></span><span class="line"><span class="cl">         +- compile exe mandelbrot Debug native (reused)</span></span></code></pre></div></div>
</div>
<p>A <code>mandelbrot.png</code> image is created in the project’s root folder. Note that the <code>zig build</code> command defaults to <code>Debug</code> mode, which does not apply optimizations.</p>
<p>To build an optimised executable you should go with either of these:</p>
<ul>
<li><code>ReleaseSafe</code> optimizations on and safety on.</li>
<li><code>ReleaseFast</code> optimizations on and safety off.</li>
<li><code>ReleaseSmall</code> optimizations on and safety off.</li>
</ul>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zig build -Doptimize<span class="o">=</span>ReleaseFast --summary all</span></span></code></pre></div></div>
</div>
<p>Now let&rsquo;s run the program with a higher image resolution and see how it performs. The following command measures the time it takes to compute and plot the Mandelbrot set for a larger image:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">time</span> ./zig-out/bin/mandelbrot mandelbrot.png 4000x3000 -1.20,0.35 -1,0.20
</span></span><span class="line"><span class="cl"><span class="go">./zig-out/bin/mandelbrot mandelbrot.png 4000x3000 -1.20,0.35 -1,0.20
</span></span></span><span class="line"><span class="cl"><span class="go">    3.78s user 0.04s system 99% cpu 3.841 total
</span></span></span></code></pre></div></div>
</div>
<p>You can see the computation took about four seconds, but as modern computers have multiple processor cores, performance could be significantly improved by utilizing concurrency. This presents a great opportunity to explore Zig’s concurrency features to enhance the program’s efficiency.</p>
<h2 id="speeding-things-up-with-concurrency">
Speeding things up with concurrency
<a href="#speeding-things-up-with-concurrency" class="heading-anchor" aria-label="Anchor link for: Speeding things up with concurrency">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The goal is to leverage concurrency to speed up the rendering of the Mandelbrot set by dividing the image into sections that can be processed in parallel. For simplicity, you’ll split the image into horizontal bands, as shown in Figure 3.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/zig/exploring-ziglang-with-mandelbrot-set/image-divided-into-bands_hu_c65ee4504cb96840.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="635">
            <source
                    srcset="https://rdiachenko.com/posts/zig/exploring-ziglang-with-mandelbrot-set/image-divided-into-bands_hu_f58bc45c1cef5fc5.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="907">
            <img
                    src="https://rdiachenko.com/posts/zig/exploring-ziglang-with-mandelbrot-set/image-divided-into-bands_hu_f58bc45c1cef5fc5.webp"
                    alt="Image Divided into Sections for Parallel Processing"
                    width="1200"
                    height="907"
                    loading="lazy">
        </picture><figcaption><small>Figure 3. Image Divided into Sections for Parallel Processing</small></figcaption></figure>
<p>Here&rsquo;s the updated code which replaces this line <code>render(pixels, imgSize, topLeft, bottomRight)</code> with the following in the <code>main</code> function:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="c1">// render(pixels, imgSize, topLeft, bottomRight)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Calculate the number of rows each thread should process based on CPU count.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="w"> </span><span class="n">threadCount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">Thread</span><span class="p">.</span><span class="nf">getCpuCount</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kr">var</span><span class="w"> </span><span class="n">threads</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">allocator</span><span class="p">.</span><span class="nf">alloc</span><span class="p">(</span><span class="n">std</span><span class="p">.</span><span class="n">Thread</span><span class="p">,</span><span class="w"> </span><span class="n">threadCount</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="n">allocator</span><span class="p">.</span><span class="nf">free</span><span class="p">(</span><span class="n">threads</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="w"> </span><span class="n">rowsPerBand</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">imgSize</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">threadCount</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Create and start threads, each processing a segment of the image.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="n">threadCount</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="n">i</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Determine the portion of the image array this thread will handle.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">band</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pixels</span><span class="p">[</span><span class="n">i</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">rowsPerBand</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">imgSize</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="o">..</span><span class="w"> </span><span class="nb">@min</span><span class="p">((</span><span class="n">i</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">*</span><span class="w"> </span><span class="n">rowsPerBand</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">imgSize</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="w"> </span><span class="n">pixels</span><span class="p">.</span><span class="n">len</span><span class="p">)];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">top</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">rowsPerBand</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">band</span><span class="p">.</span><span class="n">len</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">imgSize</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">bandSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">.{</span><span class="w"> </span><span class="n">imgSize</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">bandTopLeft</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">pixelToPoint</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">imgSize</span><span class="p">,</span><span class="w"> </span><span class="p">.{</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="n">top</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="n">topLeft</span><span class="p">,</span><span class="w"> </span><span class="n">bottomRight</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">bandBottomRight</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">pixelToPoint</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">imgSize</span><span class="p">,</span><span class="w"> </span><span class="p">.{</span><span class="w"> </span><span class="n">imgSize</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="w"> </span><span class="n">top</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="n">topLeft</span><span class="p">,</span><span class="w"> </span><span class="n">bottomRight</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Spawn a new thread to process this part of the image.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">threads</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">Thread</span><span class="p">.</span><span class="nf">spawn</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.{},</span><span class="w"> </span><span class="n">render</span><span class="p">,</span><span class="w"> </span><span class="p">.{</span><span class="w"> </span><span class="n">band</span><span class="p">,</span><span class="w"> </span><span class="n">bandSize</span><span class="p">,</span><span class="w"> </span><span class="n">bandTopLeft</span><span class="p">,</span><span class="w"> </span><span class="n">bandBottomRight</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Ensure all threads complete their tasks.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">threads</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="n">thread</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">thread</span><span class="p">.</span><span class="nf">join</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>This code snippet starts by determining how many CPU cores are available using <code>std.Thread.getCpuCount()</code> and allocates a corresponding number of threads. It then divides the image into horizontal bands, assigning each band to a different thread for processing.</p>
<p>Each thread runs a <code>render</code> function tailored to process just its assigned segment. After starting all threads, the main thread waits for each to finish by calling <code>join()</code> on them.</p>
<p>Build and run the program:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> zig build -Doptimize<span class="o">=</span>ReleaseFast --summary all
</span></span><span class="line"><span class="cl"><span class="go">Build Summary: 3/3 steps succeeded
</span></span></span><span class="line"><span class="cl"><span class="go">install success
</span></span></span><span class="line"><span class="cl"><span class="go">+- install mandelbrot success
</span></span></span><span class="line"><span class="cl"><span class="go">   +- compile exe mandelbrot ReleaseFast native success 8s MaxRSS:440M
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">time</span> ./zig-out/bin/mandelbrot mandelbrot.png 4000x3000 -1.20,0.35 -1,0.20
</span></span><span class="line"><span class="cl"><span class="go">./zig-out/bin/mandelbrot mandelbrot.png 4000x3000 -1.20,0.35 -1,0.20
</span></span></span><span class="line"><span class="cl"><span class="go">    3.89s user 0.03s system 359% cpu 1.094 total
</span></span></span></code></pre></div></div>
</div>
<p>The provided timings show that the program now completes in about 1.1 seconds, which is a significant improvement. There are ways to optimise it even further, but I&rsquo;ll leave it for you to explore.</p>
<p>The latest source code can be found in the 
<a href="https://github.com/rdiachenko/rd-blog/tree/main/mandelbrot" target="_blank" rel="nofollow noopener">rd-blog repository</a>
.</p>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Great job if you’ve reached this point! Here’s a recap of what you’ve briefly gone through:</p>
<ul>
<li>Installation process and dependency management</li>
<li>Zig functions and structures</li>
<li>Optional values, generics, and pattern matching</li>
<li>Error handling and unit testing</li>
<li>IO and concurrency</li>
<li>Building and running the final executable</li>
</ul>
<p>To solidify the knowledge you’ve gained from the Mandelbrot set exercise, I recommend exploring 
<a href="https://zig.guide/" target="_blank" rel="nofollow noopener">the official Zig guide</a>
 next. Try to tweak and enhance the Mandelbrot program even further.</p>
<p>Speaking of Zig, did you know there’s a fun backstory to how it got its name? 
<a href="https://gist.github.com/andrewrk/73742bf4b8ed795c85ce" target="_blank" rel="nofollow noopener">This Python script</a>
 was created to randomly generate four-letter words starting with ‘Z’ followed by a vowel. Although the intention was to pick a four-letter word, the three-letter word “Zig” ended up catching everyone’s attention and was ultimately chosen.</p>
]]></content:encoded></item><item><title>A Mind Map of Core Machine Learning Concepts</title><link>https://rdiachenko.com/posts/ml/machine-learning-concepts/</link><pubDate>Wed, 27 Mar 2024 09:21:58 +0000</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/ml/machine-learning-concepts/</guid><description>A mind map that breaks down the core ideas of machine learning, including types of learning, core techniques, and commonly used tools.</description><content:encoded><![CDATA[<p>
<a href="https://en.wikipedia.org/wiki/Machine_learning" target="_blank" rel="nofollow noopener">Machine Learning (ML)</a>
 is one of the biggest drivers of change in tech today. It powers everything from better customer experiences to automating boring, repetitive work.</p>
<p>Here&rsquo;s a mind map that shows the main building blocks of ML. Below it, I walk through the core ideas and techniques in simple terms to make this broad field a bit less confusing.</p>
<p>If you are new to ML, think of this as a roadmap, a quick way to see the landscape before diving deeper.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/machine-learning-mind-map_hu_e18634b8fe7cd60b.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="720">
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/machine-learning-mind-map_hu_67cb3e4319e19537.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="1028">
            <img
                    src="https://rdiachenko.com/posts/ml/machine-learning-concepts/machine-learning-mind-map_hu_67cb3e4319e19537.webp"
                    alt="Machine Learning Mind Map"
                    width="1200"
                    height="1028"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Machine Learning Mind Map</small></figcaption></figure>
<h2 id="supervised-learning-teaching-computers-to-predict">
Supervised learning: teaching computers to predict
<a href="#supervised-learning-teaching-computers-to-predict" class="heading-anchor" aria-label="Anchor link for: Supervised learning: teaching computers to predict">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/supervised-learning-mind-map_hu_c0ca0cc29c0dbd0d.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="231">
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/supervised-learning-mind-map_hu_ecc3ef4e0db4d47a.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="330">
            <img
                    src="https://rdiachenko.com/posts/ml/machine-learning-concepts/supervised-learning-mind-map_hu_ecc3ef4e0db4d47a.webp"
                    alt="Supervised Learning"
                    width="1200"
                    height="330"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. Supervised Learning</small></figcaption></figure>
<p>Supervised learning is one of the core ideas in ML. The model learns from labeled data, where the answer is already known, and then uses that knowledge to make predictions on new data.</p>
<p>The two main approaches are <strong>Regression</strong> and <strong>Classification</strong>.</p>
<h3 id="regression-predicting-continuous-values">
Regression: predicting continuous values
<a href="#regression-predicting-continuous-values" class="heading-anchor" aria-label="Anchor link for: Regression: predicting continuous values">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p><strong>Linear Regression</strong> and <strong>Polynomial Regression</strong> are used to predict continuous values.</p>
<p>For example, real estate companies apply Linear Regression to forecast house prices based on size, location, and amenities. Agricultural researchers use Polynomial Regression to estimate crop yields under different environmental conditions. These relationships are often nonlinear, which is why Polynomial Regression can be more effective.</p>
<p>In short, regression is about predicting numbers.</p>
<h3 id="classification-sorting-data-into-categories">
Classification: sorting data into categories
<a href="#classification-sorting-data-into-categories" class="heading-anchor" aria-label="Anchor link for: Classification: sorting data into categories">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Classification assigns data points to categories. Common methods include <strong>Logistic Regression</strong>, <strong>Decision Trees</strong>, <strong>Support Vector Machines (SVM)</strong>, and <strong>Random Forests</strong>.</p>
<p>Logistic Regression helps in medical diagnostics by classifying test results as positive or negative. Decision Trees are used in customer service to route inquiries to the right department. SVMs power handwriting recognition, distinguishing letters and numbers. Random Forests are widely used in banking to evaluate loan risk and improve predictive accuracy.</p>
<p>In short, classification is about predicting categories.</p>
<h2 id="unsupervised-learning-discovering-hidden-patterns">
Unsupervised learning: discovering hidden patterns
<a href="#unsupervised-learning-discovering-hidden-patterns" class="heading-anchor" aria-label="Anchor link for: Unsupervised learning: discovering hidden patterns">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/unsupervised-learning-mind-map_hu_cfb3deae7b057e85.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="199">
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/unsupervised-learning-mind-map_hu_710d3cf69974adee.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="285">
            <img
                    src="https://rdiachenko.com/posts/ml/machine-learning-concepts/unsupervised-learning-mind-map_hu_710d3cf69974adee.webp"
                    alt="Unsupervised Learning"
                    width="1200"
                    height="285"
                    loading="lazy">
        </picture><figcaption><small>Figure 3. Unsupervised Learning</small></figcaption></figure>
<p>Unsupervised Learning helps models find structure in unlabeled data. The main techniques are <strong>Clustering</strong>, <strong>Association</strong>, and <strong>Dimensionality Reduction</strong>.</p>
<h3 id="clustering-grouping-similar-items">
Clustering: grouping similar items
<a href="#clustering-grouping-similar-items" class="heading-anchor" aria-label="Anchor link for: Clustering: grouping similar items">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Clustering groups together items that share similarities. Two widely used approaches are <strong>K-Means</strong> and <strong>Hierarchical Clustering</strong>.</p>
<p>Marketing teams often use K-Means to segment customers by purchasing behavior, which allows them to create targeted campaigns for different groups. K-Means also has practical applications in image compression, as I explored in this post on 

            
        
    <a href="https://rdiachenko.com/posts/ml/k-means-image-compression/">K-Means image compression</a>
.</p>
<p>Hierarchical Clustering is frequently used in genomics to group genes with similar expression patterns, which can reveal functional relationships.</p>
<p>In short, clustering is about finding natural groupings in data.</p>
<h3 id="association-finding-variable-relationships">
Association: finding variable relationships
<a href="#association-finding-variable-relationships" class="heading-anchor" aria-label="Anchor link for: Association: finding variable relationships">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Association techniques uncover hidden relationships between variables. Two common algorithms are <strong>Apriori</strong> and <strong>Eclat</strong>.</p>
<p>The Apriori algorithm might reveal that bread and milk are often purchased together. A grocery store can use this insight to design product placements or bundle promotions to boost sales.</p>
<p>Eclat works in a similar way. An online retailer might discover that customers who buy smartphones also tend to purchase cases and screen protectors. This insight can improve recommendation systems, encouraging cross-selling.</p>
<p>In short, association learning finds the &ldquo;items that go together&rdquo; in data.</p>
<h3 id="dimensionality-reduction-simplifying-complex-data">
Dimensionality reduction: simplifying complex data
<a href="#dimensionality-reduction-simplifying-complex-data" class="heading-anchor" aria-label="Anchor link for: Dimensionality reduction: simplifying complex data">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>Dimensionality Reduction reduces the number of features in a dataset while preserving its core structure. Two popular methods are <strong>Principal Component Analysis (PCA)</strong> and <strong>Singular Value Decomposition (SVD)</strong>.</p>
<p>Finance teams use PCA to simplify complex stock market data, highlighting the most influential patterns and making it easier to analyze trends.</p>
<p>SVD plays a key role in natural language processing. It processes large term-document matrices to uncover latent semantic structures, which helps search engines better understand user intent and return more relevant results, even when exact keywords don&rsquo;t appear.</p>
<p>In short, dimensionality reduction helps make complex data easier to work with and visualize.</p>
<h2 id="reinforcement-learning-learning-through-interaction">
Reinforcement learning: learning through interaction
<a href="#reinforcement-learning-learning-through-interaction" class="heading-anchor" aria-label="Anchor link for: Reinforcement learning: learning through interaction">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/reinforcement-learning-mind-map_hu_ce160e746e534536.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="150">
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/reinforcement-learning-mind-map_hu_6bf3c8910ed8ed57.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="214">
            <img
                    src="https://rdiachenko.com/posts/ml/machine-learning-concepts/reinforcement-learning-mind-map_hu_6bf3c8910ed8ed57.webp"
                    alt="Reinforcement Learning"
                    width="1200"
                    height="214"
                    loading="lazy">
        </picture><figcaption><small>Figure 4. Reinforcement Learning</small></figcaption></figure>
<p>Reinforcement Learning (RL) is about learning by doing. A model interacts with an environment, takes actions, and receives rewards or penalties as feedback. Over time, it learns strategies that maximize rewards.</p>
<p><strong>Model-based RL</strong> is often used in robotics, where robots need to plan movements and adapt to their surroundings.</p>
<p><strong>Model-free RL</strong> methods, such as <strong>Q-Learning</strong> and <strong>Deep Q Networks (DQN)</strong>, focus on learning directly from experience. These techniques have powered breakthroughs in game AI, enabling systems to master complex games like chess and Go without human guidance.</p>
<p>In short, RL is trial-and-error learning guided by rewards.</p>
<h2 id="deep-learning-learning-from-complex-data">
Deep learning: learning from complex data
<a href="#deep-learning-learning-from-complex-data" class="heading-anchor" aria-label="Anchor link for: Deep learning: learning from complex data">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/deep-learning-mind-map_hu_615644ba3445b506.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="166">
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/deep-learning-mind-map_hu_a164d4df10169f91.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="238">
            <img
                    src="https://rdiachenko.com/posts/ml/machine-learning-concepts/deep-learning-mind-map_hu_a164d4df10169f91.webp"
                    alt="Deep Learning"
                    width="1200"
                    height="238"
                    loading="lazy">
        </picture><figcaption><small>Figure 5. Deep Learning</small></figcaption></figure>
<p>Deep Learning is a subset of Machine Learning that uses neural networks to process large and complex datasets. Different architectures specialize in different types of problems:</p>
<ul>
<li><strong>Convolutional Neural Networks (CNNs)</strong> excel at image recognition and processing.</li>
<li><strong>Recurrent Neural Networks (RNNs)</strong>, including <strong>Long Short-Term Memory (LSTM)</strong> and <strong>Gated Recurrent Units (GRU)</strong>, are designed for sequential data like text or speech.</li>
<li><strong>Transformers</strong> have revolutionized natural language processing and now power many state-of-the-art AI systems.</li>
</ul>
<p>These models are everywhere in practice. Neural networks enable voice recognition in virtual assistants. CNNs improve disease diagnosis from medical scans. RNNs drive predictive text, helping your phone suggest the next word as you type. And Transformers set new standards in machine translation, making real-time multilingual communication more accessible than ever.</p>
<p>In short, Deep Learning leverages specialized neural network architectures to tackle vision, language, and sequence problems at scale.</p>
<h2 id="core-components-of-machine-learning">
Core components of machine learning
<a href="#core-components-of-machine-learning" class="heading-anchor" aria-label="Anchor link for: Core components of machine learning">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>No Machine Learning model works in isolation. To be effective, every model relies on core components that shape how it is built, evaluated, and improved. Three of the most important are <strong>Evaluation Metrics</strong>, <strong>Feature Engineering &amp; Selection</strong>, and <strong>Optimization Techniques</strong>.</p>
<h3 id="evaluation-metrics-measuring-model-performance">
Evaluation metrics: measuring model performance
<a href="#evaluation-metrics-measuring-model-performance" class="heading-anchor" aria-label="Anchor link for: Evaluation metrics: measuring model performance">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/evaluation-metrics-mind-map_hu_4d739afb6c2f544.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="196">
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/evaluation-metrics-mind-map_hu_aebbce4db36e4d7a.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="280">
            <img
                    src="https://rdiachenko.com/posts/ml/machine-learning-concepts/evaluation-metrics-mind-map_hu_aebbce4db36e4d7a.webp"
                    alt="Evaluation Metrics"
                    width="1200"
                    height="280"
                    loading="lazy">
        </picture><figcaption><small>Figure 6. Evaluation Metrics</small></figcaption></figure>
<p>Evaluation metrics give a concrete way to measure how well a model performs.</p>
<p>For regression tasks, metrics such as <strong>Mean Squared Error (MSE)</strong> and <strong>Root Mean Squared Error (RMSE)</strong> capture the difference between predicted and actual values. For example, a real estate company might use RMSE to fine-tune its pricing models and improve accuracy.</p>
<p>For classification tasks, common metrics include <strong>Accuracy</strong>, <strong>Precision</strong>, <strong>Recall</strong>, <strong>F1-Score</strong>, and <strong>ROC-AUC (Receiver Operating Characteristic and Area Under the Curve)</strong>. In spam detection, for instance, accuracy ensures most spam is flagged correctly, while precision and recall help balance catching spam without mislabeling legitimate emails.</p>
<p>In short, Metrics turn abstract model performance into measurable outcomes, guiding improvements and helping avoid blind spots.</p>
<h3 id="feature-engineering-shaping-the-right-inputs">
Feature engineering: shaping the right inputs
<a href="#feature-engineering-shaping-the-right-inputs" class="heading-anchor" aria-label="Anchor link for: Feature engineering: shaping the right inputs">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/feature-eng-selection-mind-map_hu_e02470835035d9f9.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="160">
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/feature-eng-selection-mind-map_hu_8ee3f9054fafe919.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="228">
            <img
                    src="https://rdiachenko.com/posts/ml/machine-learning-concepts/feature-eng-selection-mind-map_hu_8ee3f9054fafe919.webp"
                    alt="Feature Engineering and Selection"
                    width="1200"
                    height="228"
                    loading="lazy">
        </picture><figcaption><small>Figure 7. Feature Engineering and Selection</small></figcaption></figure>
<p>Feature engineering is the process of choosing and transforming data attributes so that a model can learn effectively.</p>
<p><strong>Feature Extraction</strong> is often used in text analysis, for example identifying words or phrases that signal sentiment. <strong>Feature Importance</strong> highlights the variables that matter most, such as the factors driving stock market trends.</p>
<p><strong>Feature Scaling</strong> techniques like <strong>Min-Max Normalization</strong> and <strong>Standardization (Z-score)</strong> ensure that inputs are on a consistent scale. This is especially important for neural networks, where large differences in input ranges can slow or distort learning.</p>
<p>In short, feature engineering and selection shape the inputs that ultimately determine how well a model performs.</p>
<h3 id="optimization-techniques-training-smarter-models">
Optimization techniques: training smarter models
<a href="#optimization-techniques-training-smarter-models" class="heading-anchor" aria-label="Anchor link for: Optimization techniques: training smarter models">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/opt-techniques-mind-map_hu_4ff22bc6e22163e7.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="160">
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/opt-techniques-mind-map_hu_fc9477155e7d364.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="229">
            <img
                    src="https://rdiachenko.com/posts/ml/machine-learning-concepts/opt-techniques-mind-map_hu_fc9477155e7d364.webp"
                    alt="Optimization Techniques"
                    width="1200"
                    height="229"
                    loading="lazy">
        </picture><figcaption><small>Figure 8. Optimization Techniques</small></figcaption></figure>
<p>Optimization techniques adjust a model&rsquo;s parameters to improve performance, usually by minimizing or maximizing a cost function.</p>
<p>The most widely used method is <strong>Gradient Descent</strong>, which iteratively reduces prediction errors by updating parameters in the direction of steepest improvement.</p>
<p>Variants such as <strong>Stochastic Gradient Descent (SGD)</strong> and <strong>Adam Optimizer</strong> improve efficiency on large or complex datasets. They are widely applied in domains like consumer behavior prediction or logistics optimization, where models need to learn quickly from vast amounts of data.</p>
<p>In short, optimization techniques are what make training practical, allowing models to learn effectively from real-world data.</p>
<h2 id="ml-tools-and-frameworks">
ML tools and frameworks
<a href="#ml-tools-and-frameworks" class="heading-anchor" aria-label="Anchor link for: ML tools and frameworks">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/framework-tools-mind-map-cover_hu_44d1827f6a746ff7.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="279">
            <source
                    srcset="https://rdiachenko.com/posts/ml/machine-learning-concepts/framework-tools-mind-map-cover_hu_b882e1bfa798e8d5.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="399">
            <img
                    src="https://rdiachenko.com/posts/ml/machine-learning-concepts/framework-tools-mind-map-cover_hu_b882e1bfa798e8d5.webp"
                    alt="Frameworks and Tools"
                    width="1200"
                    height="399"
                    loading="lazy">
        </picture><figcaption><small>Figure 9. Frameworks and Tools</small></figcaption></figure>
<p>Frameworks and libraries are what make Machine Learning practical. Tools such as <strong>TensorFlow</strong>, <strong>PyTorch</strong>, <strong>Scikit-learn</strong>, and <strong>Keras</strong> provide ready-made building blocks for model development, training, and deployment. They lower the barrier to entry and allow both researchers and engineers to focus on experimentation and application rather than reinventing the basics.</p>
<p>In short, these tools are the backbone of modern ML work, making advanced analytics accessible to a much wider audience.</p>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Machine Learning is powerful because it learns from data. That ability makes it one of the cornerstones of modern AI and a driver of real-world change, from smarter healthcare to better search engines.</p>
<p>If you know the foundations, you can go a long way.</p>
<p>You can download the full mind map in PDF format from the 
<a href="https://rdiachenko.gumroad.com/l/ml-mind-map" target="_blank" rel="nofollow noopener">Machine Learning Mind Map</a>
 page.</p>
]]></content:encoded></item><item><title>Leaky Bucket Rate Limiting and its Queue-Based Variant</title><link>https://rdiachenko.com/posts/arch/rate-limiting/leaky-bucket-algorithm/</link><pubDate>Sun, 24 Mar 2024 15:51:15 +0000</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/arch/rate-limiting/leaky-bucket-algorithm/</guid><description>A closer look at the Leaky Bucket algorithm, how it works, when to use the queue-based variant, and how to implement it in Java.</description><content:encoded><![CDATA[<p>The Leaky Bucket 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/">Rate Limiting</a>
 algorithm utilizes a FIFO (First In, First Out) queue with a fixed capacity to manage request rates, ensuring that requests are processed at a constant rate regardless of traffic spikes. It contrasts with the 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/">Token Bucket algorithm</a>
 by not allowing for bursty traffic allowances based on token accumulation but instead focusing strictly on maintaining a constant output rate. When the queue is full, incoming requests are denied until space becomes available. This approach effectively moderates the flow of requests, preventing server overload by maintaining a steady rate of processing, akin to a bucket leaking water at a fixed rate to avoid overflow.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/leaky-bucket-algorithm/leaky-bucket-algorithm-cover_hu_bfc7538f1028bb69.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="374">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/leaky-bucket-algorithm/leaky-bucket-algorithm-cover_hu_dc5c9f946a42997.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="535">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/leaky-bucket-algorithm/leaky-bucket-algorithm-cover_hu_dc5c9f946a42997.webp"
                    alt="Leaky Bucket Algorithm in Action"
                    width="1200"
                    height="535"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Leaky Bucket Algorithm in Action</small></figcaption></figure>
<p>The above diagram shows an algorithm with a rate limit of one request per second and a bucket capacity for two requests. Initially, when request A arrives, it is added to an empty queue and processed. With space for one more, request B is also added, filling the bucket to capacity. Request C is rejected because the bucket is full and hasn&rsquo;t leaked yet. After one second, a leak removes request A, making room for request D. By the time request E arrives, the bucket is empty due to the leaks for requests B and D, allowing E to be accepted.</p>
<h2 id="implementing-the-leaky-bucket-rate-limiter">
Implementing the leaky bucket rate limiter
<a href="#implementing-the-leaky-bucket-rate-limiter" class="heading-anchor" aria-label="Anchor link for: Implementing the leaky bucket rate limiter">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>This following implementation covers the pure Leaky Bucket Rate Limiter, a no-queue-based implementation focusing on guaranteed rate limiting. In this model, the bucket&rsquo;s capacity indicates the maximum number of requests that can be &ldquo;held&rdquo; at any moment. If a request arrives when the bucket is full (indicating the water level has reached the bucket&rsquo;s capacity), the request is rejected. This strategy ensures a smooth and predictable output rate, although it may prove restrictive for handling bursty traffic.</p>
<p>The algorithm initializes with key properties as follows:</p>
<ul>
<li>The maximum number of requests a user can make within a specified period before being limited.</li>
<li>The time period during which requests are evaluated for limiting.</li>
<li>The number of requests permitted to leak out (be processed) per period.</li>
</ul>
<p>As shown in the flow diagram below, each incoming request triggers an attempt to leak the bucket, with the number of leaks varying based on the time elapsed since the last leak. If the bucket has enough capacity for the incoming request, the water level in the bucket rises, and the request is processed successfully. Otherwise, the request is either rejected or delayed until enough leakage occurs to lower the water level sufficiently.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/leaky-bucket-algorithm/leaky-bucket-algorithm-flow-diagram_hu_c3d5b468b1e13cd.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="703">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/leaky-bucket-algorithm/leaky-bucket-algorithm-flow-diagram_hu_5e259c37a0400885.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="1004">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/leaky-bucket-algorithm/leaky-bucket-algorithm-flow-diagram_hu_5e259c37a0400885.webp"
                    alt="Flow Diagram for Leaky Bucket Algorithm"
                    width="1200"
                    height="1004"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. Flow Diagram for Leaky Bucket Algorithm</small></figcaption></figure>
<p>To track user-specific buckets, you can use a dictionary that maps each user ID to a pair consisting of the last leak timestamp and the current water level, denoted as <code>Map&lt;String, LeakBucket&gt;</code>.</p>
<p>Below is a Java implementation of the Leaky Bucket Rate Limiting algorithm:</p>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="69">
        <div class="code-header">
                <span class="code-filename">LeakyBucketRateLimiter.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Duration</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.HashMap</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.Map</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">LeakyBucketRateLimiter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">capacity</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Duration</span><span class="w"> </span><span class="n">period</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">leaksPerPeriod</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"> </span><span class="n">LeakyBucket</span><span class="o">&gt;</span><span class="w"> </span><span class="n">userLeakyBucket</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">HashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">public</span><span class="w"> </span><span class="nf">LeakyBucketRateLimiter</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">capacity</span><span class="p">,</span><span class="w"> </span><span class="n">Duration</span><span class="w"> </span><span class="n">period</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">leaksPerPeriod</span><span class="p">,</span><span class="w"> </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">capacity</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">capacity</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">period</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">period</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">leaksPerPeriod</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">leaksPerPeriod</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">clock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">public</span><span class="w"> </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">allowed</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">userId</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">LeakyBucket</span><span class="w"> </span><span class="n">bucket</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">userLeakyBucket</span><span class="p">.</span><span class="na">computeIfAbsent</span><span class="p">(</span><span class="n">userId</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">k</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">LeakyBucket</span><span class="p">(</span><span class="n">clock</span><span class="p">.</span><span class="na">millis</span><span class="p">(),</span><span class="w"> </span><span class="n">0</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">bucket</span><span class="p">.</span><span class="na">leak</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">bucket</span><span class="p">.</span><span class="na">processed</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">class</span> <span class="nc">LeakyBucket</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">leakTimestamp</span><span class="p">;</span><span class="w"> </span><span class="c1">// Timestamp of the last leak.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">waterLevel</span><span class="p">;</span><span class="w"> </span><span class="c1">// Current water level represents the number of pending requests.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">LeakyBucket</span><span class="p">(</span><span class="kt">long</span><span class="w"> </span><span class="n">leakTimestamp</span><span class="p">,</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">waterLevel</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">this</span><span class="p">.</span><span class="na">leakTimestamp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">leakTimestamp</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">this</span><span class="p">.</span><span class="na">waterLevel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">waterLevel</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * Simulates the leaking of requests over time. This method adjusts the water level
</span></span></span><span class="line"><span class="cl"><span class="cm">     * based on the elapsed time since the last leak, applying the defined leak rate.
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">void</span><span class="w"> </span><span class="nf">leak</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kt">long</span><span class="w"> </span><span class="n">now</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">clock</span><span class="p">.</span><span class="na">millis</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kt">long</span><span class="w"> </span><span class="n">elapsedTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">now</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">leakTimestamp</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kt">long</span><span class="w"> </span><span class="n">elapsedPeriods</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">elapsedTime</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">period</span><span class="p">.</span><span class="na">toMillis</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kt">long</span><span class="w"> </span><span class="n">leaks</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">elapsedPeriods</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">leaksPerPeriod</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">leaks</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">waterLevel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Math</span><span class="p">.</span><span class="na">max</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"> </span><span class="n">waterLevel</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">leaks</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">leakTimestamp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">now</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * Attempts to process a request by incrementing the water level if under capacity.
</span></span></span><span class="line"><span class="cl"><span class="cm">     *
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @return true if the request is processed (under capacity), false if the bucket is full.
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">processed</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">waterLevel</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">capacity</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">++</span><span class="n">waterLevel</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="69">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 69 lines</span>
    </div>
</div>
<p>The <code>allowed</code> method determines if a user&rsquo;s request falls within the specified limits. For a new user, an empty bucket is created with a zero water level, otherwise, existing bucket is retrieved. The bucket is then leaks based on the elapsed time since the last leak. The method attempts to increase the water level in the bucket to process the current request, allowing the request to pass if a water level doesn&rsquo;t exceed the bucket&rsquo;s capacity, otherwise, rejecting it.</p>
<h2 id="simulating-requests-from-multiple-users-with-tests">
Simulating requests from multiple users with tests
<a href="#simulating-requests-from-multiple-users-with-tests" class="heading-anchor" aria-label="Anchor link for: Simulating requests from multiple users with tests">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The following Java test demonstrates the usage of this limiter:</p>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="63">
        <div class="code-header">
                <span class="code-filename">LeakyBucketRateLimiterTest.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Test</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Duration</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Assertions.assertFalse</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Assertions.assertTrue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.mockito.Mockito.mock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.mockito.Mockito.when</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">LeakyBucketRateLimiterTest</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">BOB</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;Bob&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">ALICE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;Alice&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nd">@Test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">void</span><span class="w"> </span><span class="nf">allowed_requestsFromMultipleUsers_ensuresIndividualRateLimiters</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">mock</span><span class="p">(</span><span class="n">Clock</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">when</span><span class="p">(</span><span class="n">clock</span><span class="p">.</span><span class="na">millis</span><span class="p">()).</span><span class="na">thenReturn</span><span class="p">(</span><span class="n">0L</span><span class="p">,</span><span class="w"> </span><span class="n">0L</span><span class="p">,</span><span class="w"> </span><span class="n">999L</span><span class="p">,</span><span class="w"> </span><span class="n">1000L</span><span class="p">,</span><span class="w"> </span><span class="n">1000L</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">1000L</span><span class="p">,</span><span class="w"> </span><span class="n">1001L</span><span class="p">,</span><span class="w"> </span><span class="n">2001L</span><span class="p">,</span><span class="w"> </span><span class="n">2001L</span><span class="p">,</span><span class="w"> </span><span class="n">2001L</span><span class="p">,</span><span class="w"> </span><span class="n">3002L</span><span class="p">,</span><span class="w"> </span><span class="n">3003L</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">LeakyBucketRateLimiter</span><span class="w"> </span><span class="n">limiter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">LeakyBucketRateLimiter</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"> </span><span class="n">Duration</span><span class="p">.</span><span class="na">ofSeconds</span><span class="p">(</span><span class="n">2</span><span class="p">),</span><span class="w"> </span><span class="n">1</span><span class="p">,</span><span class="w"> </span><span class="n">clock</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 0 seconds passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 1 at timestamp=0 must pass&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 2 at timestamp=999 must not be allowed,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket has reached its max capacity and no leaks occurred&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 1 second passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Alice&#39;s request 3 at timestamp=1000 must not be allowed,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket has reached its max capacity and no leaks occurred&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">ALICE</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Alice&#39;s request 1 at timestamp=1000 must pass&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">ALICE</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Alice&#39;s request 2 at timestamp=1001 must not be allowed,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket has reached its max capacity and no leaks occurred&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 2 seconds passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">ALICE</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Alice&#39;s request 3 at timestamp=2001 must not be allowed,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket has reached its max capacity and no leaks occurred&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 4 at timestamp=2001 must pass,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket leaked 1 request since the last leak timestamp=0&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; making capacity for 1 request&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 5 at timestamp=2001 must not be allowed,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket has reached its max capacity and no leaks occurred&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 3 seconds passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">ALICE</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Alice&#39;s request 4 at timestamp=3002 must pass,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket leaked 1 request since the last leak timestamp=1000&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; making capacity for 1 request&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">ALICE</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Alice&#39;s request 5 at timestamp=3003 must not be allowed,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket has reached its max capacity and no leaks occurred&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="63">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 63 lines</span>
    </div>
</div>
<p>This test simulates scenarios for different users and varying time intervals to ensure the rate limiter functions as expected. More tests and latest source code can be found in the 
<a href="https://github.com/rdiachenko/rd-blog/tree/main/rate-limiting" target="_blank" rel="nofollow noopener">rd-blog repository</a>
.</p>
<p>For additional implementations and insights, you can explore open-source resources such as:</p>
<ul>
<li>
<a href="https://github.com/mailgun/gubernator" target="_blank" rel="nofollow noopener">High Performance Rate Limiting MicroService and Library</a>
</li>
<li>
<a href="https://nginx.org/en/docs/http/ngx_http_limit_req_module.html" target="_blank" rel="nofollow noopener">The ngx_http_limit_req_module uses leaky bucket method</a>
</li>
</ul>
<h2 id="leaky-bucket-algorithm-queue-based-approach">
Leaky bucket algorithm: queue-based approach
<a href="#leaky-bucket-algorithm-queue-based-approach" class="heading-anchor" aria-label="Anchor link for: Leaky bucket algorithm: queue-based approach">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>This alternative to the pure Leaky Bucket algorithm, commonly referred to as the Leaky Bucket with queue, includes a FIFO queue with fixed capacity and aims to better handle bursty traffic patterns.</p>
<p>Here&rsquo;s how it works:</p>
<ol>
<li>The queue acts as a buffer for incoming requests.</li>
<li>When a request arrives, it&rsquo;s added to the back of the queue.</li>
<li><strong>Periodically</strong>, the &ldquo;leak&rdquo; process removes and processes the request at the front of the queue at the configured leak rate.</li>
<li>If the queue is full when a new request arrives, that request is rejected.</li>
</ol>
<p>Both the pure and queue-based implementations are valid approaches to the Leaky Bucket algorithm. The choice between them depends on specific requirements:</p>
<ul>
<li>For prioritizing a strict and guaranteed rate limit, the pure Leaky Bucket might be more suitable.</li>
<li>To manage bursty traffic patterns while maintaining an average rate limit, the Leaky Bucket with queue offers a more adaptable solution.</li>
</ul>
<h2 id="trade-offs-and-design-considerations">
Trade-offs and design considerations
<a href="#trade-offs-and-design-considerations" class="heading-anchor" aria-label="Anchor link for: Trade-offs and design considerations">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<h3 id="pros">
Pros
<a href="#pros" class="heading-anchor" aria-label="Anchor link for: Pros">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p><strong>Simplicity</strong>: The leaky bucket algorithm is conceptually straightforward and relatively easy to implement making it a good choice for scenarios where complexity needs to be minimized.</p>
<p><strong>Data Flow Consistency</strong>: The leaky bucket enforces a strict output rate. Traffic is released at a constant pace, regardless of incoming bursts. This is ideal for situations where maintaining a smooth and predictable flow of requests is critical, such as preventing denial-of-service attacks or protecting servers from overload.</p>
<p><strong>Efficiency</strong>: Operating with a constant queue size, the leaky bucket algorithm is memory efficient. This efficiency is vital for maintaining a uniform flow of requests to the server without requiring significant additional memory resources, making it suitable for systems with limited memory.</p>
<h3 id="cons">
Cons
<a href="#cons" class="heading-anchor" aria-label="Anchor link for: Cons">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p><strong>Bursty Traffic</strong>: The algorithm&rsquo;s fixed capacity can struggle with sudden bursts of traffic, leading to potential request rejections if the burst exceeds the bucket&rsquo;s capacity. This limitation can affect applications that experience occasional spikes in demand.</p>
<p><strong>Fairness</strong>: A sudden influx of requests can quickly fill the bucket, leading to the starvation of new requests and uncertainty regarding the processing time for accepted requests.</p>
<p><strong>Underutilization</strong>: During periods of low traffic, the fixed rate processing can lead to underutilization of available bandwidth or server resources, as the system continues to process requests at the predetermined rate, regardless of actual demand, potentially leading to inefficiencies.</p>
<h2 id="common-use-cases">
Common use cases
<a href="#common-use-cases" class="heading-anchor" aria-label="Anchor link for: Common use cases">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The leaky bucket algorithm is widely utilized for its ability to efficiently handle network traffic, ensuring that data flows smoothly without overwhelming network capabilities. Primarily used in applications requiring consistent data transmission, such as video streaming and telecommunications, it plays a critical role in enhancing user experiences by avoiding network congestion. Its importance is especially recognized in environments where maintaining network performance and preventing disruptions due to data overload are crucial.</p>
<p>Moreover, the Leaky Bucket algorithm&rsquo;s adaptability is evident in its application across various fields, not just in network traffic management but also in controlling API rate limits. An example of its effective use is by Shopify, which employs the algorithm to regulate its API rate limits, as detailed in 
<a href="https://shopify.dev/docs/api/usage/rate-limits" target="_blank" rel="nofollow noopener">their developer documentation</a>
. This highlights the algorithm&rsquo;s ability to ensure equitable resource allocation and prevent resource overuse, underscoring its wide relevance and effectiveness in many technological areas.</p>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The Leaky Bucket Rate Limiting algorithm provides an effective way to manage network traffic, ensuring a consistent request processing rate to mitigate spikes and avoid server overload. While its simplicity and fixed output rate approach facilitate predictable request flows, ideal for handling variable traffic, this method comes with trade-offs. Specifically, its fairness limitation to prioritise less recent requests can lead to the starvation of new requests, and during periods of low traffic, it may not utilize available bandwidth fully, potentially leading to inefficiencies.</p>
]]></content:encoded></item><item><title>Token Bucket Rate Limiting with Interval and Greedy Refillers</title><link>https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/</link><pubDate>Sun, 25 Feb 2024 08:32:57 +0000</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/</guid><description>A hands-on look at the Token Bucket algorithm, how it handles bursty traffic, why refill strategy matters, and how to build it in Java.</description><content:encoded><![CDATA[<p>The Token Bucket 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/">Rate Limiting</a>
 algorithm implements rate limiting by maintaining a fixed-capacity bucket that holds tokens. Tokens are added at a consistent rate, and their count never exceeds the bucket&rsquo;s capacity. To process a request, the algorithm checks for sufficient tokens in the bucket, deducting the necessary amount for each request. If insufficient tokens are available, the request is rejected.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/token-bucket-algorithm-cover_hu_c8caaef750b95f75.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="403">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/token-bucket-algorithm-cover_hu_fc87f9d5c5fb1ea2.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="576">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/token-bucket-algorithm-cover_hu_fc87f9d5c5fb1ea2.webp"
                    alt="Token Bucket Algorithm in Action"
                    width="1200"
                    height="576"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Token Bucket Algorithm in Action</small></figcaption></figure>
<p>The above diagram illustrates the algorithm with a rate limit set to one request per second and a bucket capacity of two tokens. When request A arrives, the token bucket is initialized with one token, enabling request A to proceed successfully. Request B is rejected due to a lack of tokens in the bucket at that moment. Since no requests are made between the first and second seconds, the bucket accumulates two tokens. Consequently, requests C and D are allowed, with each consuming a token. Request E is rejected as the bucket has been run out of tokens.</p>
<h2 id="implementing-the-token-bucket-rate-limiter">
Implementing the token bucket rate limiter
<a href="#implementing-the-token-bucket-rate-limiter" class="heading-anchor" aria-label="Anchor link for: Implementing the token bucket rate limiter">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The Token Bucket Rate Limiting algorithm is initialized with the following key properties:</p>
<ul>
<li>The maximum number of tokens that the bucket can hold.</li>
<li>The period over which tokens are replenished within the bucket.</li>
<li>The number of tokens added to the bucket at each period.</li>
</ul>
<p>As shown in the flow diagram below, an attempt to refill the token bucket is made whenever a request arrives. The number of new tokens added depends on the time elapsed since the last refill. If the bucket contains a sufficient number of tokens, the necessary amount is deducted, and the request is processed successfully, otherwise, it is either rejected or postponed until enough tokens become available.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/token-bucket-algorithm-flow-diagram_hu_90af2fcf85bc8a93.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="703">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/token-bucket-algorithm-flow-diagram_hu_8e0e438284d2672d.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="1004">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/token-bucket-algorithm-flow-diagram_hu_8e0e438284d2672d.webp"
                    alt="Flow Diagram for Token Bucket Algorithm"
                    width="1200"
                    height="1004"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. Flow Diagram for Token Bucket Algorithm</small></figcaption></figure>
<p>To track user-specific buckets, you can use a dictionary that maps each user ID to a pair consisting of the last refill timestamp and the current token count, denoted as <code>Map&lt;String, TokenBucket&gt;</code>. Below is a Java implementation of the Token Bucket Rate Limiting algorithm:</p>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="69">
        <div class="code-header">
                <span class="code-filename">TokenBucketRateLimiter.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Duration</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.HashMap</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.Map</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">TokenBucketRateLimiter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">capacity</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Duration</span><span class="w"> </span><span class="n">period</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tokensPerPeriod</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"> </span><span class="n">TokenBucket</span><span class="o">&gt;</span><span class="w"> </span><span class="n">userTokenBucket</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">HashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">public</span><span class="w"> </span><span class="nf">TokenBucketRateLimiter</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">capacity</span><span class="p">,</span><span class="w"> </span><span class="n">Duration</span><span class="w"> </span><span class="n">period</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                                 </span><span class="kt">int</span><span class="w"> </span><span class="n">tokensPerPeriod</span><span class="p">,</span><span class="w"> </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">capacity</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">capacity</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">period</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">period</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">tokensPerPeriod</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">tokensPerPeriod</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">clock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">public</span><span class="w"> </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">allowed</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">userId</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Initialize an empty bucket for new users or retrieve existing one.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">TokenBucket</span><span class="w"> </span><span class="n">bucket</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">userTokenBucket</span><span class="p">.</span><span class="na">computeIfAbsent</span><span class="p">(</span><span class="n">userId</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">k</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">TokenBucket</span><span class="p">(</span><span class="n">clock</span><span class="p">.</span><span class="na">millis</span><span class="p">(),</span><span class="w"> </span><span class="n">tokensPerPeriod</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Refill the bucket with available tokens based on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// elapsed time since last refill.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">bucket</span><span class="p">.</span><span class="na">refill</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Allow this request if a token was available and consumed,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Otherwise, reject the request.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">bucket</span><span class="p">.</span><span class="na">consume</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">class</span> <span class="nc">TokenBucket</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">refillTimestamp</span><span class="p">;</span><span class="w"> </span><span class="c1">// Timestamp of the last refill.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">tokenCount</span><span class="p">;</span><span class="w"> </span><span class="c1">// Current number of tokens in the bucket.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">TokenBucket</span><span class="p">(</span><span class="kt">long</span><span class="w"> </span><span class="n">refillTimestamp</span><span class="p">,</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">tokenCount</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">this</span><span class="p">.</span><span class="na">refillTimestamp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">refillTimestamp</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">this</span><span class="p">.</span><span class="na">tokenCount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">tokenCount</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * Regenerates tokens at fixed intervals. Waits for the entire period
</span></span></span><span class="line"><span class="cl"><span class="cm">     * to elapse before regenerating the full amount of tokens
</span></span></span><span class="line"><span class="cl"><span class="cm">     * designated for that period.
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">refill</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kt">long</span><span class="w"> </span><span class="n">now</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">clock</span><span class="p">.</span><span class="na">millis</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kt">long</span><span class="w"> </span><span class="n">elapsedTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">now</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">refillTimestamp</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kt">long</span><span class="w"> </span><span class="n">elapsedPeriods</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">elapsedTime</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">period</span><span class="p">.</span><span class="na">toMillis</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kt">long</span><span class="w"> </span><span class="n">availableTokens</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">elapsedPeriods</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">tokensPerPeriod</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">tokenCount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Math</span><span class="p">.</span><span class="na">min</span><span class="p">(</span><span class="n">tokenCount</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">availableTokens</span><span class="p">,</span><span class="w"> </span><span class="n">capacity</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">refillTimestamp</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">elapsedPeriods</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">period</span><span class="p">.</span><span class="na">toMillis</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">consume</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">tokenCount</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">--</span><span class="n">tokenCount</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="69">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 69 lines</span>
    </div>
</div>
<p>The <code>allowed</code> method determines if a user&rsquo;s request falls within the specified limits. For a new user, an empty bucket is created with initial number of tokens, otherwise, existing bucket is retrieved. The bucket is then refilled with available tokens based on elapsed time since the last refill. The method attempts to consume a token from the bucket to process the current request, allowing the request to pass if a token was available, otherwise, rejecting it.</p>
<h2 id="simulating-bursty-traffic-with-tests">
Simulating bursty traffic with tests
<a href="#simulating-bursty-traffic-with-tests" class="heading-anchor" aria-label="Anchor link for: Simulating bursty traffic with tests">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The following Java tests demonstrates the usage of this limiter:</p>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="50">
        <div class="code-header">
                <span class="code-filename">TokenBucketRateLimiterTest.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Test</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Duration</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Assertions.assertFalse</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Assertions.assertTrue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.mockito.Mockito.mock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.mockito.Mockito.when</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">TokenBucketRateLimiterTest</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">BOB</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;Bob&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nd">@Test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">void</span><span class="w"> </span><span class="nf">allowed_burstyTraffic_acceptsAllAccumulatedRequestsWithinRateLimitThresholds</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">mock</span><span class="p">(</span><span class="n">Clock</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">when</span><span class="p">(</span><span class="n">clock</span><span class="p">.</span><span class="na">millis</span><span class="p">()).</span><span class="na">thenReturn</span><span class="p">(</span><span class="n">0L</span><span class="p">,</span><span class="w"> </span><span class="n">0L</span><span class="p">,</span><span class="w"> </span><span class="n">1L</span><span class="p">,</span><span class="w"> </span><span class="n">4001L</span><span class="p">,</span><span class="w"> </span><span class="n">4002L</span><span class="p">,</span><span class="w"> </span><span class="n">4003L</span><span class="p">,</span><span class="w"> </span><span class="n">4004L</span><span class="p">,</span><span class="w"> </span><span class="n">4005L</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">TokenBucketRateLimiter</span><span class="w"> </span><span class="n">limiter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">TokenBucketRateLimiter</span><span class="p">(</span><span class="n">4</span><span class="p">,</span><span class="w"> </span><span class="n">Duration</span><span class="p">.</span><span class="na">ofSeconds</span><span class="p">(</span><span class="n">1</span><span class="p">),</span><span class="w"> </span><span class="n">1</span><span class="p">,</span><span class="w"> </span><span class="n">clock</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 0 seconds passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 1 at timestamp=0 must pass,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket has 1 token available&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 2 at timestamp=1 must not be allowed,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket has 0 tokens available&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 4 seconds passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 3 at timestamp=4001 must pass,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket accumulated 4 tokens&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; since the last refill at timestamp=0&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; and now has 4 tokens available&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 4 at timestamp=4002 must pass,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket has 3 tokens available&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 5 at timestamp=4003 must pass,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket has 2 tokens available&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 6 at timestamp=4004 must pass,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket has 1 token available&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 7 at timestamp=4005 must not be allowed,&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; because bucket has 0 tokens available&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="50">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 50 lines</span>
    </div>
</div>
<p>This test simulates a scenario where tokens are accumulated in the bucket and used later on to allow multiple requests to pass. More tests and latest source code can be found in the 
<a href="https://github.com/rdiachenko/rd-blog/tree/main/rate-limiting" target="_blank" rel="nofollow noopener">rd-blog repository</a>
.</p>
<p>For additional implementations and insights, you can explore open-source resources such as:</p>
<ul>
<li>
<a href="https://github.com/bucket4j/bucket4j/tree/master" target="_blank" rel="nofollow noopener">Java rate limiting library based on token-bucket algorithm</a>
</li>
<li>
<a href="https://github.com/mailgun/gubernator" target="_blank" rel="nofollow noopener">High Performance Rate Limiting MicroService and Library</a>
</li>
</ul>
<h2 id="refill-strategy-interval-vs-greedy">
Refill strategy: interval vs. greedy
<a href="#refill-strategy-interval-vs-greedy" class="heading-anchor" aria-label="Anchor link for: Refill strategy: interval vs. greedy">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The refiller&rsquo;s goal is to replenish a bucket with tokens based on the elapsed time.</p>
<h3 id="interval-based-refiller">
Interval-based refiller
<a href="#interval-based-refiller" class="heading-anchor" aria-label="Anchor link for: Interval-based refiller">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The implementation above uses an interval-based refiller to regenerate tokens in the bucket at fixed intervals. It waits for the entire period to elapse before regenerating the full amount of tokens designated for that period. This approach is very similar to 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/">how the Fixed Window algorithm works</a>
, and it has a similar issue with bursty traffic at the period boundaries.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/intervally-and-greedy-refiller-comparison_hu_6f91f828dd4ecbeb.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="224">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/intervally-and-greedy-refiller-comparison_hu_66307a622edb4a0a.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="319">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/intervally-and-greedy-refiller-comparison_hu_66307a622edb4a0a.webp"
                    alt="Comparison between interval-based and greedy refiller"
                    width="1200"
                    height="319"
                    loading="lazy">
        </picture><figcaption><small>Figure 3. Comparison between interval-based and greedy refiller</small></figcaption></figure>
<h3 id="greedy-refiller-a-smoother-alternative">
Greedy refiller: a smoother alternative
<a href="#greedy-refiller-a-smoother-alternative" class="heading-anchor" aria-label="Anchor link for: Greedy refiller: a smoother alternative">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The greedy refiller, on the other hand, aims to add tokens to the bucket as soon as possible without waiting for the entire period to elapse. As illustrated in the figure above, a greedy refiller with a bucket configuration of &ldquo;2 tokens per 1 second&rdquo; would add 1 token every 500 milliseconds, whereas an interval-based refiller would add all 2 tokens at once every second.</p>
<p>Below is a Java implementation for the greedy refill strategy:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">void</span><span class="w"> </span><span class="nf">refill</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">long</span><span class="w"> </span><span class="n">now</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">clock</span><span class="p">.</span><span class="na">millis</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">long</span><span class="w"> </span><span class="n">elapsedTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">now</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">refillTimestamp</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// Calculate the number of available tokens to add</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// based on the elapsed time.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">long</span><span class="w"> </span><span class="n">availableTokens</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">elapsedTime</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">tokensPerPeriod</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">period</span><span class="p">.</span><span class="na">toMillis</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// Update the token count and ensure that the total</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// does not exceed the bucket&#39;s capacity.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">tokenCount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Math</span><span class="p">.</span><span class="na">min</span><span class="p">(</span><span class="n">tokenCount</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">availableTokens</span><span class="p">,</span><span class="w"> </span><span class="n">capacity</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// Adjust the refill timestamp forward by the time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// equivalent to the number of tokens added.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">refillTimestamp</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">availableTokens</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">period</span><span class="p">.</span><span class="na">toMillis</span><span class="p">()</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">tokensPerPeriod</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>It provides a smoother token distribution within the time interval and solves the issue of bursty traffic. However, it can be tricky to implement and requires more calculations and updates.</p>
<h2 id="trade-offs-and-design-considerations">
Trade-offs and design considerations
<a href="#trade-offs-and-design-considerations" class="heading-anchor" aria-label="Anchor link for: Trade-offs and design considerations">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<h3 id="pros">
Pros
<a href="#pros" class="heading-anchor" aria-label="Anchor link for: Pros">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p><strong>Bursty Traffic Handling</strong>: Unlike the 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/leaky-bucket-algorithm/">leaky bucket algorithm</a>
, the token bucket allows for bursts of requests within the bucket capacity. This means clients can adapt to changing demands and make multiple requests quickly if needed, without being throttled immediately. This flexibility is beneficial for applications with unpredictable traffic patterns.</p>
<p><strong>Efficiency</strong>: It is memory efficient, as it only requires a fixed number of tokens to be stored. This can be important in systems with limited memory resources.</p>
<p><strong>Customization</strong>: The token bucket allows for easy customization of parameters like token refill rate and bucket capacity. This enables fine-tuning the rate limiting behavior to specific needs and scenarios.</p>
<h3 id="cons">
Cons
<a href="#cons" class="heading-anchor" aria-label="Anchor link for: Cons">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p><strong>Complexity</strong>: Implementing the token bucket in a distributed environment can be challenging due to potential race conditions. Ensuring consistent token availability across multiple servers requires additional complexity and synchronization mechanisms. Additionally, configuring it properly, especially for applications with highly variable loads, can be difficult. For example, if the token bucket is too small or the refill rate is too slow, it can lead to resource starvation, where legitimate requests are denied because the bucket runs out of tokens too quickly.</p>
<p><strong>Granularity</strong>: For use cases requiring extremely fine-grained control over very short periods, the token bucket algorithm may not offer the necessary granularity. This is because it operates on the principle of accumulating tokens at a predefined rate, which might not adjust quickly enough to rapid changes in traffic within those short windows.</p>
<p><strong>Bursty Traffic</strong>: The token bucket doesn&rsquo;t guarantee a perfectly smooth and consistent rate of requests. This can be problematic for applications that require a steady flow of data, as sudden bursts followed by slower periods can disrupt operations.</p>
<h2 id="common-use-cases">
Common use cases
<a href="#common-use-cases" class="heading-anchor" aria-label="Anchor link for: Common use cases">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The algorithm&rsquo;s flexibility in handling bursts makes it well-suited for scenarios where occasional spikes in activity are expected. Below are a few real-world examples of using this algorithm.</p>
<p>
<a href="https://stripe.com/blog/rate-limiters" target="_blank" rel="nofollow noopener">Stripe uses the token bucket algorithm</a>
 to improve availability and reliability of their API:</p>
<blockquote>
<p>We use the token bucket algorithm to do rate limiting. This algorithm has a centralized bucket host where you take tokens on each request, and slowly drip more tokens into the bucket. If the bucket is empty, reject the request. In our case, every Stripe user has a bucket, and every time they make a request we remove a token from that bucket.</p>
</blockquote>
<p>
<a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/throttling.html" target="_blank" rel="nofollow noopener">AWS relies on this algorithm</a>
 to throttle EC2 API requests for each account on a per-region basis, which improves service performance and ensures fair usage for all Amazon EC2 customers:</p>
<blockquote>
<p>Amazon EC2 uses thetoken bucket algorithmto implement API throttling. With this algorithm, your account has abucketthat holds a specific number oftokens. The number of tokens in the bucket represents your throttling limit at any given second.</p>
</blockquote>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The Token Bucket algorithm offers a balanced approach to rate limiting, capable of adapting to dynamic traffic patterns while maintaining simplicity in concept. However, its implementation complexity and the need for careful configuration to prevent resource starvation underline the importance of thoughtful development and deployment in distributed systems.</p>
]]></content:encoded></item><item><title>Sliding Window Rate Limiting and its Memory-Optimized Variant</title><link>https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/</link><pubDate>Mon, 05 Feb 2024 08:45:53 +0000</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/</guid><description>A practical look at the Sliding Window rate limiting, how it handles rolling time windows, its memory-optimized version that scales better.</description><content:encoded><![CDATA[<p>The Sliding Window 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/">Rate Limiting</a>
 is similar to the 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/">Fixed Window Rate Limiting</a>
 but differs in its approach to managing the time window. Unlike the fixed time window, which is static, the sliding window moves continuously along the request timeline. It monitors requests within the current window, rejecting additional requests once the window’s limit is reached. As the window slides forward on the timeline, older requests that fall outside the window are discarded, creating space for new requests.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/sliding-window-algorithm-cover_hu_7f45e96a637269f1.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="466">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/sliding-window-algorithm-cover_hu_c6ce4929e7eb0e01.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="666">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/sliding-window-algorithm-cover_hu_c6ce4929e7eb0e01.webp"
                    alt="Sliding Window Algorithm in Action"
                    width="1200"
                    height="666"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Sliding Window Algorithm in Action</small></figcaption></figure>
<p>In the figure above, the rate limit is one request per two seconds. Request A is processed successfully. When Request B arrives, the window moves right, using B’s current time as the end of the rolling window, but B is rejected because it falls within the same two-second window as A. Request C, arriving later, is also throttled for including A within its window. Request D, however, arrives outside A’s two-second window and is thus processed successfully.</p>
<h2 id="implementing-the-sliding-window-log-rate-limiter">
Implementing the sliding window log rate limiter
<a href="#implementing-the-sliding-window-log-rate-limiter" class="heading-anchor" aria-label="Anchor link for: Implementing the sliding window log rate limiter">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>This is a classic implementation which is commonly known as the <strong>Sliding Window Log Rate Limiter</strong>. To control the rate it initializes two key properties: the maximum number of requests allowed per time window and the window’s duration. As shown in the flow diagram below, a new sliding window is created once for each new user. This window tracks the timestamps of the user’s requests. Upon the arrival of a new request, any old timestamps outside the sliding window are discarded to accommodate new ones. If the number of requests within the window exceeds the set limit, the incoming request is throttled. Otherwise, it is added to the window and processed successfully.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/sliding-window-algorithm-flow-diagram_hu_2aeec93bcbe90121.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="874">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/sliding-window-algorithm-flow-diagram_hu_3009b7456c2b4d00.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="1248">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/sliding-window-algorithm-flow-diagram_hu_3009b7456c2b4d00.webp"
                    alt="Flow Diagram for Sliding Window Algorithm"
                    width="1200"
                    height="1248"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. Flow Diagram for Sliding Window Algorithm</small></figcaption></figure>
<p>To track user-specific request timestamps, you can use a dictionary with the user ID as the key and a deque of request timestamps as the value, represented as <code>Map&lt;String, Deque&lt;Long&gt;&gt;</code>. The deque functions as a sliding window, where old timestamps are discarded and new ones are added as the window slides forward.</p>
<p>Below is a Java implementation of the Sliding Window Log Rate Limiting algorithm:</p>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="44">
        <div class="code-header">
                <span class="code-filename">SlidingWindowLogRateLimiter.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.Deque</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.HashMap</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.LinkedList</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.Map</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">SlidingWindowLogRateLimiter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">maxCount</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"> </span><span class="n">Deque</span><span class="o">&lt;</span><span class="n">Long</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="n">userSlidingWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">HashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">SlidingWindowLogRateLimiter</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">maxCount</span><span class="p">,</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="p">,</span><span class="w"> </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">maxCount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">maxCount</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">windowLengthMillis</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">clock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">allowed</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">userId</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">long</span><span class="w"> </span><span class="n">now</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">clock</span><span class="p">.</span><span class="na">millis</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Initialize an empty sliding window for new users,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// or retrieve the existing window.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Deque</span><span class="o">&lt;</span><span class="n">Long</span><span class="o">&gt;</span><span class="w"> </span><span class="n">slidingWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">userSlidingWindow</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="na">computeIfAbsent</span><span class="p">(</span><span class="n">userId</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">LinkedList</span><span class="o">&lt;&gt;</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Remove timestamps outside the current sliding window.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">slidingWindow</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">slidingWindow</span><span class="p">.</span><span class="na">getFirst</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">now</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">slidingWindow</span><span class="p">.</span><span class="na">removeFirst</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Check if the rate limit is exceeded.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// If so, reject the request; otherwise, add the current</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// request&#39;s timestamp to the window and allow it.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">slidingWindow</span><span class="p">.</span><span class="na">size</span><span class="p">()</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="n">maxCount</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">slidingWindow</span><span class="p">.</span><span class="na">addLast</span><span class="p">(</span><span class="n">now</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="44">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 44 lines</span>
    </div>
</div>
<p>The <code>allowed</code> method determines if a user’s request falls within the set limit. A new sliding window is created for each new user. Timestamps outside the window are removed, and new request timestamps are added to the window. Once the window reaches its maximum request capacity, further requests are rejected until the window advances and old timestamps are discarded.</p>
<h2 id="simulating-bursty-traffic-with-tests">
Simulating bursty traffic with tests
<a href="#simulating-bursty-traffic-with-tests" class="heading-anchor" aria-label="Anchor link for: Simulating bursty traffic with tests">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The following Java test demonstrates the usage of this limiter:</p>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="45">
        <div class="code-header">
                <span class="code-filename">SlidingWindowLogRateLimiterTest.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Test</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Assertions.assertFalse</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Assertions.assertTrue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.mockito.Mockito.mock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.mockito.Mockito.when</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">SlidingWindowLogRateLimiterTest</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">BOB</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;Bob&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nd">@Test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">void</span><span class="w"> </span><span class="nf">allowed_burstyTraffic_acceptsAllRequestsWithinRateLimitThresholds</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">mock</span><span class="p">(</span><span class="n">Clock</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">when</span><span class="p">(</span><span class="n">clock</span><span class="p">.</span><span class="na">millis</span><span class="p">()).</span><span class="na">thenReturn</span><span class="p">(</span><span class="n">0L</span><span class="p">,</span><span class="w"> </span><span class="n">999L</span><span class="p">,</span><span class="w"> </span><span class="n">1000L</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">1001L</span><span class="p">,</span><span class="w"> </span><span class="n">1002L</span><span class="p">,</span><span class="w"> </span><span class="n">1999L</span><span class="p">,</span><span class="w"> </span><span class="n">2000L</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">SlidingWindowLogRateLimiter</span><span class="w"> </span><span class="n">limiter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">SlidingWindowLogRateLimiter</span><span class="p">(</span><span class="n">2</span><span class="p">,</span><span class="w"> </span><span class="n">1000</span><span class="p">,</span><span class="w"> </span><span class="n">clock</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 0 seconds passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 1 at timestamp=0 must pass&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 2 at timestamp=999 must pass&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 1 second passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 3 at timestamp=1000 must not be allowed&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 4 at timestamp=1001 must pass, because request 1&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; at timestamp=0 is outside the current sliding window [1; 1001]&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 5 at timestamp=1002 must not be allowed&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 6 at timestamp=1999 must not be allowed&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 2 seconds passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 7 at timestamp=2000 must pass, because request 2&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; at timestamp=999 is outside the current sliding window [1000; 2000]&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="45">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 45 lines</span>
    </div>
</div>
<p>This test simulates scenarios for bursty traffic to ensure the requests at the window&rsquo;s border are handled correctly. More tests and the latest source code are available in the 
<a href="https://github.com/rdiachenko/rd-blog/tree/main/rate-limiting" target="_blank" rel="nofollow noopener">rd-blog repository</a>
.</p>
<p>For additional implementations and insights, you can explore open-source resources such as:</p>
<ul>
<li>
<a href="https://github.com/Narasimha1997/ratelimiter?tab=readme-ov-file" target="_blank" rel="nofollow noopener">Golang rate limiter based on Sliding Window algorithm</a>
</li>
<li>
<a href="https://github.com/bvtterfly/sliding-window-rate-limiter" target="_blank" rel="nofollow noopener">Laravel Sliding Window Rate Limiter</a>
</li>
</ul>
<h2 id="memory-optimized-version-sliding-window-counter">
Memory-optimized version: sliding window counter
<a href="#memory-optimized-version-sliding-window-counter" class="heading-anchor" aria-label="Anchor link for: Memory-optimized version: sliding window counter">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The classic implementation above can be resource-intensive due to the need to store numerous request timestamps and perform complex calculations across distributed servers. This makes the Sliding Window Log Rate Limiting approach less scalable for handling large traffic bursts.</p>
<p>The optimized implementation, commonly referred to as the <strong>Sliding Window Counter Rate Limiter</strong>, aims to minimize memory usage by merging the low processing cost of the 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/">Fixed Window algorithm</a>
 with the enhanced boundary conditions of the Sliding Window Log approach. It keeps a request counter for the previous and current fixed windows and leverages information from the previous window to calculate the available limit at the current timestamp.</p>
<p>Consider a scenario where you are allowed to make 100 requests every 2 seconds. Suppose you made 100 requests in the first 2 seconds and then made 15 requests within the first 400 ms (which is 20% of a new fixed window) of the next period. With the current window 20% elapsed, the count from the previous window is weighted by 80%, as shown in Figure 3. As a result, the current request count is calculated as follows:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">count = 100 * 0.8 + 15 = 95 requests
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">100 - number of requests in the previous fixed window
</span></span><span class="line"><span class="cl">0.8 - weight of the previous window (since the sliding window covers 20%
</span></span><span class="line"><span class="cl">      of the current fixed window and 80% of the previous one)
</span></span><span class="line"><span class="cl">15  - number of requests in the current fixed window</span></span></code></pre></div></div>
</div>
<p>Based on the limit set, you can still make 5 more requests.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/weighted-count-calculation_hu_529b021968526538.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="541">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/weighted-count-calculation_hu_4780078432ffbe62.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="773">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/weighted-count-calculation_hu_4780078432ffbe62.webp"
                    alt="Calculation of Weighted Request Count"
                    width="1200"
                    height="773"
                    loading="lazy">
        </picture><figcaption><small>Figure 3. Calculation of Weighted Request Count</small></figcaption></figure>
<p>This approach, which requires tracking less data per user, is more scalable across large clusters. Below is a Java implementation of the Sliding Window Counter Rate Limiter:</p>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="69">
        <div class="code-header">
                <span class="code-filename">SlidingWindowCountRateLimiter.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.HashMap</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.Map</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">SlidingWindowCountRateLimiter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">maxCount</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"> </span><span class="n">SlidingWindow</span><span class="o">&gt;</span><span class="w"> </span><span class="n">userSlidingWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">HashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">SlidingWindowCountRateLimiter</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">maxCount</span><span class="p">,</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="p">,</span><span class="w"> </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">maxCount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">maxCount</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">windowLengthMillis</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">clock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">allowed</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">userId</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">long</span><span class="w"> </span><span class="n">now</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">clock</span><span class="p">.</span><span class="na">millis</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Initialize an empty sliding window for new users or retrieve existing one.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">SlidingWindow</span><span class="w"> </span><span class="n">slidingWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">userSlidingWindow</span><span class="p">.</span><span class="na">computeIfAbsent</span><span class="p">(</span><span class="n">userId</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">k</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">SlidingWindow</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">FixedWindow</span><span class="p">(</span><span class="n">now</span><span class="p">,</span><span class="w"> </span><span class="n">0</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">new</span><span class="w"> </span><span class="n">FixedWindow</span><span class="p">(</span><span class="n">now</span><span class="p">,</span><span class="w"> </span><span class="n">0</span><span class="p">)));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">FixedWindow</span><span class="w"> </span><span class="n">currentFixedWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">slidingWindow</span><span class="p">.</span><span class="na">currentFixedWindow</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">FixedWindow</span><span class="w"> </span><span class="n">previousFixedWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">slidingWindow</span><span class="p">.</span><span class="na">previousFixedWindow</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Transition to a new fixed window when the current one expires.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">currentFixedWindow</span><span class="p">.</span><span class="na">timestamp</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">now</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">previousFixedWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">currentFixedWindow</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">currentFixedWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">FixedWindow</span><span class="p">(</span><span class="n">now</span><span class="p">,</span><span class="w"> </span><span class="n">0</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">userSlidingWindow</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">userId</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="k">new</span><span class="w"> </span><span class="n">SlidingWindow</span><span class="p">(</span><span class="n">previousFixedWindow</span><span class="p">,</span><span class="w"> </span><span class="n">currentFixedWindow</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Weight calculation for the previous window.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">long</span><span class="w"> </span><span class="n">slidingWindowStart</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Math</span><span class="p">.</span><span class="na">max</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"> </span><span class="n">now</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">long</span><span class="w"> </span><span class="n">previousFixedWindowEnd</span><span class="w"> </span><span class="o">=</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">previousFixedWindow</span><span class="p">.</span><span class="na">timestamp</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Weight of the previous window based on overlap with the sliding window.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">double</span><span class="w"> </span><span class="n">previousFixedWindowWeight</span><span class="w"> </span><span class="o">=</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Math</span><span class="p">.</span><span class="na">max</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"> </span><span class="n">previousFixedWindowEnd</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">slidingWindowStart</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="o">/</span><span class="w"> </span><span class="p">(</span><span class="kt">double</span><span class="p">)</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Calculate total request count within the sliding window.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="n">count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="n">previousFixedWindow</span><span class="p">.</span><span class="na">count</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">*</span><span class="w"> </span><span class="n">previousFixedWindowWeight</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">+</span><span class="w"> </span><span class="n">currentFixedWindow</span><span class="p">.</span><span class="na">count</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Check if the request count within the sliding window exceeds the limit.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// If so, reject the request; otherwise, update the request count</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// in the current fixed window and allow the request.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">count</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="n">maxCount</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">currentFixedWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">FixedWindow</span><span class="p">(</span><span class="n">currentFixedWindow</span><span class="p">.</span><span class="na">timestamp</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="n">currentFixedWindow</span><span class="p">.</span><span class="na">count</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">userSlidingWindow</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">userId</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="k">new</span><span class="w"> </span><span class="n">SlidingWindow</span><span class="p">(</span><span class="n">previousFixedWindow</span><span class="p">,</span><span class="w"> </span><span class="n">currentFixedWindow</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">record</span> <span class="nc">SlidingWindow</span><span class="p">(</span><span class="n">FixedWindow</span><span class="w"> </span><span class="n">previousFixedWindow</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                               </span><span class="n">FixedWindow</span><span class="w"> </span><span class="n">currentFixedWindow</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">record</span> <span class="nc">FixedWindow</span><span class="p">(</span><span class="kt">long</span><span class="w"> </span><span class="n">timestamp</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">count</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="69">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 69 lines</span>
    </div>
</div>
<p>The sliding window tracks both the current and previous fixed windows, updating timestamps and request counts as needed. When the current window expires, it becomes the previous window, and a new window starts with zero requests. The current request count within the sliding window is determined by the weight of the previous fixed window and the number of requests made within the current fixed window. Once the sliding window reaches its maximum request capacity, further requests are rejected until the window advances and the influence of the previous fixed window diminishes.</p>
<p>However, there are a few drawbacks to this optimized approach worth noting:</p>
<ul>
<li>It is more complex to implement, debug, and maintain.</li>
<li>The requirement for the look-back window&rsquo;s timestamp to be flexible rather than strict.</li>
</ul>
<h2 id="trade-offs-and-design-considerations">
Trade-offs and design considerations
<a href="#trade-offs-and-design-considerations" class="heading-anchor" aria-label="Anchor link for: Trade-offs and design considerations">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<h3 id="pros">
Pros
<a href="#pros" class="heading-anchor" aria-label="Anchor link for: Pros">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p><strong>Bursty Traffic Handling</strong>: The sliding window approach provides a more evenly distributed control over traffic than the 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/">fixed window algorithm</a>
, reducing the likelihood of sudden spikes that could potentially overwhelm the system. It effectively handles bursts of traffic resulting the number of wrongly allowed requests to be very low.</p>
<p><strong>Fairness</strong>: This rate-limiting algorithm avoids the starvation problem of the 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/leaky-bucket-algorithm/">leaky bucket algorithm</a>
 by prioritizing recent requests and giving more weight to activity within the current window. This helps identify and mitigate ongoing attacks while ensuring active users aren’t unfairly penalized for past bursts.</p>
<p><strong>Real-Time Adaptability</strong>: The algorithm can adapt more dynamically to real-time changes in request volume, offering a more accurate reflection of current usage patterns compared to a fixed window approach.</p>
<h3 id="cons">
Cons
<a href="#cons" class="heading-anchor" aria-label="Anchor link for: Cons">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p><strong>Complexity</strong>: Compared to simpler approaches like the 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/">fixed window</a>
 or even the 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/">token bucket</a>
, the sliding window algorithm, often implemented with Redis as a data store, can be more complex to implement correctly, especially in distributed systems where time synchronization between servers can be an issue.</p>
<p><strong>Resource Intensive</strong>: Tracking requests within a continuously moving window demands more memory and complex data structures. Additionally, the computation is costly, as each request involves summing up the user’s previous requests, possibly across a server cluster. Consequently, this approach may not scale well enough to handle large traffic bursts or mitigate denial of service attacks.</p>
<p><strong>Predictability</strong>: Users may find it more difficult to predict when their rate limit will reset compared to a 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/">fixed window approach</a>
, as the available capacity can fluctuate more significantly within any given period.</p>
<h2 id="common-use-cases">
Common use cases
<a href="#common-use-cases" class="heading-anchor" aria-label="Anchor link for: Common use cases">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The sliding window algorithm&rsquo;s primary advantage is its ability to provide a more consistent and smooth control over the rate of operations or requests. This is particularly beneficial in scenarios where avoiding bursts of activity at the beginning or end of a rate limit window is important for maintaining system stability and user experience.</p>
<p>This algorithm offers more flexibility than 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/">fixed window rate limiting</a>
, adapting well to traffic spikes, making it suitable for applications and API with fluctuating usage patterns. Cloudflare, in their post 
<a href="https://blog.cloudflare.com/counting-things-a-lot-of-different-things/" target="_blank" rel="nofollow noopener">How we built rate limiting capable of scaling to millions of domains</a>
, shares insights from incorporating this algorithm into their system, highlighting its effectiveness in a real-time environment:</p>
<blockquote>
<p>It is still very accurate, as an analysis on 400 million requests from 270,000 distinct sources shown:</p>
<ul>
<li>0.003% of requests have been wrongly allowed or rate limited</li>
<li>An average difference of 6% between real rate and the approximate rate</li>
<li>3 sources have been allowed despite generating traffic slightly above the threshold (false negatives), the actual rate was less than 15% above the threshold rate</li>
<li>None of the mitigated sources was below the threshold (false positives)</li>
</ul>
</blockquote>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The Sliding Window Rate Limiting algorithm offers a great solution for applications requiring nuanced control over request rates. Despite its complexity and the demands it places on system resources, its benefits in terms of traffic management, fairness, and adaptability make it a valuable tool for maintaining system stability and ensuring a positive user experience.</p>
]]></content:encoded></item><item><title>Fixed Window Rate Limiting: Simple, Fast, and Sometimes Flawed</title><link>https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/</link><pubDate>Sat, 27 Jan 2024 07:44:42 +0000</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/</guid><description>A hands-on breakdown of the Fixed Window rate limiter, how it works, where it is fast and effective, and where it falls short.</description><content:encoded><![CDATA[<p>The Fixed Window 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/">Rate Limiting</a>
 algorithm divides time into fixed windows of a specified duration, with each window having a predetermined request limit. Requests are counted against this limit, and once the maximum number of requests for a window is reached, additional requests are rejected until the next window starts. The request count is reset at the beginning of each new window.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/fixed-window-algorithm-cover_hu_43834f8cb99d9ed0.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="339">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/fixed-window-algorithm-cover_hu_aeab1b3507ed249c.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="485">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/fixed-window-algorithm-cover_hu_aeab1b3507ed249c.webp"
                    alt="Fixed Window Algorithm in Action"
                    width="1200"
                    height="485"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Fixed Window Algorithm in Action</small></figcaption></figure>
<p>In the figure above, the rate limit is set to one request per two seconds. The initial timeframe starts when request A arrives, bringing the request count to 1. Request B is dropped because it causes the request count to reach 2 within the current two-second window. Shortly after two seconds, request C arrives. This marks the start of a new window and resets the request count, allowing Request C to be processed. Request D is subsequently dropped for the same reason as Request B.</p>
<h2 id="implementing-the-fixed-window-rate-limiter">
Implementing the fixed window rate limiter
<a href="#implementing-the-fixed-window-rate-limiter" class="heading-anchor" aria-label="Anchor link for: Implementing the fixed window rate limiter">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The Fixed Window Rate Limiting algorithm is initialized with two key properties: the maximum number of requests allowed per window and the length of each window. As illustrated in the flow diagram below, a request is successfully processed if the current request count within the ongoing window does not exceed the maximum limit. If the limit is exceeded, the request is either rejected or delayed. A new window is initiated, and the request count is reset, when the request&rsquo;s timestamp falls outside the current window.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/fixed-window-algorithm-flow-diagram_hu_3d37a91be9735a94.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="769">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/fixed-window-algorithm-flow-diagram_hu_e2cf7e970caf71ce.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="1099">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/fixed-window-algorithm-flow-diagram_hu_e2cf7e970caf71ce.webp"
                    alt="Flow Diagram for Fixed Window Algorithm"
                    width="1200"
                    height="1099"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. Flow Diagram for Fixed Window Algorithm</small></figcaption></figure>
<p>Typically, rate limits are applied per user. To track user-specific request counts and windows, you may use a dictionary with user ID as the key and a pair of the window timestamp and request count as the value, represented as <code>Map&lt;String, FixedWindow&gt;</code>.</p>
<p>Below is a Java implementation of the Fixed Window Rate Limiting algorithm:</p>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="44">
        <div class="code-header">
                <span class="code-filename">FixedWindowRateLimiter.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.HashMap</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.Map</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">FixedWindowRateLimiter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">maxCount</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"> </span><span class="n">FixedWindow</span><span class="o">&gt;</span><span class="w"> </span><span class="n">userFixedWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">HashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">FixedWindowRateLimiter</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">maxCount</span><span class="p">,</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="p">,</span><span class="w"> </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">maxCount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">maxCount</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">windowLengthMillis</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">clock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">allowed</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">userId</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">long</span><span class="w"> </span><span class="n">now</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">clock</span><span class="p">.</span><span class="na">millis</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">FixedWindow</span><span class="w"> </span><span class="n">fixedWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">userFixedWindow</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">userId</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// If there is a new user OR it is time to start a new window,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// initialize a new fixed window with the current request timestamp.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">fixedWindow</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">null</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">||</span><span class="w"> </span><span class="n">fixedWindow</span><span class="p">.</span><span class="na">timestamp</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">windowLengthMillis</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">now</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">fixedWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">FixedWindow</span><span class="p">(</span><span class="n">now</span><span class="p">,</span><span class="w"> </span><span class="n">0</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// If a number of requests within the window exceeds the limit,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// disallow this request. Otherwise, update the current request count</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// and allow the request.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">fixedWindow</span><span class="p">.</span><span class="na">count</span><span class="p">()</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="n">maxCount</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">FixedWindow</span><span class="w"> </span><span class="n">updatedWindow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">FixedWindow</span><span class="p">(</span><span class="n">fixedWindow</span><span class="p">.</span><span class="na">timestamp</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="n">fixedWindow</span><span class="p">.</span><span class="na">count</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">userFixedWindow</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">userId</span><span class="p">,</span><span class="w"> </span><span class="n">updatedWindow</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">record</span> <span class="nc">FixedWindow</span><span class="p">(</span><span class="kt">long</span><span class="w"> </span><span class="n">timestamp</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">count</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="44">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 44 lines</span>
    </div>
</div>
<p>The <code>allowed</code> method checks whether a user&rsquo;s request is within the limits. If a user&rsquo;s current window does not exist or has expired, a new window is created. Once the maximum number of requests in a window is reached, subsequent requests are rejected until the next window starts.</p>
<h2 id="simulating-requests-from-multiple-users-with-tests">
Simulating requests from multiple users with tests
<a href="#simulating-requests-from-multiple-users-with-tests" class="heading-anchor" aria-label="Anchor link for: Simulating requests from multiple users with tests">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The following Java tests demonstrates the usage of this limiter:</p>
<div class="code-block highlight-collapsed" data-frame="editor" data-collapsible data-lines="53">
        <div class="code-header">
                <span class="code-filename">FixedWindowRateLimiterTest.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Test</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.time.Clock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Assertions.assertFalse</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Assertions.assertTrue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.mockito.Mockito.mock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.mockito.Mockito.when</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">FixedWindowRateLimiterTest</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">BOB</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;Bob&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">ALICE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;Alice&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nd">@Test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">void</span><span class="w"> </span><span class="nf">allowed_requestsFromMultipleUsers_ensuresIndividualRateLimiters</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Clock</span><span class="w"> </span><span class="n">clock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">mock</span><span class="p">(</span><span class="n">Clock</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">when</span><span class="p">(</span><span class="n">clock</span><span class="p">.</span><span class="na">millis</span><span class="p">()).</span><span class="na">thenReturn</span><span class="p">(</span><span class="n">0L</span><span class="p">,</span><span class="w"> </span><span class="n">999L</span><span class="p">,</span><span class="w"> </span><span class="n">1000L</span><span class="p">,</span><span class="w"> </span><span class="n">1000L</span><span class="p">,</span><span class="w"> </span><span class="n">1001L</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">2001L</span><span class="p">,</span><span class="w"> </span><span class="n">2001L</span><span class="p">,</span><span class="w"> </span><span class="n">2001L</span><span class="p">,</span><span class="w"> </span><span class="n">3002L</span><span class="p">,</span><span class="w"> </span><span class="n">3003L</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">FixedWindowRateLimiter</span><span class="w"> </span><span class="n">limiter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">FixedWindowRateLimiter</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"> </span><span class="n">2000</span><span class="p">,</span><span class="w"> </span><span class="n">clock</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 0 seconds passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 1 at timestamp=0 must pass&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 2 at timestamp=999 must not be allowed&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 1 second passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 3 at timestamp=1000 must not be allowed&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">ALICE</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Alice&#39;s request 1 at timestamp=1000 must pass&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">ALICE</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Alice&#39;s request 2 at timestamp=1001 must not be allowed&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 2 seconds passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">ALICE</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Alice&#39;s request 3 at timestamp=2001 must not be allowed&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 4 at timestamp=2001 must pass, because a new&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; fixed window [2001; 3001] is started with reset counts&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">BOB</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Bob&#39;s request 5 at timestamp=2001 must not be allowed&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 3 seconds passed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">ALICE</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Alice&#39;s request 4 at timestamp=3002 must pass, because a new&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34; fixed window [3002; 4002] is started with reset counts&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">assertFalse</span><span class="p">(</span><span class="n">limiter</span><span class="p">.</span><span class="na">allowed</span><span class="p">(</span><span class="n">ALICE</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;Alice&#39;s request 5 at timestamp=3003 must not be allowed&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
    <div class="code-expand-bar" data-lines="53">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
        <span>Show all 53 lines</span>
    </div>
</div>
<p>This test simulates scenarios for different users and varying time intervals to ensure the rate limiter functions as expected.
More tests and latest source code can be found in the 
<a href="https://github.com/rdiachenko/rd-blog/tree/main/rate-limiting" target="_blank" rel="nofollow noopener">rd-blog repository</a>
.</p>
<p>For additional implementations and perspectives, you can explore open-source resources such as:</p>
<ul>
<li>
<a href="https://github.com/ravangen/graphql-rate-limit" target="_blank" rel="nofollow noopener">Fixed window rate limiting middleware for GraphQL</a>
</li>
<li>
<a href="https://github.com/romantomjak/redis-ratelimit" target="_blank" rel="nofollow noopener">Fixed window rate limiter based on Redis</a>
</li>
</ul>
<h2 id="trade-offs-and-design-considerations">
Trade-offs and design considerations
<a href="#trade-offs-and-design-considerations" class="heading-anchor" aria-label="Anchor link for: Trade-offs and design considerations">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<h3 id="pros">
Pros
<a href="#pros" class="heading-anchor" aria-label="Anchor link for: Pros">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p><strong>Simplicity</strong>: The fixed window rate limiter is relatively straightforward to implement. It doesn&rsquo;t require complex algorithm theory or data structures, making it a good choice for small applications or for engineers who need a quick solution. Additionally, this algorithm can be adapted for distributed rate limiting using Redis, which supports 
<a href="https://redis.io/commands/incr/#pattern-rate-limiter" target="_blank" rel="nofollow noopener">atomic execution of operations</a>
.</p>
<p><strong>Efficiency</strong>: Due to its simplicity, a fixed window rate limiter often requires less computational overhead compared to more complex algorithms such as the 

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/">sliding window rate limiting algorithm</a>
. This makes it suitable for high-performance environments where processing speed is crucial.</p>
<p><strong>Fairness</strong>: This algorithm ensures that recent requests are processed without being delayed by older ones. It facilitates the execution of more new requests as the system resets the request count at the end of each window.</p>
<h3 id="cons">
Cons
<a href="#cons" class="heading-anchor" aria-label="Anchor link for: Cons">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p><strong>Bursty Traffic</strong>: Fixed window rate limiters can lead to traffic spikes, potentially allowing the number of requests to be twice the set limit. This occurs if a large number of requests arrive at the end of one window and the start of the next, temporarily overloading the system despite the average rate of requests being within the set limits. Although adding a secondary rate limit with a smaller threshold and shorter window (e.g., 5 requests per second, in addition to 100 requests per minute) could mitigate this, it complicates the rate limiting and may impose excessively strict restrictions on user requests.</p>
<p><strong>Inflexibility</strong>: This approach does not differentiate between various types of requests or users, treating lightweight and resource-intensive requests equally. Consequently, it might not be suitable for scenarios requiring traffic prioritization or tailored handling of different request types.</p>
<p><strong>Uneven Usage</strong>: With longer window durations (e.g., 10 requests per hour), users who rapidly reach the limit may experience prolonged waiting times. For instance, a user could make 10 requests in the first minute, but would have to wait nearly an hour to make the 11th request, leading to uneven and potentially frustrating user experiences.</p>
<h2 id="common-use-cases">
Common use cases
<a href="#common-use-cases" class="heading-anchor" aria-label="Anchor link for: Common use cases">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>This algorithm is frequently applied in scenarios where rate limits need to be enforced within specific time intervals, such as in API rate limiting and user authentication processes.</p>
<p>Thanks to its simplicity and low memory requirements, the Fixed Window algorithm is well-suited for environments with limited resources, such as IoT devices and embedded software systems.</p>
<p>The Fixed Window algorithm does not require complex coordination in a distributed environment. The update operations within the algorithm can be executed atomically, enhancing its suitability for distributed systems. This characteristic makes it an advantageous choice in scenarios where a rate limiter needs to be shared between multiple instances of the same service.</p>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The beauty of the fixed window algorithm is its simplicity and resource efficiency both in local and distributed environments. Depending on your system requirements it can serve as an excellent initial choice, suitable for prototyping or as a primary layer of defense against abusive traffic.</p>
<p>This algorithm is particularly advantageous for new systems that need to monitor traffic patterns to determine future adaptations or to consider moving to a different algorithm. By implementing the fixed window approach, you can safeguard system availability and maintain a positive user experience without compromising on performance during the early stages of traffic pattern analysis.</p>
]]></content:encoded></item><item><title>Rate Limiting Concepts and Use Cases</title><link>https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/</link><pubDate>Wed, 17 Jan 2024 11:12:17 +0000</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/</guid><description>An overview of core rate limiting concepts, common use cases, and how different strategies help control system load and ensure fair usage.</description><content:encoded><![CDATA[<p>
<a href="https://en.wikipedia.org/wiki/Rate_limiting" target="_blank" rel="nofollow noopener">Rate limiting</a>
 is a critical technique in system design that controls the flow of data between systems, preventing resource monopolization, and maintaining system availability and security.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/rate-limiting-scenarios-in-web-systems_hu_be0a9c655cf2d878.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="354">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/rate-limiting-scenarios-in-web-systems_hu_c600605c4fab4ffb.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="505">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/rate-limiting-scenarios-in-web-systems_hu_c600605c4fab4ffb.webp"
                    alt="Rate Limiting Scenarios In Web Systems"
                    width="1200"
                    height="505"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Rate Limiting Scenarios In Web Systems</small></figcaption></figure>
<p>At its core, rate limiting sets a threshold on how often a user or system can perform a specific action within a set timeframe.
Examples include limiting login attempts, API requests, or chat messages to prevent abuse and overloading.</p>
<h2 id="why-is-rate-limiting-important">
Why is rate limiting important?
<a href="#why-is-rate-limiting-important" class="heading-anchor" aria-label="Anchor link for: Why is rate limiting important?">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p><strong>Defense Against Attacks</strong>: It&rsquo;s an effective barrier against various cyber threats like Denial of Service (DoS) and brute-force attacks, by limiting the rate at which requests are processed.</p>
<p><strong>Operational Efficiency</strong>: Rate limiting optimizes resource usage and reduces operational costs, especially in cloud environments where resources are metered.</p>
<p><strong>User Experience and SEO</strong>: By preventing server overloads, rate limiting ensures smoother web page loading, enhancing user experience and potentially improving search engine rankings.</p>
<h2 id="common-use-cases">
Common use cases
<a href="#common-use-cases" class="heading-anchor" aria-label="Anchor link for: Common use cases">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p><strong>Cloud and API Services</strong> like Cloudflare use rate limiting 
<a href="https://developers.cloudflare.com/waf/reference/legacy/old-rate-limiting/" target="_blank" rel="nofollow noopener">to safeguard against DDoS attacks</a>
 and other malicious traffic from overwhelming application servers. APIs often impose limits as a means to encourage upgrades to higher subscription tiers.</p>
<p><strong>Email Services</strong> like Gmail implement rate limiting 
<a href="https://support.google.com/mail/answer/22839" target="_blank" rel="nofollow noopener">to prevent spam</a>
, thereby maintaining the credibility of their service.</p>
<p><strong>Social Media and Messaging</strong> like Telegram use it 

            
        
            
        
    <a href="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/#limitations-to-keep-in-mind-when-developing-bots">to control bots</a>
, ensuring fair usage for all users.</p>
<p><strong>Streaming Services</strong> like Netflix implement rate limiting to manage server-client traffic. This can involve limiting the bitrate or 
<a href="https://netflixtechblog.medium.com/performance-under-load-3e6fa9a60581" target="_blank" rel="nofollow noopener">the number of concurrent streams</a>
, which helps prevent server overloads and improves the user experience.</p>
<p><strong>Distributed Systems</strong> use rate limiting to smooth traffic between nodes and prevent bottlenecks that could make the system unusable. This may include limiting the number of messages between services or the number of concurrent connections to a database.</p>
<p><strong>Network Traffic</strong> is often rate-limited to prioritize certain types of traffic, thereby enhancing overall network efficiency.</p>
<h2 id="types-of-rate-limits">
Types of rate limits
<a href="#types-of-rate-limits" class="heading-anchor" aria-label="Anchor link for: Types of rate limits">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/rate-limit-types-cover_hu_d129fa66cc457a28.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="499">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/rate-limit-types-cover_hu_81d7836519d899d8.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="713">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/rate-limit-types-cover_hu_81d7836519d899d8.webp"
                    alt="Rate Limit Types"
                    width="1200"
                    height="713"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. Rate Limit Types</small></figcaption></figure>
<p><strong>User-Based</strong>: This method caps the number of requests a user can make in a given timeframe. If the user exceeds the limit, their subsequent requests are either dropped or delayed until the next timeframe. Users are usually identified by an IP address or an API key.</p>
<p><strong>Resource-Based</strong>: This approach limits the number of requests to a specific server resource.</p>
<p><strong>Traffic-Based</strong>: This type limits the amount of data transmitted over a network. It can be used to prioritize different types of traffic, such as prioritizing real-time data over batch processing.</p>
<p><strong>Geographic-Based</strong>: This strategy implements different limits based on geographic locations and timeframes. For example, consider two application servers, as shown in Figure 2: one in New York, using the New York Stock Exchange, and another in London, using the London Stock Exchange. Since the London Stock Exchange operates Monday through Friday from 8:00 AM to 4:30 PM GMT, the server in London may experience high user activity during these hours. Therefore, you might set a higher rate limit for your London server during this period and lower it outside these hours. This approach helps detect and prevent suspicious traffic.</p>
<p><strong>Concurrency-Based</strong>: This method sets a maximum number of parallel sessions or connections per user. It is useful in mitigating distributed denial-of-service (DDoS) attacks.</p>
<h2 id="rate-limiting-algorithms">
Rate limiting algorithms
<a href="#rate-limiting-algorithms" class="heading-anchor" aria-label="Anchor link for: Rate limiting algorithms">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<h3 id="fixed-window">
Fixed window
<a href="#fixed-window" class="heading-anchor" aria-label="Anchor link for: Fixed window">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/fixed-window-algorithm/">Fixed Window</a>
 divides time into fixed windows of specified duration. Each window has a limit on the number of requests it can handle, and requests are counted against this limit. The request count is reset at the beginning of each new window.</p>
<p>In the figure below, the rate limit is set to one request per two seconds. Request B is dropped because the request counter reaches two in the current two-second timeframe. The next timeframe resets the request count, allowing Request C to be processed.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/fixed-window-algorithm_hu_43834f8cb99d9ed0.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="339">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/fixed-window-algorithm_hu_aeab1b3507ed249c.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="485">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/fixed-window-algorithm_hu_aeab1b3507ed249c.webp"
                    alt="Fixed Window Algorithm"
                    width="1200"
                    height="485"
                    loading="lazy">
        </picture><figcaption><small>Figure 3. Fixed Window Algorithm</small></figcaption></figure>
<p>This algorithm is applied in scenarios where rate limits need to be enforced within specific time intervals, such as in API rate limiting and user authentication.</p>
<h3 id="sliding-window">
Sliding window
<a href="#sliding-window" class="heading-anchor" aria-label="Anchor link for: Sliding window">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/sliding-window-algorithm/">Sliding Window</a>
 is similar to fixed-window rate limiting, but it differs in that the time window slides forward continuously, instead of being fixed and reset. This approach tracks the number of operations within the current time window and adjusts as time progresses.</p>
<p>In Figure 4, requests B and C are dropped because the moving two-second windows ending at the timestamps of requests B and C still include the processed request A. The timeframe ending with the timestamp of request D does not include request A, as it is too far in the past, thus allowing request D to be processed.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/sliding-window_hu_59fbee20010b8b7a.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="466">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/sliding-window_hu_f6263174e230bb01.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="666">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/sliding-window_hu_f6263174e230bb01.webp"
                    alt="Sliding Window Algorithm"
                    width="1200"
                    height="666"
                    loading="lazy">
        </picture><figcaption><small>Figure 4. Sliding Window Algorithm</small></figcaption></figure>
<p>This algorithm is effective in scenarios where there is a need to track and limit the rate of operations over a dynamic timeframe.</p>
<h3 id="token-bucket">
Token bucket
<a href="#token-bucket" class="heading-anchor" aria-label="Anchor link for: Token bucket">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/token-bucket-algorithm/">Token Bucket</a>
 involves a bucket that is filled with tokens at a fixed rate. Each token represents permission for an action. Requests are allowed if there are available tokens in the bucket; otherwise, they are delayed or dropped, as shown in Figure 5.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/token-bucket-algorithm_hu_907ad7e6aeb7852b.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="235">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/token-bucket-algorithm_hu_5e66a56b2a365123.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="336">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/token-bucket-algorithm_hu_5e66a56b2a365123.webp"
                    alt="Token Bucket Algorithm"
                    width="1200"
                    height="336"
                    loading="lazy">
        </picture><figcaption><small>Figure 5. Token Bucket Algorithm</small></figcaption></figure>
<p>This algorithm is commonly used in network traffic shaping, API rate limiting, and resource access control.</p>
<h3 id="leaky-bucket">
Leaky bucket
<a href="#leaky-bucket" class="heading-anchor" aria-label="Anchor link for: Leaky bucket">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>

            
        
    <a href="https://rdiachenko.com/posts/arch/rate-limiting/leaky-bucket-algorithm/">Leaky Bucket</a>
 is represented by a bucket with a leak, emptying its contents at a constant rate. Incoming requests are added to the bucket, functioning as a queue. If the bucket overflows, new requests are either delayed or discarded, as illustrated in Figure 6.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/leaky-bucket-algorithm_hu_4df83b9eaab67e21.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="258">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/leaky-bucket-algorithm_hu_36ebe04819f9998e.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="369">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/leaky-bucket-algorithm_hu_36ebe04819f9998e.webp"
                    alt="Leaky Bucket Algorithm"
                    width="1200"
                    height="369"
                    loading="lazy">
        </picture><figcaption><small>Figure 6. Leaky Bucket Algorithm</small></figcaption></figure>
<p>This algorithm is typically used to smooth out traffic patterns, prevent bursts in data transmission, and control the rate of data processing.</p>
<p><strong>Adaptive</strong> rate limiting dynamically adjusts rate limits based on current system conditions, user behavior, or other contextual factors. This approach is particularly effective in scenarios where rate limits need to be flexible and adapt to changing conditions, such as in content delivery networks (CDNs) or real-time systems.</p>
<p><strong>Distributed</strong> rate limiting is typically built on top of the aforementioned algorithms and used in distributed systems where rate limits are enforced across multiple servers or nodes. Each server may maintain its local rate limits, and a centralized mechanism can coordinate or aggregate these limits. This method is suitable for microservice architectures and scenarios where rate limiting needs to be coordinated across multiple components.</p>
<h2 id="rate-limiting-vs-throttling">
Rate limiting vs. throttling
<a href="#rate-limiting-vs-throttling" class="heading-anchor" aria-label="Anchor link for: Rate limiting vs. throttling">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>While rate limiting focuses on establishing a strict quantitative limit on the number of operations within a specified timeframe, throttling is a broader concept focused on controlling the data flow in a more dynamic and adaptive manner. Throttling may involve rate limiting but can also include other measures, such as load shedding and backpressure, to manage resource utilization.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/throttling-types_hu_b0b943f3bf7c83.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="336">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/throttling-types_hu_128c0e389d9b9f67.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="480">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/throttling-types_hu_128c0e389d9b9f67.webp"
                    alt="Throttling Types"
                    width="1200"
                    height="480"
                    loading="lazy">
        </picture><figcaption><small>Figure 7. Throttling Types</small></figcaption></figure>
<p>Rate limiting, load shedding, and backpressure can be considered specific types of throttling, each focusing on one aspect of resource management.</p>
<h2 id="rate-limiting-vs-load-shedding">
Rate limiting vs. load shedding
<a href="#rate-limiting-vs-load-shedding" class="heading-anchor" aria-label="Anchor link for: Rate limiting vs. load shedding">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Load shedding is a form of rate limiting based on the overall system state, where lower-priority requests are discarded to maintain critical functions. An illustrative example is Stripe&rsquo;s implementation of two load shedders in their system, as detailed in 
<a href="https://stripe.com/blog/rate-limiters" target="_blank" rel="nofollow noopener">Stripe&rsquo;s blog on rate limiters</a>
. This approach enhances API availability and prioritizes critical traffic over lower-priority requests.</p>
<blockquote>
<p><strong>Fleet usage load shedder</strong>.  Using this type of load shedder ensures that a certain percentage of your fleet will always be available for your most important API requests. We divide up our traffic into two types: critical API methods (e.g. creating charges) and non-critical methods (e.g. listing charges.) We have a Redis cluster that counts how many requests we currently have of each type. We always reserve a fraction of our infrastructure for critical requests. If our reservation number is 20%, then any non-critical request over their 80% allocation would be rejected with status code 503.</p>
</blockquote>
<blockquote>
<p><strong>Worker utilisation load shedder</strong>. Most API services use a set of workers to independently respond to incoming requests in a parallel fashion. This load shedder is the final line of defense. If your workers start getting backed up with requests, then this will shed lower-priority traffic. We divide our traffic into 4 categories: Critical methods, POSTs, GETs, Test mode traffic. We track the number of workers with available capacity at all times. If a box is too busy to handle its request volume, it will slowly start shedding less-critical requests, starting with test mode traffic. If shedding test mode traffic gets it back into a good state, great! We can start to slowly bring traffic back. Otherwise, it’ll escalate and start shedding even more traffic.</p>
</blockquote>
<h2 id="rate-limiting-vs-backpressure">
Rate limiting vs. backpressure
<a href="#rate-limiting-vs-backpressure" class="heading-anchor" aria-label="Anchor link for: Rate limiting vs. backpressure">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Backpressure is a mechanism that signals upstream components to slow down or stop producing data when downstream components are unable to handle the current rate. It involves a combination of client-side and server-side mechanisms. On the server side, backpressure includes rate limiting, while on the client side, it involves understanding how to adjust the request rate based on server load.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/backpressure-in-action_hu_2c94d24693ab70d6.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="468">
            <source
                    srcset="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/backpressure-in-action_hu_29bec4db86940cb8.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="668">
            <img
                    src="https://rdiachenko.com/posts/arch/rate-limiting/rate-limiting-basics/backpressure-in-action_hu_29bec4db86940cb8.webp"
                    alt="Backpressure In Action"
                    width="1200"
                    height="668"
                    loading="lazy">
        </picture><figcaption><small>Figure 8. Backpressure In Action</small></figcaption></figure>
<h2 id="challenges-and-limitations">
Challenges and limitations
<a href="#challenges-and-limitations" class="heading-anchor" aria-label="Anchor link for: Challenges and limitations">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Setting the appropriate rate limits can be a challenging task. If the limits are set too low, they can unnecessarily restrict legitimate users; however, if they are set too high, they might not effectively prevent system abuse.</p>
<p>Another consideration is managing sudden surges in legitimate traffic, such as during special events or promotions. Such spikes can lead to genuine requests being wrongly blocked, negatively affecting the user experience.</p>
<p>In a distributed system environment, ensuring consistent and effective rate limiting across multiple servers or geographic locations can be a complex task, requiring intelligent coordination.</p>
<p>Implementing rate limiting involves balancing security and usability. It&rsquo;s also important to consider the impact of the algorithm on both system performance and the user experience.</p>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Rate limiting is not a silver bullet against all security issues, but it is a fundamental part of a multi-layered defense strategy. It plays an essential role in maintaining system integrity, optimizing costs, and ensuring a seamless user experience. As technologies evolve, so too do the methods and strategies for effective rate limiting, making it a continually relevant topic for software engineers.</p>
]]></content:encoded></item><item><title>How to Move Files Between Git Repositories and Keep History</title><link>https://rdiachenko.com/posts/tools/git/moving-git-repo-files-preserving-change-history/</link><pubDate>Sat, 21 Oct 2023 17:28:29 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/tools/git/moving-git-repo-files-preserving-change-history/</guid><description>How to move files from one Git repository to another without losing commit history. Covers the most common scenarios and clean ways to do it.</description><content:encoded><![CDATA[<p>Sometimes, you realize that a common library is being used in a single project but resides in its own Git repository.
At other times, you may decide to merge a few Git repositories into one to reduce maintenance issues.
There are many other similar scenarios that involve moving a portion of one Git repository into another.
A common problem that unites all these cases is how to preserve the Git history for the moved files.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/tools/git/moving-git-repo-files-preserving-change-history/moving-files-between-git-repos-cover_hu_b682883345dc0db5.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="440">
            <source
                    srcset="https://rdiachenko.com/posts/tools/git/moving-git-repo-files-preserving-change-history/moving-files-between-git-repos-cover_hu_2d0fbfa0bb9cebc4.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="628">
            <img
                    src="https://rdiachenko.com/posts/tools/git/moving-git-repo-files-preserving-change-history/moving-files-between-git-repos-cover_hu_2d0fbfa0bb9cebc4.webp"
                    alt="Moving files between git repositories"
                    width="1200"
                    height="628"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Moving files between git repositories</small></figcaption></figure>
<p>The problem can be described as follows: given two Git repositories, source and target,
the task is to move content from source to target, either wholly or partially,
while preserving the Git change history.</p>
<h2 id="how-to-preserve-change-history">
How to preserve change history
<a href="#how-to-preserve-change-history" class="heading-anchor" aria-label="Anchor link for: How to preserve change history">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The main idea, as described in 
<a href="https://stackoverflow.com/a/11426261/1150425" target="_blank" rel="nofollow noopener">this source of wisdom</a>
,
is to create a patch file that includes all commits for the files you want to move.
Then, apply that patch to a target repository, as shown in the code snippet below.</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Go into the source git repo and create a patch.</span>
</span></span><span class="line"><span class="cl">git log --pretty<span class="o">=</span>email --reverse --binary --full-index <span class="se">\
</span></span></span><span class="line"><span class="cl">        --patch-with-stat --first-parent -m <span class="se">\
</span></span></span><span class="line"><span class="cl">        -- &lt;path-to-file-or-directory&gt; &gt; history.patch
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Go into the target git repo and apply the patch.</span>
</span></span><span class="line"><span class="cl">git am --committer-date-is-author-date <span class="se">\
</span></span></span><span class="line"><span class="cl">       &lt; &lt;path-to-source-repo-with-patch&gt;/history.patch</span></span></code></pre></div></div>
</div>
<p>Options used in the aforementioned commands:</p>
<ul>
<li><code>--pretty=email</code> - shows commit logs in the <code>email</code> format, which is required by <code>git am</code> to apply the patch.</li>
<li><code>--reverse</code> - lists commits in reverse order.</li>
<li><code>--binary</code> - includes a binary diff in the patch file.</li>
<li><code>--full-index</code> - shows the full hash for each commit on the &ldquo;index&rdquo; line when generating patch output.</li>
<li><code>--patch-with-stat</code> - generates a patch with diff statistics, which is a synonym for <code>-p --stat</code>.</li>
<li><code>--first-parent</code> - follows only the first parent commit when encountering a merge commit.</li>
<li><code>-m</code> - provides information for each commit, including merge commits. Without this option the individual changes introduced by merge commits are not shown.</li>
<li><code>-- &lt;path-to-file-or-directory&gt;</code> - shows only commits related to a specified file or directory.</li>
<li><code>--committer-date-is-author-date</code> - keeps the committer date the same as the author date to preserve the original timestamps of the changes.</li>
</ul>
<p>You may encounter some variations depending on the resulting directory structure where you want the files to be moved.
However, the approach is almost the same. To gain hands-on experience,
let&rsquo;s use two Git repositories and explore the main possible scenarios for moving files along with their change history.</p>
<h2 id="setting-up-the-example-repositories">
Setting up the example repositories
<a href="#setting-up-the-example-repositories" class="heading-anchor" aria-label="Anchor link for: Setting up the example repositories">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>I have created two Git repositories with their initial history. Here&rsquo;s the structure of the <code>source-git-repo</code> repository:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">source-git-repo
</span></span><span class="line"><span class="cl">└── app
</span></span><span class="line"><span class="cl">    ├── docs
</span></span><span class="line"><span class="cl">    │   └── HOWTO.txt
</span></span><span class="line"><span class="cl">    └── src
</span></span><span class="line"><span class="cl">        ├── main
</span></span><span class="line"><span class="cl">        │   └── java
</span></span><span class="line"><span class="cl">        │       └── App.java
</span></span><span class="line"><span class="cl">        └── test
</span></span><span class="line"><span class="cl">            └── java
</span></span><span class="line"><span class="cl">                └── AppTest.java</span></span></code></pre></div></div>
</div>
<p>And here&rsquo;s the structure of the <code>target-git-repo</code> repository:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">target-git-repo
</span></span><span class="line"><span class="cl">├── README.md
</span></span><span class="line"><span class="cl">└── app
</span></span><span class="line"><span class="cl">    └── src
</span></span><span class="line"><span class="cl">        └── main
</span></span><span class="line"><span class="cl">            ├── cpp
</span></span><span class="line"><span class="cl">            │   └── app.cpp
</span></span><span class="line"><span class="cl">            └── headers
</span></span><span class="line"><span class="cl">                └── app.h</span></span></code></pre></div></div>
</div>
<h2 id="moving-files-into-the-same-directory">
Moving files into the same directory
<a href="#moving-files-into-the-same-directory" class="heading-anchor" aria-label="Anchor link for: Moving files into the same directory">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Let&rsquo;s move <code>source-git-repo/app/src</code> into <code>target-git-repo/app/src</code>.</p>
<p>Create a patch specifically for a directory to be moved:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> source-git-repo
</span></span><span class="line"><span class="cl">git log --pretty<span class="o">=</span>email --reverse --binary --full-index <span class="se">\
</span></span></span><span class="line"><span class="cl">        --patch-with-stat --first-parent -m <span class="se">\
</span></span></span><span class="line"><span class="cl">        -- app/src &gt; history.patch</span></span></code></pre></div></div>
</div>
<p>Now, go to the target repository and apply the patch:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">cd</span> ../target-git-repo
</span></span><span class="line"><span class="cl"><span class="gp">$</span> git am --committer-date-is-author-date &lt; ../source-git-repo/history.patch
</span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; add simple App
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; remove redundant comments
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; add tests
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; remove java comments from tests
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; move to default package
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; fix compilation errors
</span></span></span></code></pre></div></div>
</div>
<p>If you check the target repository&rsquo;s structure, you&rsquo;ll see new files have appeared under the correct structure:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">target-git-repo
</span></span><span class="line"><span class="cl">├── README.md
</span></span><span class="line"><span class="cl">└── app
</span></span><span class="line"><span class="cl">    └── src
</span></span><span class="line"><span class="cl">        ├── main
</span></span><span class="line"><span class="cl">        │   ├── cpp
</span></span><span class="line"><span class="cl">        │   │   └── app.cpp
</span></span><span class="line"><span class="cl">        │   ├── headers
</span></span><span class="line"><span class="cl">        │   │   └── app.h
</span></span><span class="line"><span class="cl">        │   └── java
</span></span><span class="line"><span class="cl">        │       └── App.java
</span></span><span class="line"><span class="cl">        └── test
</span></span><span class="line"><span class="cl">            └── java
</span></span><span class="line"><span class="cl">                └── AppTest.java</span></span></code></pre></div></div>
</div>
<p>Let&rsquo;s also check the Git history for the target repository. Before the patch:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> git log --pretty<span class="o">=</span>oneline
</span></span><span class="line"><span class="cl"><span class="go">238ebaae28d50f0a5598ea6c82a2f10f47552a66 (HEAD -&gt; main)  target-git-repo -&gt; remove gradle and simplify repo
</span></span></span><span class="line"><span class="cl"><span class="go">a00279e8ab44f3623e9b79f8915ffe1592709e38  target-git-repo -&gt; init cpp app skeleton
</span></span></span></code></pre></div></div>
</div>
<p>After the patch:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> git log --pretty<span class="o">=</span>oneline
</span></span><span class="line"><span class="cl"><span class="go">21d445aab6ed57cda334021edac818f9d694d078 (HEAD -&gt; main)  source-git-repo -&gt; fix compilation errors
</span></span></span><span class="line"><span class="cl"><span class="go">16509a5d165e42637d4d34705a3d90f7087d2016  source-git-repo -&gt; move to default package
</span></span></span><span class="line"><span class="cl"><span class="go">051f615172de04db74869882d39183c8f2a5d2fd  source-git-repo -&gt; remove java comments from tests
</span></span></span><span class="line"><span class="cl"><span class="go">47b06a0a1df7cd252fa6d7904224081a40a82ced  source-git-repo -&gt; add tests
</span></span></span><span class="line"><span class="cl"><span class="go">c24f2d1faa637781d5e171904d3c78764dc1cee7  source-git-repo -&gt; remove redundant comments
</span></span></span><span class="line"><span class="cl"><span class="go">46220bc67b4a7121b396cd61e5650ca74862e776  source-git-repo -&gt; add simple App
</span></span></span><span class="line"><span class="cl"><span class="go">238ebaae28d50f0a5598ea6c82a2f10f47552a66  target-git-repo -&gt; remove gradle and simplify repo
</span></span></span><span class="line"><span class="cl"><span class="go">a00279e8ab44f3623e9b79f8915ffe1592709e38  target-git-repo -&gt; init cpp app skeleton
</span></span></span></code></pre></div></div>
</div>
<p>You can see that the commits from the source repository were applied on top of the existing history.
Therefore, we have successfully moved the files and preserved their change history in the target repository.</p>
<h2 id="moving-files-into-a-different-directory">
Moving files into a different directory
<a href="#moving-files-into-a-different-directory" class="heading-anchor" aria-label="Anchor link for: Moving files into a different directory">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Let&rsquo;s do the following file movement now: <code>source-git-repo/app/*</code> -&gt; <code>target-git-repo/app-java</code>.
You don&rsquo;t want the <code>app</code> directory to be present in <code>app-java</code>; instead,
you want all subdirectories and files from <code>app</code> to be directly present under <code>app-java</code>.</p>
<p>Let&rsquo;s create a patch for this scenario:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> source-git-repo
</span></span><span class="line"><span class="cl">git log --pretty<span class="o">=</span>email --reverse --binary --full-index <span class="se">\
</span></span></span><span class="line"><span class="cl">        --patch-with-stat --first-parent -m <span class="se">\
</span></span></span><span class="line"><span class="cl">        -- app &gt; history.patch</span></span></code></pre></div></div>
</div>
<p>Now, to apply it to the target repository and properly organize the new files, you need to use two additional options
<code>-p2</code> and <code>--directory</code>:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">cd</span> ../target-git-repo
</span></span><span class="line"><span class="cl"><span class="gp">$</span> git am -p2 --committer-date-is-author-date --directory app-java <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="go">       &lt; ../source-git-repo/history.patch
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; add simple App
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; remove redundant comments
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; add build.gradle
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; add tests
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; remove java comments from tests
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; move to default package
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; fix compilation errors
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; fix build errors
</span></span></span><span class="line"><span class="cl"><span class="go">Applying:  source-git-repo -&gt; simplify repo
</span></span></span></code></pre></div></div>
</div>
<p>Here, you are applying the patch while removing a leading path component <code>source-git-repo/app</code> (stripping directory information)
using the <code>-p2</code> option and placing the patched files into a subdirectory
named <code>target-git-repo/app-java</code> via the <code>--directory</code> option.</p>
<p>Take a look at the resulted structure of the target repository:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">target-git-repo
</span></span><span class="line"><span class="cl">├── README.md
</span></span><span class="line"><span class="cl">├── app
</span></span><span class="line"><span class="cl">│   └── src
</span></span><span class="line"><span class="cl">│       └── main
</span></span><span class="line"><span class="cl">│           ├── cpp
</span></span><span class="line"><span class="cl">│           │   └── app.cpp
</span></span><span class="line"><span class="cl">│           └── headers
</span></span><span class="line"><span class="cl">│               └── app.h
</span></span><span class="line"><span class="cl">└── app-java
</span></span><span class="line"><span class="cl">    ├── docs
</span></span><span class="line"><span class="cl">    │   └── HOWTO.txt
</span></span><span class="line"><span class="cl">    └── src
</span></span><span class="line"><span class="cl">        ├── main
</span></span><span class="line"><span class="cl">        │   └── java
</span></span><span class="line"><span class="cl">        │       └── App.java
</span></span><span class="line"><span class="cl">        └── test
</span></span><span class="line"><span class="cl">            └── java
</span></span><span class="line"><span class="cl">                └── AppTest.java</span></span></code></pre></div></div>
</div>
<p>As expected, all the content from <code>source-git-repo/app</code> is now available in <code>target-git-repo/app-java</code>.
The Git history contains all changes related to the moved files:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> git log --pretty<span class="o">=</span>oneline
</span></span><span class="line"><span class="cl"><span class="go">a770f378bced672290fc62da66145a9e3b073f37 (HEAD -&gt; main)  source-git-repo -&gt; simplify repo
</span></span></span><span class="line"><span class="cl"><span class="go">b16dd23121d56e9d0703a2ec5628f2c743207500  source-git-repo -&gt; fix build errors
</span></span></span><span class="line"><span class="cl"><span class="go">ae07d962403660b493d9a6454b0f8d0813015fb4  source-git-repo -&gt; fix compilation errors
</span></span></span><span class="line"><span class="cl"><span class="go">a2d1fe162ec9009f218031a3f49b9087683c6046  source-git-repo -&gt; move to default package
</span></span></span><span class="line"><span class="cl"><span class="go">fda2ef3b4d1c559f0ebb39eda3f61c7f62c68d81  source-git-repo -&gt; remove java comments from tests
</span></span></span><span class="line"><span class="cl"><span class="go">fd158342bc14c25db7aa7faba881e555012102a7  source-git-repo -&gt; add tests
</span></span></span><span class="line"><span class="cl"><span class="go">c7e626fafd5ad552f8f4ca0b0a038e8753498d89  source-git-repo -&gt; add build.gradle
</span></span></span><span class="line"><span class="cl"><span class="go">058a0331d922654b0c5209c3b3e7c227226a763f  source-git-repo -&gt; remove redundant comments
</span></span></span><span class="line"><span class="cl"><span class="go">6b85c90fbfd5ae978946e117b01188b99a487a86  source-git-repo -&gt; add simple App
</span></span></span><span class="line"><span class="cl"><span class="go">238ebaae28d50f0a5598ea6c82a2f10f47552a66  target-git-repo -&gt; remove gradle and simplify repo
</span></span></span><span class="line"><span class="cl"><span class="go">a00279e8ab44f3623e9b79f8915ffe1592709e38  target-git-repo -&gt; init cpp app skeleton
</span></span></span></code></pre></div></div>
</div>
<h2 id="moving-files-while-excluding-specific-paths">
Moving files while excluding specific paths
<a href="#moving-files-while-excluding-specific-paths" class="heading-anchor" aria-label="Anchor link for: Moving files while excluding specific paths">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Let&rsquo;s say you want to move <code>source-git-repo/app</code> into <code>target-git-repo/app</code>,
but you don&rsquo;t want <code>source-git-repo/app/src/test</code> to be included in the target repository.
This can be achieved using an exclusion string called <code>pathspec</code> during patch creation, as follows:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> source-git-repo
</span></span><span class="line"><span class="cl">git log --pretty<span class="o">=</span>email --reverse --binary --full-index <span class="se">\
</span></span></span><span class="line"><span class="cl">        --patch-with-stat --first-parent -m <span class="se">\
</span></span></span><span class="line"><span class="cl">        -- app <span class="s1">&#39;:!app/src/test&#39;</span> &gt; history.patch</span></span></code></pre></div></div>
</div>
<p>Where <code>-- app ':!app/src/test'</code> means: obtain all changes related to <code>app</code> directory but exclude all changes
under the <code>app/src/test</code> path.</p>
<p>Apply the patch:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> ../target-git-repo
</span></span><span class="line"><span class="cl">git am --committer-date-is-author-date <span class="se">\
</span></span></span><span class="line"><span class="cl">       &lt; ../source-git-repo/history.patch</span></span></code></pre></div></div>
</div>
<p>The structure of the target repository includes everything from <code>source-git-repo/app</code>,
excluding all files and subdirectories under <code>source-git-repo/app/src/test</code>:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">target-git-repo
</span></span><span class="line"><span class="cl">├── README.md
</span></span><span class="line"><span class="cl">└── app
</span></span><span class="line"><span class="cl">    ├── docs
</span></span><span class="line"><span class="cl">    │   └── HOWTO.txt
</span></span><span class="line"><span class="cl">    └── src
</span></span><span class="line"><span class="cl">        └── main
</span></span><span class="line"><span class="cl">            ├── cpp
</span></span><span class="line"><span class="cl">            │   └── app.cpp
</span></span><span class="line"><span class="cl">            ├── headers
</span></span><span class="line"><span class="cl">            │   └── app.h
</span></span><span class="line"><span class="cl">            └── java
</span></span><span class="line"><span class="cl">                └── App.java</span></span></code></pre></div></div>
</div>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The approach to move files while preserving their change history, as described in this post, is one of many.
It is not ideal and has its own drawbacks:</p>
<ul>
<li>It may be challenging to create a patch for files with a very large change history.</li>
<li>The change history for moved files is applied on top of the existing history in the target repository.
This can result in a lengthy list of commits related to the moved files on top of the current history,
making it challenging to navigate the current history.</li>
</ul>
<p>However, it gets the job done and can be considered a good starting point.
If you know of a better approach, I would be eager to learn more. Please share in the comments below.</p>
]]></content:encoded></item><item><title>Flaky Concurrent Tests Caused by Static Fields</title><link>https://rdiachenko.com/posts/java/flaky-behavior-in-concurrent-tests-caused-by-static-fields/</link><pubDate>Wed, 16 Aug 2023 20:55:04 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/java/flaky-behavior-in-concurrent-tests-caused-by-static-fields/</guid><description>A look at how shared static fields can introduce unpredictable behavior in concurrent tests and how to avoid it.</description><content:encoded><![CDATA[<p>What&rsquo;s wrong with this Java code?</p>
<div class="code-block" data-frame="editor">
        <div class="code-header">
                <span class="code-filename">App.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">App</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="n">Repo</span><span class="w"> </span><span class="n">repo</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">App</span><span class="p">(</span><span class="n">Repo</span><span class="w"> </span><span class="n">repo</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">this</span><span class="p">.</span><span class="na">repo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">repo</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="nf">greet</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">id</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">repo</span><span class="p">.</span><span class="na">getGreeting</span><span class="p">(</span><span class="n">id</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">Repo</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="nf">getGreeting</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">id</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">throw</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">UnsupportedOperationException</span><span class="p">(</span><span class="s">&#34;not implemented yet&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<h2 id="starting-with-a-unit-test">
Starting with a unit test
<a href="#starting-with-a-unit-test" class="heading-anchor" aria-label="Anchor link for: Starting with a unit test">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Let&rsquo;s try to test it. Below is a basic unit test that mocks an unimplemented <code>Repo</code> dependency and executes two tests.
These tests expect different greeting messages based on the provided greeting <code>id</code>.</p>
<div class="code-block" data-frame="editor">
        <div class="code-header">
                <span class="code-filename">AppTest.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Test</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.junit.jupiter.api.Assertions.assertEquals</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.mockito.Mockito.mock</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"> </span><span class="nn">org.mockito.Mockito.when</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">AppTest</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Repo</span><span class="w"> </span><span class="n">repo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">mock</span><span class="p">(</span><span class="n">Repo</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">App</span><span class="w"> </span><span class="n">app</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">App</span><span class="p">(</span><span class="n">repo</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">void</span><span class="w"> </span><span class="nf">greet_john</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">greeting</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;Hello, John!&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">when</span><span class="p">(</span><span class="n">repo</span><span class="p">.</span><span class="na">getGreeting</span><span class="p">(</span><span class="n">1</span><span class="p">)).</span><span class="na">thenReturn</span><span class="p">(</span><span class="n">greeting</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">assertEquals</span><span class="p">(</span><span class="n">greeting</span><span class="p">,</span><span class="w"> </span><span class="n">app</span><span class="p">.</span><span class="na">greet</span><span class="p">(</span><span class="n">1</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">void</span><span class="w"> </span><span class="nf">greet_mike</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">greeting</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;Hi, Mike!&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">when</span><span class="p">(</span><span class="n">repo</span><span class="p">.</span><span class="na">getGreeting</span><span class="p">(</span><span class="n">2</span><span class="p">)).</span><span class="na">thenReturn</span><span class="p">(</span><span class="n">greeting</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">assertEquals</span><span class="p">(</span><span class="n">greeting</span><span class="p">,</span><span class="w"> </span><span class="n">app</span><span class="p">.</span><span class="na">greet</span><span class="p">(</span><span class="n">2</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<h2 id="running-tests-sequentially">
Running tests sequentially
<a href="#running-tests-sequentially" class="heading-anchor" aria-label="Anchor link for: Running tests sequentially">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Executing it using 
<a href="https://junit.org/junit5/" target="_blank" rel="nofollow noopener">JUnit 5 testing framework</a>
 yields the expected outcome: both tests pass successfully.
By default, JUnit generates a new instance of the test class before executing each test method.
This process occurs sequentially, as illustrated in Figure 1.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/java/flaky-behavior-in-concurrent-tests-caused-by-static-fields/sequential-test-execution_hu_66e108d82046e2ee.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="795">
            <source
                    srcset="https://rdiachenko.com/posts/java/flaky-behavior-in-concurrent-tests-caused-by-static-fields/sequential-test-execution_hu_e18bf3ce7a49afc3.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="1136">
            <img
                    src="https://rdiachenko.com/posts/java/flaky-behavior-in-concurrent-tests-caused-by-static-fields/sequential-test-execution_hu_e18bf3ce7a49afc3.webp"
                    alt="Sequential test execution"
                    width="1200"
                    height="1136"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Sequential test execution</small></figcaption></figure>
<p>Before executing <code>greet_hohn</code>, a new instance of <code>AppTest</code> is created.
A mock <code>Repo</code> object is then generated and assigned to the static field <code>App.repo</code>.
The test is executed, during which the mock is enhanced with a stub that indirectly returns the actual value.
This value is compared with expected greeting message, test passed.</p>
<p>JUnit proceeds to <code>greet_mike</code> and follows a similar process.
It creates a new instance of <code>AppTest</code>, resets the <code>App.repo</code> field to a new mock,
executes the test, adds a stub, verifies the obtained greeting message, and we are done, the test is green.</p>
<h2 id="switching-to-concurrent-execution">
Switching to concurrent execution
<a href="#switching-to-concurrent-execution" class="heading-anchor" aria-label="Anchor link for: Switching to concurrent execution">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>There are way more tests in real projects.
Leveraging concurrent execution can accelerate the testing phase.
Let&rsquo;s execute the aforementioned tests concurrently now. To achieve this,
we must override the subsequent properties and provide them to JUnit.</p>
<div class="code-block" data-frame="editor">
        <div class="code-header">
                <span class="code-filename">junit-platform.properties</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="c1"># Enable concurrent test execution</span>
</span></span><span class="line"><span class="cl"><span class="na">junit.jupiter.execution.parallel.enabled</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run all test methods within a class concurrently</span>
</span></span><span class="line"><span class="cl"><span class="na">junit.jupiter.execution.parallel.mode.default</span><span class="o">=</span><span class="s">concurrent</span></span></span></code></pre></div></div>
</div>
<h2 id="welcome-to-the-world-of-flaky-tests">
Welcome to the world of flaky tests
<a href="#welcome-to-the-world-of-flaky-tests" class="heading-anchor" aria-label="Anchor link for: Welcome to the world of flaky tests">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The test methods within <code>AppTest</code> are executed concurrently now. If we attempt to run the tests multiple times,
we may encounter a situation where one of them succeeds while another one fails.
The outcome could differ from one run to another, but you will certainly experience either of the following.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">expected: &lt;Hello, John!&gt; but was: &lt;null&gt;
</span></span><span class="line"><span class="cl">Expected :Hello, John!
</span></span><span class="line"><span class="cl">Actual   :null</span></span></code></pre></div></div>
</div>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">expected: &lt;Hi, Mike!&gt; but was: &lt;null&gt;
</span></span><span class="line"><span class="cl">Expected :Hi, Mike!
</span></span><span class="line"><span class="cl">Actual   :null</span></span></code></pre></div></div>
</div>
<h2 id="static-fields-and-shared-state">
Static fields and shared state
<a href="#static-fields-and-shared-state" class="heading-anchor" aria-label="Anchor link for: Static fields and shared state">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>It appears that we have entered the realm of flaky tests. To understand why this occurs,
let&rsquo;s examine the sequence of execution and the state of the <code>App.repo</code> field when each of the tests
is executed concurrently, as depicted in Figure 2.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/java/flaky-behavior-in-concurrent-tests-caused-by-static-fields/concurrent-test-execution-cover_hu_82521185def84a04.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="641">
            <source
                    srcset="https://rdiachenko.com/posts/java/flaky-behavior-in-concurrent-tests-caused-by-static-fields/concurrent-test-execution-cover_hu_6f393975bdf38e09.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="916">
            <img
                    src="https://rdiachenko.com/posts/java/flaky-behavior-in-concurrent-tests-caused-by-static-fields/concurrent-test-execution-cover_hu_6f393975bdf38e09.webp"
                    alt="Concurrent test execution"
                    width="1200"
                    height="916"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. Concurrent test execution</small></figcaption></figure>
<p>JUnit detects the presence of two tests. Initially, it creates an instance of <code>AppTest</code> for <code>greet_john</code>.
Consequently, the <code>App.repo</code> static field points to the mock established within the <code>greet_john</code> instance.
However, JUnit does not execute the test at this point; instead, it proceeds to create another instance of <code>AppTest</code>
for <code>greet_mike</code>. This action overwrites the reference in the <code>App.repo</code> with the one originating
from the <code>greet_mike</code> instance. As a result, any modifications made to the <code>greet_john</code> repo mock
will not be reflected in the <code>App.repo</code> static field.</p>
<p>With the preparations complete, JUnit advances to concurrently executing both tests.
In the case of <code>greet_john</code>, a stub is added into a local mock,
yet this modification isn&rsquo;t propagated to <code>App.repo</code>. As a result, the obtained greeting message is <code>null</code>,
leading to a test failure. Conversely, in the case of <code>greet_mike</code>,
the created stub is visible from the <code>App.repo</code> field, resulting in the expected greeting message and a successful test outcome.</p>
<p>Do you see the issue? Due to <code>App</code> having <code>repo</code> as a static field,
that field is global across all instances of <code>App</code>. Hence,
whichever instance of <code>AppTest</code> happens to be the most recent one in assigning a reference to a mock to that static field
will have its mock utilized by all test methods across all threads uniformly.</p>
<h2 id="how-to-fix-the-flakiness-without-touching-main-code">
How to fix the flakiness without touching main code
<a href="#how-to-fix-the-flakiness-without-touching-main-code" class="heading-anchor" aria-label="Anchor link for: How to fix the flakiness without touching main code">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The evident solution would involve refactoring the <code>App</code> code to eliminate the usage of a static field, as shown below.</p>
<div class="code-block" data-frame="editor">
        <div class="code-header">
                <span class="code-filename">App.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">App</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Repo</span><span class="w"> </span><span class="n">repo</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">App</span><span class="p">(</span><span class="n">Repo</span><span class="w"> </span><span class="n">repo</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">this</span><span class="p">.</span><span class="na">repo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">repo</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="nf">greet</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">id</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">repo</span><span class="p">.</span><span class="na">getGreeting</span><span class="p">(</span><span class="n">id</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>However, there are cases where achieving this can prove challenging,
particularly within large codebases with legacy code and complex logic.
If direct modification of the main codebase is unattainable, it remains feasible to adjust the test itself.
However, the benefits obtained from such adjustments should surpass the effort and maintenance investment involved.</p>
<div class="code-block" data-frame="editor">
        <div class="code-header">
                <span class="code-filename">AppTest.java</span>
        </div>
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@TestInstance</span><span class="p">(</span><span class="n">TestInstance</span><span class="p">.</span><span class="na">Lifecycle</span><span class="p">.</span><span class="na">PER_CLASS</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nd">@Execution</span><span class="p">(</span><span class="n">ExecutionMode</span><span class="p">.</span><span class="na">CONCURRENT</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">AppTest</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Repo</span><span class="w"> </span><span class="n">repo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">mock</span><span class="p">(</span><span class="n">Repo</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">App</span><span class="w"> </span><span class="n">app</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">App</span><span class="p">(</span><span class="n">repo</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@BeforeEach</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">void</span><span class="w"> </span><span class="nf">before</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">when</span><span class="p">(</span><span class="n">repo</span><span class="p">.</span><span class="na">getGreeting</span><span class="p">(</span><span class="n">1</span><span class="p">)).</span><span class="na">thenReturn</span><span class="p">(</span><span class="s">&#34;Hello, John!&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">when</span><span class="p">(</span><span class="n">repo</span><span class="p">.</span><span class="na">getGreeting</span><span class="p">(</span><span class="n">2</span><span class="p">)).</span><span class="na">thenReturn</span><span class="p">(</span><span class="s">&#34;Hi, Mike!&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">void</span><span class="w"> </span><span class="nf">greet_john</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">assertEquals</span><span class="p">(</span><span class="s">&#34;Hello, John!&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">app</span><span class="p">.</span><span class="na">greet</span><span class="p">(</span><span class="n">1</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">void</span><span class="w"> </span><span class="nf">greet_mike</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">assertEquals</span><span class="p">(</span><span class="s">&#34;Hi, Mike!&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">app</span><span class="p">.</span><span class="na">greet</span><span class="p">(</span><span class="n">2</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>The usage of <code>TestInstance.Lifecycle.PER_CLASS</code> instructs JUnit to create a new test instance once per test class.
In our scenario, this means that a single <code>AppTest</code> instance will be created for both tests,
preventing any overwriting of the <code>App.repo</code> field.</p>
<p>The multi-threaded execution mode should be explicitly configured using <code>ExecutionMode.CONCURRENT</code>
when utilizing <code>@TestInstance(PER_CLASS)</code>. Failing to do so will result in the sequential execution
of test methods within the same thread.</p>
<h2 id="thread-safety-in-mocking-libraries">
Thread safety in mocking libraries
<a href="#thread-safety-in-mocking-libraries" class="heading-anchor" aria-label="Anchor link for: Thread safety in mocking libraries">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>The mocking library we use 
<a href="https://github.com/mockito/mockito/wiki/FAQ#is-mockito-thread-safe" target="_blank" rel="nofollow noopener">is not thread-safe</a>

as mentioned in the Mockito FAQ:</p>
<blockquote>
<p>For healthy scenarios Mockito plays nicely with threads. For instance, you can run tests in parallel to speed up the build.
Also, you can let multiple threads call methods on a shared mock to test in concurrent conditions.
Check out a <code>timeout()</code> feature for testing concurrency.</p>
</blockquote>
<blockquote>
<p>However Mockito is only thread-safe in healthy tests, that is tests without multiple threads
stubbing/verifying a shared mock. Stubbing or verification of a shared mock from different threads is
NOT the proper way of testing because it will always lead to intermittent behavior.
In general, mutable state + assertions in multi-threaded environment lead to random results.
If you do stub/verify a shared mock across threads you will face occasional exceptions like:
<code>WrongTypeOfReturnValue</code>, etc.</p>
</blockquote>
<p>And indeed, when we execute the tests, one or both of them might randomly fail.
To address this problem, we establish all stubs within the <code>before()</code> method,
which is executed prior to each test method. However, adopting this approach brings its own set of challenges:</p>
<ul>
<li>Ambiguity arises when certain stubs are intended for exclusive use in a single test method but inadvertently influence other tests.</li>
<li>Conflicts emerge when multiple stubs are required for the same input.</li>
</ul>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Give particular consideration to static fields within the classes being tested.
Should you encounter flaky behavior during concurrent test execution,
these fields could serve as a solid starting point for investigation.</p>
<p>It is feasible to fix unreliable tests without refactoring the main application logic.
However, this process can be intricate and error-prone, albeit potentially acceptable for short-term resolutions.
For a more sustainable solution, consider moving towards non-static fields where applicable.</p>
]]></content:encoded></item><item><title>How Telegram Bots Work</title><link>https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/</link><pubDate>Thu, 03 Aug 2023 20:46:32 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/</guid><description>What Telegram bots are, how they work behind the scenes, and what you need to build your own from scratch.</description><content:encoded><![CDATA[<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/telegram-cyberpunk-bot-cover_hu_50904fe3b9b4e5ac.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="756">
            <source
                    srcset="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/telegram-cyberpunk-bot-cover_hu_a389d5a4997e04cf.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="1079">
            <img
                    src="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/telegram-cyberpunk-bot-cover_hu_a389d5a4997e04cf.webp"
                    alt="Telegram bot in cyberpunk universe"
                    width="1200"
                    height="1079"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Telegram bot in cyberpunk universe</small></figcaption></figure>
<p>You may have heard about Telegram bots or even use them on a daily basis; however, for many people, they seem like small pieces of magic that somehow accomplish tasks. The goal of this post is to grasp the technical side of the Telegram system from a bot’s perspective, to examine how chatbots communicate with other system components, and to explore what is required to build one.</p>
<h2 id="what-is-a-telegram-chatbot">
What is a Telegram chatbot?
<a href="#what-is-a-telegram-chatbot" class="heading-anchor" aria-label="Anchor link for: What is a Telegram chatbot?">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>A Telegram bot is a special account that can receive and respond to updates from real users, groups, or channels. It is a piece of software that runs on a remote server and processes received updates. A bot is a small program that can be embedded in Telegram chats and perform specific functions.</p>
<p>Telegram bots can be used for a wide range of purposes, limited only by common sense. Primarily, they help to automate routine tasks such as receiving updates about upcoming weather or your favorite RSS feed, as well as sending reminders for various activities. Big companies utilize bots to offer support features to their users.</p>
<p>Bots are also helpful in managing groups or channels with a large number of participants, as they can assist in removing spam messages and blocking troublesome users. Furthermore, you can even create small games and play with your friends, with the bot taking care of all the management tasks.</p>
<h2 id="overview-of-telegram-system-components">
Overview of Telegram system components
<a href="#overview-of-telegram-system-components" class="heading-anchor" aria-label="Anchor link for: Overview of Telegram system components">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Telegram is a centralized instant messaging service where clients need to communicate with Telegram servers to exchange messages with other clients. This communication occurs through the 
<a href="https://en.wikipedia.org/wiki/Telegram_%28software%29#Encryption_scheme" target="_blank" rel="nofollow noopener">MTProto - Telegram&rsquo;s encryption protocol</a>
. encryption protocol, which was designed and built by Telegram engineers.</p>
<p>Thankfully, you don’t have to learn the intricacies of this protocol to create a bot. The Telegram team has developed an intermediary server that conceals the complexity of MTProto and handles all encryption and communication with the Telegram API on your behalf. They named it Telegram Bot API, and it offers a straightforward REST API over HTTPS for receiving and sending updates to client applications.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/telegram-bot-components_hu_eafd32341de1e7ef.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="569">
            <source
                    srcset="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/telegram-bot-components_hu_5f5c93b7d11d84a6.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="813">
            <img
                    src="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/telegram-bot-components_hu_5f5c93b7d11d84a6.webp"
                    alt="Telegram system components"
                    width="1200"
                    height="813"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. Telegram system components</small></figcaption></figure>
<p>Now, let&rsquo;s explore the main Telegram components and their interactions as depicted in the figure above.</p>
<h3 id="telegram-api-vs-telegram-bot-api">
Telegram API vs. Telegram bot API
<a href="#telegram-api-vs-telegram-bot-api" class="heading-anchor" aria-label="Anchor link for: Telegram API vs. Telegram bot API">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The Telegram API serves as the primary entry point for all Telegram clients. If you wish to develop a new client, you must grasp the MTProto protocol and utilize the 
<a href="https://core.telegram.org/api#telegram-api" target="_blank" rel="nofollow noopener">Telegram API</a>
 for communication. On the other hand, the 
<a href="https://core.telegram.org/bots/api" target="_blank" rel="nofollow noopener">Telegram Bot API</a>
 offers a simplified version of the Telegram API specifically tailored for bots. In most cases, this Bot API should be the preferred choice for implementing bots.</p>
<p>Although it is not mandatory, you can choose to implement a bot using the MTProto API directly (option 1 in Figure 2) instead of going through the Bot API (option 2 in Figure 2). This approach offers certain advantages, such as the ability to retrieve a message by its ID, which the Bot API doesn’t allow. However, it may be overkilling for your use case and could lead to an increase in overall complexity. Additionally, using the MTProto API directly doesn’t provide access to many other features supported by the Bot API, such as webhooks and simpler message formatting.</p>
<h3 id="what-is-a-telegram-client">
What is a Telegram client?
<a href="#what-is-a-telegram-client" class="heading-anchor" aria-label="Anchor link for: What is a Telegram client?">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>A Telegram client is any application approved by Telegram that engages in communication with servers and other clients, enhancing customer support and automation. If you intend to create a new client, whether it’s for desktop or mobile, you need to 
<a href="https://core.telegram.org/api/obtaining_api_id" target="_blank" rel="nofollow noopener">obtain the api_id and api_hash</a>
 and utilize the MTProto Telegram API to integrate with Telegram servers.</p>
<h3 id="meet-botfather-the-gateway-to-bot-creation">
Meet BotFather: the gateway to bot creation
<a href="#meet-botfather-the-gateway-to-bot-creation" class="heading-anchor" aria-label="Anchor link for: Meet BotFather: the gateway to bot creation">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>This is a super bot that serves as both the starting point for any new bot and a management tool for all your bots. If you wish to create a new bot, you must first contact BotFather to register your bot and obtain a token, as shown in the figure below. This token is necessary for using the Bot API.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/bot-registration-via-bot-father_hu_ce365583c70ee47b.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="672">
            <source
                    srcset="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/bot-registration-via-bot-father_hu_8c2197eabcbb2f18.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="960">
            <img
                    src="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/bot-registration-via-bot-father_hu_8c2197eabcbb2f18.webp"
                    alt="Registering a new bot on Telegram and obtaining a token via BotFather"
                    width="1200"
                    height="960"
                    loading="lazy">
        </picture><figcaption><small>Figure 3. Registering a new bot on Telegram and obtaining a token via BotFather</small></figcaption></figure>
<h3 id="the-bot-itself-software-behind-the-scenes">
The bot itself: software behind the scenes
<a href="#the-bot-itself-software-behind-the-scenes" class="heading-anchor" aria-label="Anchor link for: The bot itself: software behind the scenes">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>It’s the actual software you write and run. Essentially, it is code that interacts with the Telegram Bot API. The API provides a list of updates received from users, groups, or channels, and allows you to respond to users with messages as if the bot were a real user.</p>
<h2 id="key-components-of-a-telegram-bot">
Key components of a Telegram bot
<a href="#key-components-of-a-telegram-bot" class="heading-anchor" aria-label="Anchor link for: Key components of a Telegram bot">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>To authenticate each request to the Bot API endpoint, a bot must use a token provided by the BotFather. The endpoint URL follows the format <code>https://api.telegram.org/bot&lt;token&gt;/METHOD_NAME</code>.</p>
<p>Here&rsquo;s an example of calling <code>getMe</code> to test a token and retrieve basic information about the bot:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># This is a fake token, use your own instead ;)</span>
</span></span><span class="line"><span class="cl"><span class="nv">BOT_TOKEN</span><span class="o">=</span>123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">curl -s https://api.telegram.org/bot<span class="nv">$BOT_TOKEN</span>/getMe <span class="p">|</span> jq <span class="s1">&#39;.&#39;</span></span></span></code></pre></div></div>
</div>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;ok&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;result&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="mi">123456</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;is_bot&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;first_name&#34;</span><span class="p">:</span> <span class="s2">&#34;Test&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;username&#34;</span><span class="p">:</span> <span class="s2">&#34;test_bot&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;can_join_groups&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;can_read_all_group_messages&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;supports_inline_queries&#34;</span><span class="p">:</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>A bot receives a list of <code>Update</code> objects from Telegram users and responds to those updates by sending <code>Message</code> objects via the Telegram Bot API. These two objects are essential and will be used extensively in your bot development. Let&rsquo;s execute a few requests and take a closer look at how these objects look like.</p>
<p>Here&rsquo;s an example of retrieving an update from a test channel using a Telegram bot:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Limit a number of updates to one</span>
</span></span><span class="line"><span class="cl">curl -s https://api.telegram.org/bot<span class="nv">$BOT_TOKEN</span>/getUpdates<span class="se">\?</span><span class="nv">limit</span><span class="o">=</span><span class="m">1</span> <span class="p">|</span> jq <span class="s1">&#39;.&#39;</span></span></span></code></pre></div></div>
</div>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;ok&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;result&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;update_id&#34;</span><span class="p">:</span> <span class="mi">589880615</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;channel_post&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;message_id&#34;</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;sender_chat&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="mi">-1001870001123</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Test channel&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;channel&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;chat&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="mi">-1001870001123</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Test channel&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;channel&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;date&#34;</span><span class="p">:</span> <span class="mi">1690960211</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;text&#34;</span><span class="p">:</span> <span class="s2">&#34;Hey bot!&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>And this is how we can send a message to a test channel:</p>
<div class="code-block" data-frame="terminal">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s -X POST <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s1">&#39;Content-Type: application/json&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{&#34;chat_id&#34;: &#34;-1001870001123&#34;, &#34;text&#34;: &#34;Hey folks in the channel!&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  https://api.telegram.org/bot<span class="nv">$BOT_TOKEN</span>/sendMessage <span class="p">|</span> jq <span class="s1">&#39;.&#39;</span></span></span></code></pre></div></div>
</div>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;ok&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;result&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;message_id&#34;</span><span class="p">:</span> <span class="mi">205</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;sender_chat&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="mi">-1001870001123</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Test channel&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;channel&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;chat&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="mi">-1001870001123</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Test channel&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;channel&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;date&#34;</span><span class="p">:</span> <span class="mi">1691003950</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;text&#34;</span><span class="p">:</span> <span class="s2">&#34;Hey folks in the channel!&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</div>
<p>There are two approaches to obtaining updates from the Bot API. You can either use Webhooks or long polling, as described below. It’s not possible to use both simultaneously; if Webhook is set, no updates can be obtained via long polling.</p>
<h3 id="webhooks-when-telegram-pushes-updates-to-you">
Webhooks: when Telegram pushes updates to you
<a href="#webhooks-when-telegram-pushes-updates-to-you" class="heading-anchor" aria-label="Anchor link for: Webhooks: when Telegram pushes updates to you">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>As shown in Figure 4, you must create a POST endpoint and request the Telegram server to register it using the <code>setWebhook</code> method. This setup only needs to be done once. The endpoint will be called each time there is a new update for a bot.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/bot-communication-via-webhook_hu_fcc6138ad5142e0e.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="472">
            <source
                    srcset="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/bot-communication-via-webhook_hu_1b0911553f8a968b.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="674">
            <img
                    src="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/bot-communication-via-webhook_hu_1b0911553f8a968b.webp"
                    alt="Bot receiving updates via registered Webhook"
                    width="1200"
                    height="674"
                    loading="lazy">
        </picture><figcaption><small>Figure 4. Bot receiving updates via registered Webhook</small></figcaption></figure>
<p>Upon receiving a POST request with an update, a bot can either:</p>
<ul>
<li>Reply to this request, so the user will receive a reply message.</li>
<li>Ignore responding and send a message to the user via a separate API call.</li>
</ul>
<p>Using Webhook is a good approach if you want to save some CPU resources and achieve a better response time compared to long polling. However, it’s important to note that Telegram only supports HTTPS hooks, so you’ll need a valid SSL certificate to make it work. Using this method for prototyping and running a bot on localhost can make it even more expensive.</p>
<h3 id="long-polling-let-your-bot-ask-for-updates">
Long polling: let your bot ask for updates
<a href="#long-polling-let-your-bot-ask-for-updates" class="heading-anchor" aria-label="Anchor link for: Long polling: let your bot ask for updates">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>The alternative to Webhook is long polling, as shown in Figure 5. A bot initiates an HTTP request using the <code>getUpdates</code> method and waits for the server to respond. Whenever there is an update, the Telegram server responds with a list of new updates. The bot then reestablishes the connection, sends a new <code>getUpdates</code> request with the last update offset, and waits for new data.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/bot-communication-via-long-polling_hu_174d4a443cc682d4.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="372">
            <source
                    srcset="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/bot-communication-via-long-polling_hu_d3e4b5b15fec94e4.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="531">
            <img
                    src="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/bot-communication-via-long-polling_hu_d3e4b5b15fec94e4.webp"
                    alt="Bot receiving updates via long polling"
                    width="1200"
                    height="531"
                    loading="lazy">
        </picture><figcaption><small>Figure 5. Bot receiving updates via long polling</small></figcaption></figure>
<p>This is what official documentation says about <code>offset</code> parameter of the <code>getUpdates</code> method:</p>
<blockquote>
<p>Identifier of the first update to be returned. Must be greater by one than the highest among the identifiers of previously received updates.
By default, updates starting with the earliest unconfirmed update are returned. An update is considered confirmed as
soon as <code>getUpdates</code> is called with an offset higher than its <code>update_id</code>. The negative offset can be specified
to retrieve updates starting from <code>-offset</code> update from the end of the updates queue. All previous updates will be forgotten.</p>
</blockquote>
<p>To avoid receiving updates that the bot has already processed, it needs to keep track of the last seen update and request only new updates by providing a proper offset when calling <code>getUpdates</code> as follows: <code>offset = update_id of the last processed update + 1</code>.</p>
<p>Long polling is an excellent method that works best during the development phase. It doesn&rsquo;t require any pre-setup like Webhook and a dedicated remote server. You can simply run your bot on localhost and test it right away.</p>
<h3 id="using-client-libraries-to-simplify-bot-development">
Using client libraries to simplify bot development
<a href="#using-client-libraries-to-simplify-bot-development" class="heading-anchor" aria-label="Anchor link for: Using client libraries to simplify bot development">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h3>
<p>You can use the Telegram Bot API directly; however, many client 
<a href="https://core.telegram.org/bots/samples" target="_blank" rel="nofollow noopener">libraries</a>
 have been built to simplify that interaction. There are multiple options available and actively supported for each mainstream programming language.</p>
<p>The beauty of such libraries is that they abstract the interaction part and provide you with methods and objects to work with. You don’t need to focus on how to properly implement long polling or parse received JSON; instead, you focus on the business logic of your bot.</p>
<h2 id="steps-to-build-a-telegram-bot">
Steps to build a Telegram bot
<a href="#steps-to-build-a-telegram-bot" class="heading-anchor" aria-label="Anchor link for: Steps to build a Telegram bot">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>So, what do you need to create bots? Here&rsquo;s a checklist to follow:</p>
<ol>
<li>Register a new bot with the BotFather by executing the <code>/newbot</code> command and providing a name and username for the bot.</li>
<li>BotFather will give you a token, which you&rsquo;ll use for communication with the Telegram Bot API. Now you have two options to proceed:
<ol>
<li>Call the Telegram Bot API directly.</li>
<li>Use a bot client library to call the Bot API.</li>
</ol>
</li>
<li>Write the bot&rsquo;s logic: process updates and respond with messages.</li>
<li>Run and test the bot on localhost. Using long polling allows you to interact with real Telegram servers even while the bot is running locally.</li>
<li>Deploy the bot to a remote server and set up a Webhook if needed. You may decide to continue using long polling, which is also fine for many use cases.</li>
<li>Manage the bot via the BotFather: set description, register commands, regenerate token, set up a payment provider, delete the bot, etc.</li>
</ol>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/bot-creation-steps_hu_50e774d0ed2dfa1a.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="538">
            <source
                    srcset="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/bot-creation-steps_hu_1a4cf5a7b6a0aee3.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="769">
            <img
                    src="https://rdiachenko.com/posts/bots/telegram/how-do-telegram-bots-work/bot-creation-steps_hu_1a4cf5a7b6a0aee3.webp"
                    alt="Steps to create a bot"
                    width="1200"
                    height="769"
                    loading="lazy">
        </picture><figcaption><small>Figure 6. Steps to create a bot</small></figcaption></figure>
<h2 id="limitations-to-keep-in-mind-when-developing-bots">
Limitations to keep in mind when developing bots
<a href="#limitations-to-keep-in-mind-when-developing-bots" class="heading-anchor" aria-label="Anchor link for: Limitations to keep in mind when developing bots">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>There are a few 
<a href="https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this" target="_blank" rel="nofollow noopener">limitations applied by Telegram</a>
 that you should keep in mind when developing a bot:</p>
<ul>
<li>1 message per second when sending messages inside a particular chat.</li>
<li>30 messages per second when sending bulk notifications to multiple users.</li>
<li>20 messages per minute when sending messages to the same group.</li>
</ul>
<p>However, these are considered soft limits rather than hard ones and may be subject to change in the future.</p>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>We have now examined the main components of the Telegram system and explored the role of bots, as well as how they function and interact with users. Bots are not magical entities; they are simply code running on the server, responding to user updates.</p>
<p>The next step in our journey is to delve into the Telegram Bot API, where we’ll create a small bot and gain hands-on experience by utilizing the features provided by Telegram. This will involve working with commands, callback queries, keyboards, and setting up a bot for a Telegram group or channel. So, stay tuned, and see you in the next post!</p>
]]></content:encoded></item><item><title>Reclaiming Disk Space in PostgreSQL After DELETE</title><link>https://rdiachenko.com/posts/databases/postgresql/postgresql-does-not-free-up-physical-space-after-delete/</link><pubDate>Wed, 17 May 2023 21:57:36 +0100</pubDate><author>ruslan@rdiachenko.com (Ruslan Diachenko)</author><guid>https://rdiachenko.com/posts/databases/postgresql/postgresql-does-not-free-up-physical-space-after-delete/</guid><description>How PostgreSQL handles disk space after DELETE operations and what you can do to actually free up storage and keep things efficient.</description><content:encoded><![CDATA[<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/databases/postgresql/postgresql-does-not-free-up-physical-space-after-delete/initial-table-with-index-cover_hu_97831fe1014c8d83.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="562">
            <source
                    srcset="https://rdiachenko.com/posts/databases/postgresql/postgresql-does-not-free-up-physical-space-after-delete/initial-table-with-index-cover_hu_7d195236a6cfeeb3.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="803">
            <img
                    src="https://rdiachenko.com/posts/databases/postgresql/postgresql-does-not-free-up-physical-space-after-delete/initial-table-with-index-cover_hu_7d195236a6cfeeb3.webp"
                    alt="Example of initial table with corresponding index"
                    width="1200"
                    height="803"
                    loading="lazy">
        </picture><figcaption><small>Figure 1. Example of initial table with corresponding index</small></figcaption></figure>
<p>The above is a simple table with a corresponding index that we are going to use to describe
the data deletion flow using two methods: <code>DELETE</code> and <code>TRUNCATE</code>,
and how those methods affect physical space after completion.</p>
<h2 id="how-postgresql-handles-disk-space-after-delete-and-truncate">
How PostgreSQL handles disk space after DELETE and TRUNCATE
<a href="#how-postgresql-handles-disk-space-after-delete-and-truncate" class="heading-anchor" aria-label="Anchor link for: How PostgreSQL handles disk space after DELETE and TRUNCATE">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>There are scenarios where it becomes necessary to occasionally remove all the data from a table.
To achieve this, you can use either <code>DELETE</code> or <code>TRUNCATE</code> operation.</p>
<p><code>DELETE</code> operation may be more favorable when there are frequent queries against the table that is being cleaned up.
That&rsquo;s because data is removed without waiting for all transactions to complete,
allowing them to still access the table rows which were marked for deletion.</p>
<p>In comparison, <code>TRUNCATE</code> operation 
<a href="https://wiki.postgresql.org/wiki/MVCC_violations" target="_blank" rel="nofollow noopener">violates the strict MVCC (multiversion concurrency control) semantics</a>
.
This may lead to inconsistency between the contents of the truncated table and other tables in the database.</p>
<p>A problem arises when dealing with tables containing gigabytes of data.
PostgreSQL does not release the freed memory back to the operating system after <code>DELETE</code> operation.
To make it happen, you have to request it explicitly through the execution of <code>VACUUM FULL</code>, as shown in Figure 2.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/databases/postgresql/postgresql-does-not-free-up-physical-space-after-delete/delete-with-vacuum-full-flow_hu_8b68c5644e907fac.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="581">
            <source
                    srcset="https://rdiachenko.com/posts/databases/postgresql/postgresql-does-not-free-up-physical-space-after-delete/delete-with-vacuum-full-flow_hu_c06247493ea214bc.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="830">
            <img
                    src="https://rdiachenko.com/posts/databases/postgresql/postgresql-does-not-free-up-physical-space-after-delete/delete-with-vacuum-full-flow_hu_c06247493ea214bc.webp"
                    alt="DELETE with VACUUM FULL operations flow"
                    width="1200"
                    height="830"
                    loading="lazy">
        </picture><figcaption><small>Figure 2. DELETE with VACUUM FULL operations flow</small></figcaption></figure>
<p><code>TRUNCATE</code> operation, on the other hand, immediately reclaims physical memory after completion, as shown in Figure 3.</p>
<figure >
        <picture>
            <source
                    srcset="https://rdiachenko.com/posts/databases/postgresql/postgresql-does-not-free-up-physical-space-after-delete/truncate-flow_hu_6453c83892635ccc.webp"
                    media="(max-width: 768px)"
                    width="840"
                    height="297">
            <source
                    srcset="https://rdiachenko.com/posts/databases/postgresql/postgresql-does-not-free-up-physical-space-after-delete/truncate-flow_hu_497cd1fd401d01d5.webp"
                    media="(min-width: 769px)"
                    width="1200"
                    height="424">
            <img
                    src="https://rdiachenko.com/posts/databases/postgresql/postgresql-does-not-free-up-physical-space-after-delete/truncate-flow_hu_497cd1fd401d01d5.webp"
                    alt="TRUNCATE operation flow"
                    width="1200"
                    height="424"
                    loading="lazy">
        </picture><figcaption><small>Figure 3. TRUNCATE operation flow</small></figcaption></figure>
<h2 id="how-storage-size-changes-after-delete-and-truncate">
How storage size changes after DELETE and TRUNCATE
<a href="#how-storage-size-changes-after-delete-and-truncate" class="heading-anchor" aria-label="Anchor link for: How storage size changes after DELETE and TRUNCATE">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Let&rsquo;s create a table and compare the storage space before and after applying both operations.</p>
<p>First, create initial table with some data and check its size:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">books</span><span class="w"> </span><span class="p">(</span><span class="n">id</span><span class="w"> </span><span class="nb">serial</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w"> </span><span class="n">title</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="w"> </span><span class="p">(</span><span class="mi">255</span><span class="p">)</span><span class="w"> </span><span class="k">UNIQUE</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">books</span><span class="w"> </span><span class="p">(</span><span class="n">title</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;Software Architecture: The Hard Parts&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">books</span><span class="w"> </span><span class="p">(</span><span class="n">title</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;Fundamentals of Software Architecture&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">books</span><span class="w"> </span><span class="p">(</span><span class="n">title</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;High Output Management&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">books</span><span class="w"> </span><span class="p">(</span><span class="n">title</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;The History of Philosophy&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">books</span><span class="w"> </span><span class="p">(</span><span class="n">title</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;Cassandra: The Definitive Guide&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">pg_size_pretty</span><span class="p">(</span><span class="n">pg_total_relation_size</span><span class="p">(</span><span class="s1">&#39;books&#39;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">pg_size_pretty</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">----------------
</span></span></span><span class="line"><span class="cl"><span class="mi">40</span><span class="w"> </span><span class="n">kB</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">(</span><span class="mi">1</span><span class="w"> </span><span class="k">row</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<p>Next, execute <code>TRUNCATE</code> operation and check the size:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">TRUNCATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">books</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">pg_size_pretty</span><span class="p">(</span><span class="n">pg_total_relation_size</span><span class="p">(</span><span class="s1">&#39;books&#39;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">pg_size_pretty</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">----------------
</span></span></span><span class="line"><span class="cl"><span class="mi">16</span><span class="w"> </span><span class="n">kB</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">(</span><span class="mi">1</span><span class="w"> </span><span class="k">row</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<p>Now, do the same with <code>DELETE</code> operation:</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">DELETE</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">books</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">pg_size_pretty</span><span class="p">(</span><span class="n">pg_total_relation_size</span><span class="p">(</span><span class="s1">&#39;books&#39;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">pg_size_pretty</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">----------------
</span></span></span><span class="line"><span class="cl"><span class="mi">40</span><span class="w"> </span><span class="n">kB</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">(</span><span class="mi">1</span><span class="w"> </span><span class="k">row</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<p>As observed, the table size after <code>TRUNCATE</code> is <code>16 kB</code>, when after <code>DELETE</code> it is still <code>40 kB</code>.
Operating system thinks that the occupied disk space is still in use after <code>DELETE</code>, while <code>TRUNCATE</code> immediately reclaims it.</p>
<h2 id="emulating-truncate-behavior-using-delete-and-vacuum-full">
Emulating TRUNCATE behavior using DELETE and VACUUM FULL
<a href="#emulating-truncate-behavior-using-delete-and-vacuum-full" class="heading-anchor" aria-label="Anchor link for: Emulating TRUNCATE behavior using DELETE and VACUUM FULL">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>You can achieve a similar behavior to <code>TRUNCATE</code> using <code>DELETE</code>. Execute <code>DELETE</code> followed by
<code>VACUUM FULL</code>, as shown below. Keep in mind that because <code>VACUUM FULL</code> places an <code>ACCESS EXCLUSIVE</code> lock,
it may take some time to acquire that lock initially.</p>
<div class="code-block" data-frame="editor">
    <div class="code-content">
        <i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg></i><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">VACUUM</span><span class="w"> </span><span class="p">(</span><span class="k">FULL</span><span class="p">,</span><span class="w"> </span><span class="k">ANALYZE</span><span class="p">)</span><span class="w"> </span><span class="n">books</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">pg_size_pretty</span><span class="p">(</span><span class="n">pg_total_relation_size</span><span class="p">(</span><span class="s1">&#39;books&#39;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">pg_size_pretty</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">----------------
</span></span></span><span class="line"><span class="cl"><span class="mi">16</span><span class="w"> </span><span class="n">kB</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">(</span><span class="mi">1</span><span class="w"> </span><span class="k">row</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<p>Furthermore, when executing <code>VACUUM FULL</code>,
it utilizes additional disk space roughly equivalent to the size of the table.
That is because the previous copy of the table cannot be released until the new one is completed.</p>
<h2 id="summary">
Summary
<a href="#summary" class="heading-anchor" aria-label="Anchor link for: Summary">
    
    
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
    
</a>
</h2>
<p>Following a <code>DELETE</code> operation, a <code>VACUUM FULL</code> is essential to release space, but it necessitates exclusive access and temporary utilization of extra disk space.</p>
<p><code>TRUNCATE</code> quickly recovers physical disk space, while <code>DELETE</code> does not free up physical space unless a <code>VACUUM FULL</code> operation is performed.</p>
<p><code>DELETE</code> is recommended for tables that are frequently queried, as it enables ongoing transactions to access rows that are scheduled for deletion.</p>
<p>In the case of large tables where data is regularly removed, <code>TRUNCATE</code> is more effective and easier to use compared to <code>DELETE</code> followed by <code>VACUUM FULL</code>.</p>
<p>Check 
<a href="https://www.postgresql.org/docs/current/routine-vacuuming.html" target="_blank" rel="nofollow noopener">routine vacuuming documentation</a>
 which gives more details about <code>VACUUM</code> operations and their automation.</p>
]]></content:encoded></item></channel></rss>