<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Archit Agarwal</title>
    <description>The latest articles on DEV Community by Archit Agarwal (@architagr).</description>
    <link>https://dev.to/architagr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1451523%2Fd3c7d8bf-e491-4bc5-b6e8-ff0e92a9b6a2.jpeg</url>
      <title>DEV Community: Archit Agarwal</title>
      <link>https://dev.to/architagr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/architagr"/>
    <language>en</language>
    <item>
      <title>Your Delete Button Is Lying to You in Production - Here's the Data Structure That Fixes It</title>
      <dc:creator>Archit Agarwal</dc:creator>
      <pubDate>Sun, 31 May 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/architagr/your-delete-button-is-lying-to-you-in-production-heres-the-data-structure-that-fixes-it-m6d</link>
      <guid>https://dev.to/architagr/your-delete-button-is-lying-to-you-in-production-heres-the-data-structure-that-fixes-it-m6d</guid>
      <description>&lt;p&gt;When you hit delete and the data comes right back, the problem isn't a bug. It's a design gap that only shows up at scale.&lt;/p&gt;

&lt;p&gt;You've seen this before.&lt;/p&gt;

&lt;p&gt;A user gets banned. You delete their entry from the database. The system confirms the deletion. Fifteen minutes later, another server in your cluster still shows them as active. You delete again. It comes back again. You feel like you're in a horror movie.&lt;/p&gt;

&lt;p&gt;Or consider a simpler scenario: your team is running a feature-flag system across multiple data centres. Someone deprecates a flag - it needs to die permanently. But your distributed nodes keep merging state, and the deleted flag keeps resurrecting. It's not malicious. It's just physics.&lt;/p&gt;

&lt;p&gt;This is one of the most infuriating classes of bugs in distributed systems: &lt;strong&gt;deletes that don't stick.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The good news? This has a known cause, a clean mental model, and a data structure purpose-built to solve it - the &lt;strong&gt;2-Phase Set (2P-Set).&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Decoding the Problem: Why Deletes Are Hard at Scale
&lt;/h2&gt;

&lt;p&gt;On a single server, deletes are easy. You remove a row. Done. The authoritative truth lives in one place.&lt;/p&gt;

&lt;p&gt;The moment you scale horizontally - multiple nodes, multiple replicas, multiple data centres - that single source of truth disappears. Now you have competing sources of truth, and they need to sync up periodically. This sync is called &lt;strong&gt;reconciliation&lt;/strong&gt; or &lt;strong&gt;merge&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's where things get interesting.&lt;/p&gt;

&lt;p&gt;When two nodes merge their state, what happens to a delete? Node A deleted a record. Node B never got that delete message (maybe a network partition, maybe just a delayed sync). Node B still has the record. When A and B merge - Node B's "present" wins. The delete is gone.&lt;/p&gt;

&lt;p&gt;This is called the &lt;strong&gt;delete wins vs add wins&lt;/strong&gt; problem, and it's not a theoretical edge case. It's what happens at 3am when your on-call engineer is trying to figure out why a banned user is still sending messages.&lt;/p&gt;

&lt;p&gt;The naive fix - timestamps - doesn't help much either. "Most recent write wins" sounds clean until you realise clocks across distributed nodes are never perfectly in sync. You end up with a different class of inconsistency.&lt;/p&gt;

&lt;p&gt;The scale-triggered part matters here: on a single node, you never see this. On two nodes with reliable sync, you might not notice. With ten nodes, partial connectivity, and concurrent writes - the problem is unavoidable. &lt;strong&gt;This isn't a bug that appears in development. It's a bug that appears in production, under load, after your third customer complaint.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Fix It: The Two-Toybox Mental Model
&lt;/h2&gt;

&lt;p&gt;Before we talk algorithms, let me give you a mental model that will make everything else obvious.&lt;/p&gt;

&lt;p&gt;Imagine you're a kid with two toyboxes.&lt;/p&gt;

&lt;p&gt;The first one is the &lt;strong&gt;Add-box&lt;/strong&gt;. Any toy you want in your collection goes here. You can keep adding things indefinitely.&lt;/p&gt;

&lt;p&gt;The second one is the &lt;strong&gt;Remove-box&lt;/strong&gt;. When you're done with a toy - permanently done - you move it here. Not back to the shelf. Not to a friend. &lt;em&gt;Into the Remove-box, and it stays there forever.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The toys you actually "have" at any given moment are: everything in the Add-box that is NOT in the Remove-box.&lt;/p&gt;

&lt;p&gt;That's it. That's the 2P-Set.&lt;/p&gt;

&lt;p&gt;The magic of this model is what happens when two kids merge their toyboxes. Alice has an Add-box and a Remove-box. Bob has an Add-box and a Remove-box. When they combine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Combined Add-box = Alice's Add-box ∪ Bob's Add-box&lt;/li&gt;
&lt;li&gt;Combined Remove-box = Alice's Remove-box ∪ Bob's Remove-box&lt;/li&gt;
&lt;li&gt;Final collection = Combined Add-box − Combined Remove-box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice something critical: once a toy lands in any Remove-box across any node, that removal propagates everywhere during the next merge - and it can never be undone. The remove is &lt;strong&gt;permanent and irrevocable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is why 2P-Set works for the banned-user problem. You don't just delete the user. You move them into the Remove-set. That Remove-set is a grow-only set (just like the G-Set from Episode 4). It only ever gets bigger, never smaller. So when nodes merge, removals always survive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-world rule of thumb:&lt;/strong&gt; Use a 2P-Set when you have a distributed collection where deletions must be permanent and must survive node merges.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where 2P-Set Fits Perfectly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Permanent bans and blacklists.&lt;/strong&gt; Add users to the Allow-set on signup. Move them to the Ban-set on moderation action. Any node that gets the update will propagate the ban everywhere. No re-emergence after sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feature flags with permanent deprecation.&lt;/strong&gt; Your config system adds feature keys freely. When a flag is killed, it goes into the removed-set. Even if a stale node briefly re-surfaces it, the next merge corrects course.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service registry tombstones.&lt;/strong&gt; A microservice is decommissioned. You don't just unregister it - you add it to the removed-set. Any service-discovery node that tries to re-register the old name gets blocked, because the tombstone is present.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access revocation.&lt;/strong&gt; A user loses a permission. That revocation needs to hold across all nodes, even if one node was partitioned when the revocation happened. The Remove-set is how you make that hold.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Trade-off You Need to Know About
&lt;/h2&gt;

&lt;p&gt;2P-Set has one hard constraint that you shouldn't try to work around: &lt;strong&gt;once removed, the element is gone forever&lt;/strong&gt;. You cannot re-add a removed element.&lt;/p&gt;

&lt;p&gt;This sounds limiting, but it's actually the source of the guarantee. If you could re-add something after removal, you'd re-introduce the "whose update wins" problem you were trying to escape.&lt;/p&gt;

&lt;p&gt;The practical implication: if you need to "re-add" something, model it differently. Don't re-add the same logical identifier - create a new one. A banned user who creates a new account gets a new user ID, not a resurrection of the old one.&lt;/p&gt;

&lt;p&gt;This is a clean design constraint that forces your domain model to be explicit about intent.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Algorithm
&lt;/h2&gt;

&lt;p&gt;The formal structure is straightforward, and once you see it, it clicks immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State:&lt;/strong&gt; Each node maintains two grow-only sets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;A&lt;/code&gt; - the Add-set (all elements ever added)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;R&lt;/code&gt; - the Remove-set (all elements ever removed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Operations:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Add(e):&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Precondition: &lt;code&gt;e&lt;/code&gt; must not be in &lt;code&gt;R&lt;/code&gt; (can't re-add a removed element)&lt;/li&gt;
&lt;li&gt;Effect: Insert &lt;code&gt;e&lt;/code&gt; into &lt;code&gt;A&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Remove(e):&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Precondition: &lt;code&gt;e&lt;/code&gt; must be in &lt;code&gt;A&lt;/code&gt; (can only remove what was added)&lt;/li&gt;
&lt;li&gt;Effect: Insert &lt;code&gt;e&lt;/code&gt; into &lt;code&gt;R&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Lookup(e):&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Returns &lt;code&gt;true&lt;/code&gt; if &lt;code&gt;e ∈ A&lt;/code&gt; and &lt;code&gt;e ∉ R&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Equivalently: &lt;code&gt;e ∈ (A − R)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Value():&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Returns &lt;code&gt;A − R&lt;/code&gt; (all elements in Add-set not in Remove-set)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Merge(local, remote):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;merged.A = local.A ∪ remote.A
merged.R = local.R ∪ remote.R
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both A and R are G-Sets under the hood - they only grow. Merging G-Sets via union is always safe, always commutative, always idempotent. Apply the merge in any order, any number of times, you get the same result. That's the CRDT guarantee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this is conflict-free:&lt;/strong&gt; The only operation that's "destructive" is a remove. But removes go into R, and R only grows. So across any merge, a removal that happened on any node will eventually appear in every node's R-set. The deletion propagates monotonically. There is no conflict to resolve.&lt;/p&gt;




&lt;h2&gt;
  
  
  Go Implementation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;crdt&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"sync"&lt;/span&gt;

&lt;span class="c"&gt;// TwoPhaseSet implements a 2P-Set CRDT.&lt;/span&gt;
&lt;span class="c"&gt;// Once an element is removed, it can never be re-added.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mu&lt;/span&gt;        &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;
    &lt;span class="n"&gt;addSet&lt;/span&gt;    &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;removeSet&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewTwoPhaseSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;
        &lt;span class="n"&gt;addSet&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}),&lt;/span&gt;
        &lt;span class="n"&gt;removeSet&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Add inserts an element into the set.&lt;/span&gt;
&lt;span class="c"&gt;// Returns false if the element was previously removed - once removed, it stays removed.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;removed&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;removed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt; &lt;span class="c"&gt;// 2P-Set invariant: removed elements cannot be re-added&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Remove moves an element from the add-set to the remove-set.&lt;/span&gt;
&lt;span class="c"&gt;// The element must have been added first. Returns false otherwise.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt; &lt;span class="c"&gt;// can only remove what has been added&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Contains returns true if the element is in the add-set but not the remove-set.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inAdd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inRemove&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inAdd&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;inRemove&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Items returns a snapshot of all live elements (add-set minus remove-set).&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addSet&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addSet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;removed&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;removed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Merge combines another 2P-Set into this one.&lt;/span&gt;
&lt;span class="c"&gt;// Both add-sets and remove-sets are merged via union - safe, idempotent, commutative.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;Merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addSet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeSet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walkthrough with the banned-user scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;nodeA&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;crdt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTwoPhaseSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;
    &lt;span class="n"&gt;nodeB&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;crdt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTwoPhaseSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;

    &lt;span class="c"&gt;// Both nodes add the user during normal operation&lt;/span&gt;
    &lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user:42"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user:42"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Node A bans the user - this is the remove&lt;/span&gt;
    &lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user:42"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Network partition: Node B hasn't received the ban yet&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user:42"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;// true - B doesn't know yet&lt;/span&gt;

    &lt;span class="c"&gt;// Sync happens - B merges A's state&lt;/span&gt;
    &lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Now the ban has propagated&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user:42"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;// false - ban survived the merge&lt;/span&gt;

    &lt;span class="c"&gt;// Trying to re-add the banned user on Node B fails&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user:42"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;// false - once removed, gone forever&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The merge doesn't care about the order of operations or how many times you call it. The ban is permanent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Java Implementation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Collections&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.HashSet&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Set&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.concurrent.ConcurrentHashMap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;addSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newKeySet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;removeSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newKeySet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Adds an element to the set.
     * Returns false if the element was previously removed - 2P-Set invariant.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;removeSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// once removed, cannot be re-added&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;addSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Removes an element permanently.
     * Element must have been added first; returns false otherwise.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;addSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// can only remove what was added&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;removeSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Returns true if the element is in addSet but not in removeSet.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;addSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;removeSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Returns a snapshot of all live elements (addSet minus removeSet).
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;addSet&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;removeAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;removeSet&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unmodifiableSet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Merges another 2P-Set into this one via union of both add and remove sets.
     * Safe to call multiple times - idempotent and commutative.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;addSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addSet&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;removeSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;removeSet&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walkthrough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nodeA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nodeB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TwoPhaseSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Both nodes add the feature flag during rollout&lt;/span&gt;
        &lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flag:new-checkout"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flag:new-checkout"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Flag is deprecated - permanently removed on Node A&lt;/span&gt;
        &lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flag:new-checkout"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Node B is partitioned - it still sees the flag&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flag:new-checkout"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;

        &lt;span class="c1"&gt;// Sync: B merges A's state&lt;/span&gt;
        &lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;merge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Deprecation has propagated&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flag:new-checkout"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;

        &lt;span class="c1"&gt;// Someone tries to re-add the deprecated flag on Node B&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flag:new-checkout"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// false - tombstone wins&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Java version uses &lt;code&gt;ConcurrentHashMap.newKeySet()&lt;/code&gt; for thread-safe operations without explicit locking - appropriate for read-heavy workloads where you're merging state from background sync threads.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparing to What Came Before
&lt;/h2&gt;

&lt;p&gt;You've seen the G-Set in Episode 4 - it only grows, never shrinks. Simple and elegant, but useless if you need to remove anything.&lt;/p&gt;

&lt;p&gt;The 2P-Set is a direct evolution: you layer a second G-Set (the Remove-set) on top. The Remove-set is your delete log, and it only ever grows. Deletions become a monotonic write rather than a destructive one. That's the insight.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5xbhglfdbyx9n8uyc9o2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5xbhglfdbyx9n8uyc9o2.jpg" alt=" " width="686" height="263"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The One Catch Worth Repeating
&lt;/h2&gt;

&lt;p&gt;The Remove-set never shrinks. In a long-lived system with lots of deletions, this means your Remove-set grows indefinitely. You can't garbage-collect it without coordination - because the whole point is that it outlives any single node.&lt;/p&gt;

&lt;p&gt;For most use cases (bans, tombstones, feature deprecations) the Remove-set is small and this is fine. For high-churn systems where elements are added and removed at high frequency, 2P-Set isn't the right tool. That's where the OR-Set comes in - and that's exactly what Episode 6 covers.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The next piece steps back from the counters entirely.&lt;/p&gt;

&lt;p&gt;What if instead of tracking numbers, you need to track membership - items in a set, users in a group, tags on a document? &lt;br&gt;
The same concurrent update problems apply. And a surprisingly simple grow-only constraint solves the basic case cleanly.&lt;/p&gt;

&lt;p&gt;Episode 6: OR sets, solves the re-add problem without breaking conflict-freedom. It does this using a concept called unique tags, and it's the most practically useful set CRDT for general-purpose distributed state.&lt;/p&gt;

&lt;p&gt;The full series:&lt;/p&gt;

&lt;p&gt;Episode 1: &lt;a href="https://dev.to/architagr/youtube-doesnt-actually-know-how-many-views-your-video-has-52fm"&gt;G-Counter - why YouTube's view count is approximate&lt;/a&gt;&lt;br&gt;
Episode 2: &lt;a href="https://dev.to/architagr/reddits-karma-score-is-a-lie-heres-the-math-behind-it-14a4"&gt;PN-Counter - Reddit karma and the two-notebook trick&lt;/a&gt;&lt;br&gt;
Episode 3: &lt;a href="https://dev.to/architagr/your-inventory-counter-just-went-negative-heres-why-and-how-to-fix-it-5fp3"&gt;Bounded Counter - inventory floors and distributed permission&lt;/a&gt;&lt;br&gt;
Episode 4: &lt;a href="https://dev.to/architagr/youre-writing-conflict-resolution-code-for-data-that-can-never-conflict-4cbb?preview=728df703fd7858e1848d82e703980ebead4815bf7b11713b539f07c6158f40607f8d982c218226d5dad5080b09fcf33df8c1e4ef499e78d03ca23964"&gt;G-Set - grow-only sets&lt;/a&gt;&lt;br&gt;
Episode 5: 2P-2 Phase sets (you're here)&lt;br&gt;
Episode 6: OR-Set - the fix for "deleted but still there"&lt;br&gt;
Episode 7: The full picture - when to use CRDTs and when to walk away&lt;/p&gt;

&lt;p&gt;Subscribe to the newsletter to get each episode as it drops.&lt;br&gt;
If you're building distributed systems and want to think through invariant enforcement, CRDT design, or distributed architecture with someone who has been deep in this space, I do 1:1 sessions on Topmate.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>You're Writing Conflict Resolution Code for Data That Can Never Conflict</title>
      <dc:creator>Archit Agarwal</dc:creator>
      <pubDate>Tue, 26 May 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/architagr/youre-writing-conflict-resolution-code-for-data-that-can-never-conflict-4cbb</link>
      <guid>https://dev.to/architagr/youre-writing-conflict-resolution-code-for-data-that-can-never-conflict-4cbb</guid>
      <description>&lt;p&gt;You've built complex sync logic, last-write-wins strategies, and vector clocks to handle merges between distributed nodes. But for append-only data — events, logs, audit trails — none of that conflict resolution was ever necessary. At scale, that accidental complexity quietly drops data, creates race conditions, and pages you at 3 AM. There's a data structure that makes the entire problem disappear.&lt;/p&gt;




&lt;p&gt;Here's a scenario that plays out more often than people admit.&lt;/p&gt;

&lt;p&gt;You're running a multi-region analytics service. Writes come in from Singapore and Amsterdam simultaneously. Both nodes accept events independently — which is correct, because you don't want a transatlantic network hiccup to stop recording user activity. Then the nodes sync.&lt;/p&gt;

&lt;p&gt;Now what? Someone has to decide what the "merged" state looks like. Last-write-wins? You'll silently drop events that lost the timestamp race. Vector clocks? Now you're maintaining causal history for every key. Operational transforms? You've just signed up to maintain a distributed consensus protocol. Each of these approaches is a moving part that can fail, drift, or produce surprising behavior under weird race conditions.&lt;/p&gt;

&lt;p&gt;Here's what makes this painful: the data was never actually conflicting. An event recorded in Singapore and an event recorded in Amsterdam are two different facts about the world. There's no conflict to resolve. You invented the complexity by reaching for a data structure that was never the right fit.&lt;/p&gt;

&lt;p&gt;For append-only data, there's a data structure that makes the entire problem structurally impossible: the Grow-only Set, or G-Set. It's one of the simplest CRDTs (Conflict-free Replicated Data Types), and once you understand it, you'll recognize the pattern everywhere — and stop reaching for solutions to problems you don't actually have.&lt;/p&gt;

&lt;p&gt;Let's pull this apart properly.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Two Replicas, One Truth, Zero Agreement
&lt;/h2&gt;

&lt;p&gt;To understand why G-Sets matter, you need to feel the pain they solve first.&lt;/p&gt;

&lt;p&gt;Imagine you're running a user analytics service. You have two data center nodes — call them Node A (Singapore) and Node B (Amsterdam). Both are recording user events. Both accept writes independently so that a network partition between Singapore and Amsterdam doesn't bring your whole system down. That's the right call. That's the whole point of distributed systems.&lt;/p&gt;

&lt;p&gt;Here's where it gets interesting.&lt;/p&gt;

&lt;p&gt;A user in Singapore clicks "Purchase". Node A records the event. A user in Amsterdam completes a form. Node B records that event. So far, so good.&lt;/p&gt;

&lt;p&gt;Now Node A and Node B sync up. In a naive system backed by a regular set or a plain key-value store, you'd ask: who wins? If both nodes added different things with the same key, you'd need a conflict resolution strategy. Last-write-wins? Vector clocks? Operational transforms? Each of these is a moving part that can fail, drift, or produce surprising behavior under weird race conditions.&lt;/p&gt;

&lt;p&gt;And here's the subtle, nasty part: the problem doesn't show up when you have two nodes. It shows up when you have twenty. It shows up when network partitions happen mid-sync. It shows up when one node is lagging by 40 seconds and another is lagging by 4 minutes. It shows up when you're merging states that went through three intermediate nodes before reaching the final destination.&lt;/p&gt;

&lt;p&gt;At small scale, your ad-hoc conflict resolution seems fine. At scale — multiple data centers, hundreds of nodes, millions of events per minute — the edge cases multiply faster than your ability to reason about them. Eventually, you're not building a feature. You're building a distributed consensus protocol from scratch, badly, while also shipping product.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix, in Plain English
&lt;/h2&gt;

&lt;p&gt;What if you just… didn't allow the conflicts to exist in the first place?&lt;/p&gt;

&lt;p&gt;That's the core insight behind G-Sets. Instead of building conflict resolution for a general-purpose mutable set, you restrict the operation set to only what can never conflict: addition only.&lt;br&gt;
Think about it like a physical toy box with a one-way slot — like a ballot box. You can drop things in. You cannot take things out. The slot only goes one direction.&lt;/p&gt;

&lt;p&gt;Now imagine two kids (two nodes) each have their own toy box. They both drop toys in independently, all day long, without talking to each other. At the end of the day, you want one combined box with everything both kids put in.&lt;/p&gt;

&lt;p&gt;The merge is trivial: you just take everything from both boxes. There is no conflict because there was never a competing operation. Kid A adding a rubber duck doesn't conflict with Kid B adding a LEGO set. Addition + Addition = Union. Always. No exceptions.&lt;/p&gt;

&lt;p&gt;That's a G-Set.&lt;/p&gt;

&lt;p&gt;The formal guarantee is this: at any point in time, on any replica, the set of elements only grows. It never shrinks. When two replicas merge, they compute the union of their states. The union of two sets is commutative (order doesn't matter), associative (grouping doesn't matter), and idempotent (merging the same thing twice gives the same result). Those three properties together mean you can sync replicas in any order, any number of times, across any topology, and you always converge to the same state.&lt;/p&gt;

&lt;p&gt;No locks. No coordination. No conflict resolution. No 3 AM pages.&lt;/p&gt;




&lt;h2&gt;
  
  
  When You'd Actually Use This
&lt;/h2&gt;

&lt;p&gt;G-Sets shine everywhere you naturally think "append-only". Here are five real-world patterns where this isn't just theoretical — it's the right tool:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User event logs in analytics.&lt;/strong&gt; Every click, login, page view, or purchase is an event that happened. Events don't un-happen. You record them, and they stay forever. Each node adds events to its local G-Set replica. When replicas sync, the union is the full history. No event is lost, no event is double-counted from merging.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Training data pipelines in ML.&lt;/strong&gt; Different ingestion nodes might be collecting labeled training samples from different regions. Once a sample is added to the corpus, it stays. The "global training set" is just the union of everything every node has seen. Merging two replicas is just "take all samples from both" — which is exactly what you want.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Config version history.&lt;/strong&gt; A service that tracks every version of a configuration key — so you can replay history, debug what changed, or roll back — needs to keep all version metadata permanently. You only ever insert version entries; you never delete them. A G-Set gives you that audit trail with automatic merge semantics across replicas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Social graph: follow edges.&lt;/strong&gt; In a "follow" relationship model (think Twitter/X, GitHub stars), you add edges to a graph: "user A followed user B". If your system doesn't support unfollow (or handles unfollow via a different mechanism), the follow-graph itself is a G-Set. New follows can be added from any replica without coordination, and they all merge cleanly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Permission grant audit trail.&lt;/strong&gt; Every time a user is granted a permission — "can read resource X", "can deploy to environment Y" — that grant is recorded permanently. You don't delete grants; you expire them or supersede them via a newer grant. The history of all grants ever issued is a G-Set across your auth service replicas.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Notice the pattern:&lt;/strong&gt; G-Sets are perfect wherever the business semantics naturally say "this thing happened and we never want to forget it."&lt;/p&gt;




&lt;h2&gt;
  
  
  The Algorithm
&lt;/h2&gt;

&lt;p&gt;The G-Set is deliberately minimal. Three operations, all of them simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State:&lt;/strong&gt; A set &lt;code&gt;S&lt;/code&gt; of elements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add(element):&lt;/strong&gt; Insert the element into the local set. This is always safe. It never conflicts with anything. Complexity: O(1) average.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lookup(element):&lt;/strong&gt; Check if an element is in the local set. Returns true if present. Complexity: O(1) average.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merge(remote_state):&lt;/strong&gt; Given another replica's set, compute the union of both sets and store it locally. Formally: &lt;code&gt;S_local = S_local ∪ S_remote&lt;/code&gt;. Complexity: O(|S_remote|).&lt;/p&gt;

&lt;p&gt;The convergence proof is embarrassingly simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any two replicas that have seen the same set of &lt;code&gt;Add&lt;/code&gt; operations will have the same state.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Merge&lt;/code&gt; is monotonically non-decreasing — the set can only grow, never shrink.&lt;/li&gt;
&lt;li&gt;Because merge is a union operation, it's commutative &lt;code&gt;(A ∪ B = B ∪ A)&lt;/code&gt;, associative &lt;code&gt;((A ∪ B) ∪ C = A ∪ (B ∪ C))&lt;/code&gt;, and idempotent &lt;code&gt;(A ∪ A = A)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Therefore, regardless of the order in which replicas sync, they will all eventually converge to the union of all elements ever added.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This property — where any number of merges in any order reaches the same final state — is called Strong Eventual Consistency (SEC). It's a stricter and more useful guarantee than plain eventual consistency, because it means you don't just eventually agree; you agree &lt;em&gt;immediately&lt;/em&gt; once you've seen the same information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One important constraint to keep in mind:&lt;/strong&gt; G-Sets have no &lt;code&gt;Remove&lt;/code&gt; operation. That's not a bug — it's the feature. The moment you add removal, you open the door to conflicts (what if one replica removes an element that another replica just added?). If you need removal semantics, you'd graduate to a 2P-Set (Two-Phase Set) or an OR-Set (Observed-Remove Set), which are more complex CRDTs. But if your use case is naturally append-only, don't reach for the complex tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation in Go
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;crdt&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"sync"&lt;/span&gt;

&lt;span class="c"&gt;// GSet is a Grow-only Set — elements can be added but never removed.&lt;/span&gt;
&lt;span class="c"&gt;// Safe for concurrent use via a read-write mutex.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;GSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mu&lt;/span&gt;   &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewGSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;GSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Add inserts an element into the set.&lt;/span&gt;
&lt;span class="c"&gt;// Always safe: no conflict is possible with a pure add operation.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Contains returns true if the element is present in the set.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Items returns a snapshot of all elements currently in the set.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Merge computes the union of this set and another replica's set.&lt;/span&gt;
&lt;span class="c"&gt;// Commutative, associative, idempotent — safe to call in any order, any number of times.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;Merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Example: two analytics nodes recording events independently, then syncing.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ExampleTwoReplicas&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Node A: Singapore data center&lt;/span&gt;
    &lt;span class="n"&gt;nodeA&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewGSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;
    &lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"event:user_123:purchase"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"event:user_456:login"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Node B: Amsterdam data center&lt;/span&gt;
    &lt;span class="n"&gt;nodeB&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewGSet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;
    &lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"event:user_789:pageview"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"event:user_456:login"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// same event recorded on both nodes&lt;/span&gt;

    &lt;span class="c"&gt;// Sync: merge B into A&lt;/span&gt;
    &lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// nodeA now contains all three unique events.&lt;/span&gt;
    &lt;span class="c"&gt;// The duplicate "event:user_456:login" is handled automatically — idempotency in action.&lt;/span&gt;
    &lt;span class="c"&gt;// Result: {"event:user_123:purchase", "event:user_456:login", "event:user_789:pageview"}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth pointing out in this implementation:&lt;/p&gt;

&lt;p&gt;The mutex strategy matters here. We take a snapshot of &lt;code&gt;other.data&lt;/code&gt; under &lt;code&gt;other&lt;/code&gt;'s read lock first, then release it before acquiring &lt;code&gt;s&lt;/code&gt;'s write lock. This avoids a classic deadlock scenario where two goroutines simultaneously try to merge into each other, each waiting for the other's lock. Snapshot-then-write is the safe pattern.&lt;/p&gt;

&lt;p&gt;The generic constraint &lt;code&gt;[T comparable]&lt;/code&gt; is load-bearing — Go's map keys must be comparable, so string, int, UUID types all work. Custom structs work too as long as all fields are comparable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation in Java
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Collections&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.HashSet&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Set&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.concurrent.locks.ReadWriteLock&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.concurrent.locks.ReentrantReadWriteLock&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * GSet — a Grow-only Set CRDT.
 *
 * Elements can only be added, never removed.
 * Merge is a union operation: commutative, associative, and idempotent.
 * Thread-safe via a ReentrantReadWriteLock.
 *
 * @param &amp;lt;T&amp;gt; the type of elements; must implement equals() and hashCode() correctly.
 */&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ReadWriteLock&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReentrantReadWriteLock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Add an element to the set.
     * Always safe — a pure add can never conflict with any other operation.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeLock&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeLock&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;unlock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Check if an element is present in the set.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readLock&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readLock&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;unlock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Returns an immutable snapshot of all elements currently in the set.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readLock&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unmodifiableSet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readLock&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;unlock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Merge another replica's state into this set (union operation).
     *
     * Safe to call in any order, any number of times.
     * A snapshot of the other set is taken under the other set's read lock
     * before acquiring this set's write lock — prevents deadlock on mutual merge.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Snapshot first, under other's read lock&lt;/span&gt;
        &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeLock&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeLock&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;unlock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example: simulating two service nodes syncing their event logs&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GSetsExample&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Node A: US-East&lt;/span&gt;
        &lt;span class="nc"&gt;GSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nodeA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"grant:user_101:read:resource_A"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"grant:user_202:write:resource_B"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Node B: EU-West&lt;/span&gt;
        &lt;span class="nc"&gt;GSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nodeB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"grant:user_303:read:resource_C"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"grant:user_202:write:resource_B"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// same grant, recorded independently&lt;/span&gt;

        &lt;span class="c1"&gt;// Sync: merge B into A&lt;/span&gt;
        &lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;merge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeB&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// nodeA now contains all three unique grants.&lt;/span&gt;
        &lt;span class="c1"&gt;// Duplicate handled automatically via HashSet semantics.&lt;/span&gt;
        &lt;span class="n"&gt;nodeA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Output (order may vary):&lt;/span&gt;
        &lt;span class="c1"&gt;// grant:user_101:read:resource_A&lt;/span&gt;
        &lt;span class="c1"&gt;// grant:user_202:write:resource_B&lt;/span&gt;
        &lt;span class="c1"&gt;// grant:user_303:read:resource_C&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Java version's &lt;code&gt;items()&lt;/code&gt; returning an unmodifiable snapshot does double duty: it protects the internal state from external mutation and serves as the snapshot mechanism that &lt;code&gt;merge()&lt;/code&gt; relies on for the deadlock-safe two-phase lock acquisition.&lt;/p&gt;

&lt;p&gt;One Java-specific note: make sure your element type &lt;code&gt;T&lt;/code&gt; has a correct &lt;code&gt;equals()&lt;/code&gt; and &lt;code&gt;hashCode()&lt;/code&gt; implementation. &lt;code&gt;HashSet.add()&lt;/code&gt; uses these to determine uniqueness. If you're storing custom objects (like a &lt;code&gt;UserEvent&lt;/code&gt; record), and you forget to override these, you'll end up with duplicate "equal" events in your set — which defeats the idempotency guarantee. Java records (&lt;code&gt;record UserEvent(String userId, String type)&lt;/code&gt;) handle this correctly by default.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Same Data Structure, Everywhere You Look
&lt;/h2&gt;

&lt;p&gt;Once you internalize G-Sets, you start recognizing the pattern in places you never expected. The question to ask is simple: "Does this data only ever grow? Is removing something not a real operation, just a display concern?" If yes, you have a G-Set waiting to happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IoT sensor readings&lt;/strong&gt;. A fleet of temperature sensors across a warehouse each record readings independently. Readings never get un-taken. The "global history of all readings" is the union of every sensor's local set. G-Set handles the merge across all nodes with zero coordination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distributed tag systems&lt;/strong&gt;. Content tagging in a CMS or knowledge base: articles accumulate tags across different editor nodes. Tags are added freely; "removing" a tag is typically a business-level soft delete handled separately. The growth of tags across replicas is a pure G-Set problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service discovery registries&lt;/strong&gt;. When microservices register themselves with a discovery service, each new registration is an append. The set of "services that have ever been registered" is a G-Set. Deregistration is a separate concern (and a different CRDT problem).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feature flag rollout tracking&lt;/strong&gt;. Which user IDs have had a feature enabled for them — for audit or rollback purposes — is naturally append-only. You enable features for more users over time, and you want every region's view of "who saw this feature" to converge to the same answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distributed deduplication registries&lt;/strong&gt;. Idempotency keys for payment systems or message queues. Once a key is "seen", it's in the set forever. Multiple nodes can independently record the same key without conflict — the G-Set's idempotency handles duplicates automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blockchain-style ledgers&lt;/strong&gt;. Transaction IDs in a distributed ledger. New transactions are appended; nothing is ever removed. Each participating node maintains a local G-Set of confirmed transaction IDs, and merging across nodes gives you the complete global ledger with zero conflict resolution needed.&lt;/p&gt;

&lt;p&gt;The unifying mental model: &lt;strong&gt;any time your data represents facts that happened, rather than state that changes, you're looking at a G-Set&lt;/strong&gt;. Events happened. Grants were issued. Sensors fired. Services registered. None of those facts un-happen. Stop treating them like mutable state — and stop writing conflict resolution code for them.&lt;/p&gt;




&lt;h2&gt;
  
  
  What G-Sets Can’t Do (and That’s Fine)
&lt;/h2&gt;

&lt;p&gt;It would be dishonest to end without stating the obvious limitation: you cannot remove elements. A G-Set grows forever.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you need “soft deletes” or element removal, G-Set is the wrong tool. Look at 2P-Sets or OR-Sets.&lt;/li&gt;
&lt;li&gt;If your set grows unboundedly and you care about memory, you’ll need a tombstone strategy or a compaction layer on top.&lt;/li&gt;
&lt;li&gt;If the “has this element ever been added” semantic doesn’t match your domain (i.e., you need “is this element currently active”), you’re modeling the wrong thing as a G-Set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But for the patterns where it does fit — event logs, audit trails, append-only graphs, training data — the G-Set gives you something rare in distributed systems: a correctness guarantee that doesn’t require you to think harder. It just works.&lt;/p&gt;

&lt;p&gt;And in systems at scale, “it just works” is worth more than almost anything else.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;The next piece steps back from the counters entirely.&lt;/p&gt;

&lt;p&gt;What if instead of tracking numbers, you need to track membership — items in a set, users in a group, tags on a document? The same concurrent update problems apply. And a surprisingly simple grow-only constraint solves the basic case cleanly.&lt;/p&gt;

&lt;p&gt;Episode 5: 2P — 2-Phase sets, where we add the ability to remove elements — and discover why even that simple extension requires careful design.&lt;/p&gt;

&lt;p&gt;The full series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Episode 1: &lt;a href="https://dev.to/architagr/youtube-doesnt-actually-know-how-many-views-your-video-has-52fm"&gt;G-Counter — why YouTube’s view count is approximate&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Episode 2: &lt;a href="https://dev.to/architagr/reddits-karma-score-is-a-lie-heres-the-math-behind-it-14a4"&gt;PN-Counter — Reddit karma and the two-notebook trick&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Episode 3: &lt;a href="https://dev.to/architagr/your-inventory-counter-just-went-negative-heres-why-and-how-to-fix-it-5fp3"&gt;Bounded Counter — inventory floors and distributed permission&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Episode 4: G-Set — grow-only sets (you’re here)&lt;/li&gt;
&lt;li&gt;Episode 5: 2P-2 Phase sets&lt;/li&gt;
&lt;li&gt;Episode 6: OR-Set — the fix for “deleted but still there”&lt;/li&gt;
&lt;li&gt;Episode 7: The full picture — when to use CRDTs and when to walk away&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Subscribe to the &lt;a href="https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7261403856079597568" rel="noopener noreferrer"&gt;newsletter&lt;/a&gt; to get each episode as it drops.&lt;/p&gt;

&lt;p&gt;If you’re building distributed systems and want to think through invariant enforcement, CRDT design, or distributed architecture with someone who has been deep in this space, I do 1:1 sessions on &lt;a href="https://topmate.io/archit_agarwal" rel="noopener noreferrer"&gt;Topmate&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>go</category>
      <category>softwareengineering</category>
      <category>java</category>
    </item>
    <item>
      <title>Your Inventory Counter Just Went Negative. Here’s Why — and How to Fix It.</title>
      <dc:creator>Archit Agarwal</dc:creator>
      <pubDate>Mon, 18 May 2026 06:14:44 +0000</pubDate>
      <link>https://dev.to/architagr/your-inventory-counter-just-went-negative-heres-why-and-how-to-fix-it-5fp3</link>
      <guid>https://dev.to/architagr/your-inventory-counter-just-went-negative-heres-why-and-how-to-fix-it-5fp3</guid>
      <description>&lt;p&gt;Black Friday. 9:02am. Your e-commerce platform has been live for two minutes.&lt;/p&gt;

&lt;p&gt;A limited-edition sneaker drops - 500 pairs in stock. Within seconds, the inventory counter is spinning. Add to cart. Checkout. Payment confirmed. Your servers in the US, EU, and Asia are all taking orders simultaneously to keep latency low for every customer on the planet.&lt;/p&gt;

&lt;p&gt;By 9:04am, you've sold 620 pairs.&lt;/p&gt;

&lt;p&gt;You have 500.&lt;/p&gt;

&lt;p&gt;Your ops channel lights up. Support tickets flood in. You're now emailing 120 paying customers to tell them their confirmed order doesn't exist. The refunds go out. The trust doesn't come back.&lt;/p&gt;

&lt;p&gt;What went wrong wasn't a bug in your payment code. It wasn't a race condition in the traditional sense. It was something more fundamental: your distributed counter had no floor. And when multiple servers decremented it concurrently, each one saw a local view that looked safe - and the global invariant silently broke.&lt;/p&gt;

&lt;p&gt;This is the problem Bounded Counters were designed to solve. And the solution is one of the most elegant ideas in the CRDT literature.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an Inventory Counter Actually Is
&lt;/h2&gt;

&lt;p&gt;Before the distributed systems, let's ground this in something simple.&lt;/p&gt;

&lt;p&gt;An inventory counter is just a number. It represents how many units of something you have available. When a customer buys one, the number goes down. When a shipment arrives, the number goes up. The rule is straightforward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The counter must never go below zero.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In SQL, this is trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;product_id&lt;/span&gt;  &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;stock&lt;/span&gt;       &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CHECK&lt;/code&gt; constraint enforces the floor. If a transaction tries to bring &lt;code&gt;stock&lt;/code&gt; below zero, the database rejects it. One server. One source of truth. The constraint holds perfectly.&lt;/p&gt;

&lt;p&gt;The same logic applies to a wallet: balance must never go below zero. To an ad budget: spend must never exceed the limit. To a rate limiter: requests must never exceed the ceiling.&lt;/p&gt;

&lt;p&gt;These are &lt;strong&gt;bounded counters&lt;/strong&gt; - counters with a numeric invariant that must never be violated.&lt;/p&gt;

&lt;p&gt;Simple when you have one server. Deeply interesting when you have many.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment Simplicity Breaks
&lt;/h2&gt;

&lt;p&gt;Your platform grows. One server can't handle Black Friday traffic. You distribute.&lt;/p&gt;

&lt;p&gt;You deploy database replicas in the US, EU, and Asia. Each region accepts writes locally to keep latency low. The system is eventually consistent - replicas sync in the background, but any given server might be working from a slightly stale view of the world.&lt;/p&gt;

&lt;p&gt;Here's what happens to your inventory counter.&lt;/p&gt;

&lt;p&gt;500 pairs in stock. Three servers, each seeing the same initial state: &lt;code&gt;stock = 500&lt;/code&gt;. In the first two minutes, customers are hammering all three regions simultaneously.&lt;/p&gt;

&lt;p&gt;The US server processes 200 orders. Locally: &lt;code&gt;500 - 200 = 300&lt;/code&gt;. Looks fine.&lt;/p&gt;

&lt;p&gt;The EU server, working from the same initial state it loaded before the US orders propagated, processes 180 orders. Locally: &lt;code&gt;500 - 180 = 320&lt;/code&gt;. Also looks fine.&lt;/p&gt;

&lt;p&gt;The Asia server processes 170 orders. Locally: &lt;code&gt;500 - 170 = 330&lt;/code&gt;. Still fine locally.&lt;/p&gt;

&lt;p&gt;When the three servers finally sync: &lt;code&gt;500 - 200 - 180 - 170 = -50&lt;/code&gt;.&lt;br&gt;
You've oversold by 50 pairs. Every check passed locally. The global invariant shattered silently.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why the Solutions You're Thinking Of Don't Work Here
&lt;/h2&gt;

&lt;p&gt;At this point, two approaches come to mind. Both fail at scale in ways worth understanding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distributed locks:&lt;/strong&gt; Before decrementing, acquire a global lock on the inventory key across all replicas. Only one server writes at a time.&lt;/p&gt;

&lt;p&gt;This works - and kills your latency. A lock that spans data centres separated by hundreds of milliseconds of network round-trip adds that latency to every single purchase. On Black Friday, when the whole point of distributing was to make checkout fast, a global lock turns your purchase flow into a queue. Users abandon cart. Revenue drops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strong consistency everywhere:&lt;/strong&gt; Route all inventory writes through a single primary node or quorum. Every decrement gets serialised.&lt;/p&gt;

&lt;p&gt;Also works. Also slow. Same problem as distributed locks, different mechanism. The CAP theorem is not negotiable: if you want strong consistency across geographically distributed nodes, you pay in latency and availability.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/architagr/reddits-karma-score-is-a-lie-heres-the-math-behind-it-14a4"&gt;Episode 2&lt;/a&gt;, PN-Counter gave us increment and decrement without coordination. But I ended that piece with a warning: PN-Counter has no floor. Nothing in its design prevents the score from going to -50,000. For Reddit karma, nobody cares. For inventory, that's a P0 incident.&lt;/p&gt;

&lt;p&gt;We need something new.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Insight: Stop Distributing the Counter. Distribute the Permission.
&lt;/h2&gt;

&lt;p&gt;Here's the shift in thinking that makes Bounded Counters work.&lt;/p&gt;

&lt;p&gt;With PN-Counter, we stopped trying to coordinate on every write. We accepted that replicas might temporarily disagree on the exact value. That was fine because eventual consistency was acceptable.&lt;/p&gt;

&lt;p&gt;Bounded Counters need something stronger: a hard floor that can never be violated, even under concurrent writes, even during network partitions, even when replicas have stale views.&lt;/p&gt;

&lt;p&gt;The key insight: &lt;strong&gt;you don't need every replica to know the exact current value to enforce the floor. You just need every replica to know it has permission to decrement.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think of it like tickets.&lt;/p&gt;

&lt;p&gt;You have 100 items in stock. The floor is 0. That means you have 100 units of "decrement permission" - 100 tickets. You distribute those tickets across your three servers before the sale starts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;US server: 50 tickets&lt;/li&gt;
&lt;li&gt;EU server: 30 tickets&lt;/li&gt;
&lt;li&gt;Asia server: 20 tickets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the rule is simple: &lt;strong&gt;a server can only decrement if it has at least one ticket to spend&lt;/strong&gt;. When it decrements by N, it spends N tickets.&lt;/p&gt;

&lt;p&gt;The US server takes 50 orders: spends 50 tickets, now has 0. Stops accepting orders locally. The EU server takes 30 orders: spends 30 tickets, now has 0. Stops accepting orders locally. The Asia server takes 20 orders: spends 20 tickets, now has 0. Stops.&lt;/p&gt;

&lt;p&gt;Total orders: 100. Exactly the stock. Global invariant: never violated.&lt;/p&gt;

&lt;p&gt;Nobody coordinated on a single order. Every check was purely local. The constraint held globally because the math is airtight: if total tickets = total available decrements, and each replica can only spend what it owns, the sum of decrements can never exceed the total tickets.&lt;/p&gt;

&lt;p&gt;This is the escrow idea, applied to distributed systems. You're not distributing the counter. You're distributing the permission to decrement.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Wallet Walk-Through
&lt;/h2&gt;

&lt;p&gt;Let's trace this concretely with a wallet that must never go below zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Initial state:&lt;/strong&gt; Wallet value = 100. Floor = 0. Available decrement rights = 100 − 0 = 100.&lt;/p&gt;

&lt;p&gt;Rights distributed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DC_A: 50 rights
DC_B: 30 rights
DC_C: 20 rights
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Payment at DC_A:&lt;/strong&gt; User pays 15 units.&lt;/p&gt;

&lt;p&gt;DC_A checks local rights: 50 ≥ 15 ✓. Executes locally. No coordination needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DC_A: 35 rights remaining
DC_B: 30 rights remaining
DC_C: 20 rights remaining
Global value: 85
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Concurrent payment at DC_B:&lt;/strong&gt; User pays 25 units.&lt;/p&gt;

&lt;p&gt;DC_B checks local rights: 30 ≥ 25 ✓. Executes locally. DC_A and DC_B never talked during either payment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DC_A: 35 rights remaining
DC_B: 5 rights remaining
DC_C: 20 rights remaining
Global value: 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both payments went through. Both were validated purely locally. The global floor - wallet ≥ 0 - was never at risk, because the sum of all rights (35 + 5 + 20 = 60) exactly equals the remaining balance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failed payment at DC_C:&lt;/strong&gt; User tries to pay 25 units.&lt;/p&gt;

&lt;p&gt;DC_C checks local rights: 20 &amp;lt; 25 ✗. Cannot guarantee the floor locally.&lt;/p&gt;

&lt;p&gt;Two options: fail immediately, or coordinate to borrow rights from another replica.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rights transfer:&lt;/strong&gt; DC_C asks DC_A for 10 rights. DC_A has 35 and can spare them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DC_A: 25 rights  (gave 10 to DC_C)
DC_B: 5 rights
DC_C: 30 rights  (received 10 from DC_A)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DC_C re-attempts: 30 ≥ 25 ✓. Payment goes through.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DC_A: 25 rights
DC_B: 5 rights
DC_C: 5 rights
Global value: 35
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At no point did the wallet go below zero. At no point did all three servers need to agree on the exact current value before processing a payment. The invariant held through local checks alone - with one brief coordination event only when a replica ran out of its allocated rights.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Structure
&lt;/h2&gt;

&lt;p&gt;The Bounded Counter state at each replica tracks three things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;BoundedCounter&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Min&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;     &lt;span class="c"&gt;// lower bound (e.g. 0)&lt;/span&gt;
    &lt;span class="n"&gt;N&lt;/span&gt;   &lt;span class="kt"&gt;int&lt;/span&gt;       &lt;span class="c"&gt;// number of replicas&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;  &lt;span class="kt"&gt;int&lt;/span&gt;       &lt;span class="c"&gt;// this replica's index&lt;/span&gt;

    &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="p"&gt;[][]&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;   &lt;span class="c"&gt;// R[i][j]: rights replica i transferred to j&lt;/span&gt;
                  &lt;span class="c"&gt;// R[i][i]: rights replica i created for itself (via increments)&lt;/span&gt;
    &lt;span class="n"&gt;U&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;     &lt;span class="c"&gt;// U[i]: decrements executed at replica i&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rights matrix &lt;code&gt;R&lt;/code&gt; is the core. Each replica owns the diagonal - &lt;code&gt;R[i][i]&lt;/code&gt; is the rights it created. Off-diagonal entries &lt;code&gt;R[i][j]&lt;/code&gt; track rights transferred from replica &lt;code&gt;i&lt;/code&gt; to replica &lt;code&gt;j&lt;/code&gt;. Every entry only ever increases. This is the grow-only constraint, applied to a matrix instead of a single value.&lt;/p&gt;

&lt;p&gt;Reading the current value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BoundedCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dec&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;inc&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;dec&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Min&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;inc&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;dec&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sum the diagonal (total increments), subtract total decrements, add the floor.&lt;/p&gt;

&lt;p&gt;Checking local rights:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BoundedCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;LocalRights&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spent&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;
    &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;received&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;spent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;spent&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rights I created, plus rights I received, minus rights I gave away, minus rights I already spent.&lt;/p&gt;

&lt;p&gt;Decrementing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BoundedCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Dec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LocalRights&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not enough rights"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only decrement if you have the tickets. Otherwise, reject or trigger a rights transfer.&lt;/p&gt;

&lt;p&gt;Transferring rights:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BoundedCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LocalRights&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not enough rights to transfer"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The merge - identical pattern to every CRDT we've built:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BoundedCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BoundedCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Element-wise maximum. Every entry in &lt;code&gt;R&lt;/code&gt; and &lt;code&gt;U&lt;/code&gt; only ever grows. Merge is commutative, associative, idempotent. Replicas always converge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Invariant Can Never Break
&lt;/h2&gt;

&lt;p&gt;Here's the mathematical guarantee, stated plainly.&lt;/p&gt;

&lt;p&gt;Total decrement rights across all replicas = current value − floor.&lt;/p&gt;

&lt;p&gt;Each replica can only decrement by spending rights it owns. A replica cannot own negative rights. Therefore, the total decrements across all replicas can never exceed the total rights in the system. Therefore, the global value can never drop below the floor.&lt;/p&gt;

&lt;p&gt;This holds even when replicas have stale views of each other's state. Each replica's local check - "do I have enough rights?" - is conservative by design. If you have 20 rights and the operation needs 25, you say no. You might technically have been able to borrow rights from another replica - but you don't know that yet, and the safe answer is always no.&lt;/p&gt;

&lt;p&gt;Coordination is infrequent and amortised. For operations that fit within local rights, zero coordination. For operations that exceed local rights, one round-trip to request a transfer. Compared to coordinating on every write, this is a dramatic improvement in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-Off You Should Know
&lt;/h2&gt;

&lt;p&gt;Bounded Counter is the most complex CRDT in this series so far - and it comes with trade-offs worth naming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rights starvation:&lt;/strong&gt; if a replica runs out of rights and the replica it needs to borrow from is unreachable (network partition, server failure), the operation fails. The invariant is safe - you'd rather fail an operation than violate the floor - but the user experience is a rejection, not a delay. For inventory, that means "out of stock" even when stock technically exists elsewhere. Acceptable for correctness. Worth designing around.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State size scales with replica count:&lt;/strong&gt; the R matrix is N×N. With 3 replicas, 9 cells. With 100 replicas, 10,000. For most geo-distributed systems (2-7 data centres), this is negligible. For edge computing with thousands of nodes, you'd want a different approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rights rebalancing is a background concern:&lt;/strong&gt; a replica that consistently gets more traffic than its rights allocation will hit starvation more often. Operational tooling to monitor and rebalance rights distribution is not optional - it's part of running this in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Pattern
&lt;/h2&gt;

&lt;p&gt;Three episodes in, the shape of CRDT design is clear.&lt;/p&gt;

&lt;p&gt;Every CRDT we've built solves a hard distributed problem by identifying a constraint that eliminates ambiguity.&lt;/p&gt;

&lt;p&gt;G-Counter said: make it grow-only. The maximum is always correct.&lt;/p&gt;

&lt;p&gt;PN-Counter said: never decrement - use two grow-only counters and subtract at read time.&lt;/p&gt;

&lt;p&gt;Bounded Counter says: never coordinate on every operation - distribute the permission to operate instead.&lt;/p&gt;

&lt;p&gt;The constraint is always the solution. The art is finding the right one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The next piece steps back from counters entirely.&lt;/p&gt;

&lt;p&gt;What if instead of tracking numbers, you need to track membership - items in a set, users in a group, tags on a document? The same concurrent update problems apply. And a surprisingly simple grow-only constraint solves the basic case cleanly.&lt;/p&gt;

&lt;p&gt;Episode 4: G-Set - grow-only sets, where the items go in but never come out, and why that constraint enables a clean distributed implementation.&lt;/p&gt;

&lt;p&gt;The full series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Episode 1: &lt;a href="https://dev.to/architagr/youtube-doesnt-actually-know-how-many-views-your-video-has-52fm"&gt;G-Counter - why YouTube's view count is approximate&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Episode 2: &lt;a href="https://dev.to/architagr/reddits-karma-score-is-a-lie-heres-the-math-behind-it-14a4"&gt;PN-Counter - Reddit karma and the two-notebook trick&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Episode 3: Bounded Counter - inventory floors and distributed permission (you're here)&lt;/li&gt;
&lt;li&gt;Episode 4: G-Set - grow-only sets&lt;/li&gt;
&lt;li&gt;Episode 5: LWW Registry - last-write-wins&lt;/li&gt;
&lt;li&gt;Episode 6: OR-Set - the fix for "deleted but still there"&lt;/li&gt;
&lt;li&gt;Episode 7: The full picture - when to use CRDTs and when to walk away&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Subscribe to the newsletter to get each episode as it drops.&lt;br&gt;
If you're building distributed systems and want to think through invariant enforcement, CRDT design, or distributed architecture with someone who has been deep in this space - I do 1:1 sessions on &lt;a href="https://topmate.io/archit_agarwal" rel="noopener noreferrer"&gt;Topmate&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>go</category>
      <category>systemdesign</category>
      <category>programming</category>
    </item>
    <item>
      <title>Reddit's Karma Score Is a Lie. Here's the Math Behind It.</title>
      <dc:creator>Archit Agarwal</dc:creator>
      <pubDate>Mon, 11 May 2026 03:00:00 +0000</pubDate>
      <link>https://dev.to/architagr/reddits-karma-score-is-a-lie-heres-the-math-behind-it-14a4</link>
      <guid>https://dev.to/architagr/reddits-karma-score-is-a-lie-heres-the-math-behind-it-14a4</guid>
      <description>&lt;p&gt;Two comments. Same subreddit. Same topic. Posted minutes apart.&lt;/p&gt;

&lt;p&gt;One has +847 karma. It sits at the top, gets gilded, spawns a thread of 200 replies. The person who wrote it gets DMs. Their next post gets upvoted reflexively because people remember the name.&lt;/p&gt;

&lt;p&gt;The other has +1. It exists somewhere below the fold, unseen by 99% of readers. Same words, different fate.&lt;/p&gt;

&lt;p&gt;That number — Reddit karma — is one of the most consequential invisible forces on the internet. It shapes what millions of people read, what opinions gain traction, whose voices carry weight in a community. And most Reddit users have a vague sense of how it works: upvotes good, downvotes bad, net score determines visibility.&lt;/p&gt;

&lt;p&gt;What almost nobody knows is what's actually happening inside Reddit's infrastructure every time someone clicks that arrow.&lt;/p&gt;

&lt;p&gt;The score you see? It's a lie. Not a malicious one. A mathematically necessary, deliberately constructed approximation — and the algorithm behind it is one of the most elegant ideas in distributed systems.&lt;/p&gt;

&lt;p&gt;This is that story.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Reddit Karma Actually Is
&lt;/h2&gt;

&lt;p&gt;Let's start simple, because the simple version matters.&lt;/p&gt;

&lt;p&gt;Every post and comment on Reddit has a score. That score is calculated as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Score = Upvotes − Downvotes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A post with 1,200 upvotes and 200 downvotes shows a score of +1,000. A comment with 50 upvotes and 80 downvotes shows -30. Reddit uses this score to rank content — higher scores float up, lower scores sink. In highly active subreddits, the first few minutes of score accumulation can determine whether a post reaches the front page or disappears forever.&lt;/p&gt;

&lt;p&gt;The score also feeds into account-level karma, which affects posting privileges, community credibility, and how other users perceive you before they've read a single word you've written.&lt;/p&gt;

&lt;p&gt;Simple math. Massive consequences.&lt;/p&gt;

&lt;p&gt;Now let's build the system that computes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Simple Version (That Works Fine Until It Doesn't)
&lt;/h2&gt;

&lt;p&gt;Imagine Reddit in its early days. A few thousand users. One web server. One database.&lt;/p&gt;

&lt;p&gt;The schema for tracking votes is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;post_scores&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;post_id&lt;/span&gt;   &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt;     &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When someone upvotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;post_scores&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;post_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When someone downvotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;post_scores&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;post_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;User loads the page, you return &lt;code&gt;score&lt;/code&gt;. Done.&lt;/p&gt;

&lt;p&gt;This works beautifully for a small community. Your database handles concurrent updates with row-level locking. Reads are fast. Writes are fast. The score is always accurate.&lt;/p&gt;

&lt;p&gt;And then the site grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment Everything Gets Complicated
&lt;/h2&gt;

&lt;p&gt;Reddit today serves over 50 million daily active users. Popular posts on r/worldnews or r/AskReddit can accumulate thousands of votes in minutes. During major news events, that number spikes even higher.&lt;/p&gt;

&lt;p&gt;One database server cannot handle that write throughput. So you do what every scaling team eventually does — you distribute.&lt;/p&gt;

&lt;p&gt;You spin up multiple database replicas. You deploy servers in the US, Europe, and Asia so votes from each region get handled locally. Latency drops. Throughput goes up. Everything seems fine.&lt;/p&gt;

&lt;p&gt;Until two servers try to update the same post's score at the same time.&lt;/p&gt;

&lt;p&gt;Here's the problem in its simplest form. The US server reads the score: 142. The EU server reads the score: 142. A user in the US clicks upvote — the US server wants to write 143. Simultaneously, a user in Europe clicks downvote — the EU server wants to write 141.&lt;/p&gt;

&lt;p&gt;Both writes land. One of them overwrites the other. A real vote disappears.&lt;/p&gt;

&lt;p&gt;At small scale this is a rounding error. At Reddit scale, with thousands of concurrent votes per second on a single viral post, you're losing a meaningful number of real votes from real users. Users who clicked an arrow and expected their opinion to count.&lt;/p&gt;

&lt;p&gt;Let's be honest — this is not a hypothetical. This is the exact class of problem that has caused production incidents at companies you use every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Approaches That Don't Work
&lt;/h2&gt;

&lt;p&gt;The instinct is to reach for coordination. Make the servers talk to each other before writing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distributed locks:&lt;/strong&gt; before updating the score, acquire a lock on that post's row across all replicas. Only one server writes at a time. Others wait.&lt;/p&gt;

&lt;p&gt;This works. It's also slow. Lock acquisition across data centres separated by hundreds of milliseconds of network latency adds that latency to every single vote. During peak traffic, lock contention turns your vote system into a queue. Users notice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two-phase commit (2PC):&lt;/strong&gt; a coordinator server proposes the write, all participants acknowledge, then the coordinator commits. Guarantees consistency across all nodes.&lt;/p&gt;

&lt;p&gt;Also works. Also slow. 2PC requires multiple round trips between servers. It assumes all participants are available — if any node is down or unreachable during the commit phase, the transaction blocks. In a system operating across multiple continents, "all participants available" is never guaranteed.&lt;/p&gt;

&lt;p&gt;Both approaches trade availability for consistency. That's a valid trade-off in some systems. For a vote counter on a social platform — where a few hundred milliseconds of delay on every click would be immediately noticeable and wildly unacceptable — it's not.&lt;/p&gt;

&lt;p&gt;There had to be a better way.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Different Question
&lt;/h2&gt;

&lt;p&gt;The distributed systems community eventually asked a different question.&lt;/p&gt;

&lt;p&gt;Instead of "how do we make all servers agree before writing?" — what if we asked: &lt;strong&gt;"what constraints can we place on the data so that disagreements become impossible?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This line of thinking led to CRDTs — Conflict-free Replicated Data Types. Data structures specifically designed so that concurrent updates on multiple replicas can always be merged correctly, without coordination, without locks, without consensus rounds.&lt;/p&gt;

&lt;p&gt;The key insight: if you design your data structure such that the merge operation is always unambiguous — no matter what order updates arrive, no matter how long replicas were disconnected — you get eventual consistency for free.&lt;/p&gt;

&lt;p&gt;I covered the simplest CRDT — the G-Counter — in &lt;a href="https://dev.to/architagr/youtube-doesnt-actually-know-how-many-views-your-video-has-52fm"&gt;Episode 1 of this series&lt;/a&gt;. The short version: each replica keeps its own local count, only ever increments it, and merge means "take the maximum per replica." YouTube's view counter works this way. There's no conflict possible because a count that can only grow is always unambiguous — the higher number is always more up to date.&lt;/p&gt;

&lt;p&gt;But G-Counters have an obvious limitation.&lt;/p&gt;

&lt;p&gt;They only go up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Upvotes Alone Aren't Enough
&lt;/h2&gt;

&lt;p&gt;Reddit's score isn't a count of upvotes. It's upvotes minus downvotes. You need both directions.&lt;/p&gt;

&lt;p&gt;A G-Counter can track upvotes fine. But downvotes require decrement. And decrement is where things get complicated — because "subtract one" from a shared counter across distributed replicas brings back exactly the ambiguity we were trying to avoid.&lt;/p&gt;

&lt;p&gt;Two servers both see the score as 50. Both receive a downvote simultaneously. Both want to write 49. Same lost vote problem as before, just through a different door.&lt;/p&gt;

&lt;p&gt;So: we need increment and decrement, without coordination, with correct merging across replicas.&lt;/p&gt;

&lt;p&gt;Here's where things get interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Insight: You Never Actually Decrement
&lt;/h2&gt;

&lt;p&gt;This is the moment the whole thing clicks.&lt;/p&gt;

&lt;p&gt;What if you never decremented anything? What if every downvote was secretly an increment — just to a different counter?&lt;/p&gt;

&lt;p&gt;Go back to the lemonade stand mental model. You and your friends are tracking the cash register. Money comes in from customers. Money goes out for supplies. You need to track both.&lt;/p&gt;

&lt;p&gt;You already know the G-Counter trick: give everyone a notebook where they can only add tally marks. Works for money coming in.&lt;/p&gt;

&lt;p&gt;For money going out, the solution is almost embarrassingly simple: &lt;strong&gt;give everyone two notebooks.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;green notebook&lt;/strong&gt; for every dollar that came in&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;red notebook&lt;/strong&gt; for every dollar that went out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither notebook ever loses a mark. Both only ever grow. At the end of the day, you sync the green notebooks (take max per person), sync the red notebooks (take max per person), and do one subtraction:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Green total − Red total = what's in the register&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a PN-Counter. Positive-Negative Counter.&lt;/p&gt;

&lt;p&gt;Two G-Counters. One subtraction at read time. No coordination. No locks.&lt;/p&gt;

&lt;p&gt;The "decrement" you thought you needed was never a real operation. It was always just an increment to a second counter that you subtract when you read the value. The counter doesn't go down — your accounting does.&lt;/p&gt;

&lt;p&gt;The Code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;gotype&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;inc&lt;/span&gt; &lt;span class="n"&gt;GCounter&lt;/span&gt;
    &lt;span class="n"&gt;dec&lt;/span&gt; &lt;span class="n"&gt;GCounter&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewPNCounter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two G-Counters. That's the entire data structure.&lt;br&gt;
Reading the score:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The subtraction only happens here — at read time, when someone loads the page. Never during updates. Never during merges.&lt;/p&gt;

&lt;p&gt;Upvoting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;ReplicaId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Downvoting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Dec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;ReplicaId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at &lt;code&gt;Dec&lt;/code&gt;. It doesn't subtract anything. It calls &lt;code&gt;.Inc()&lt;/code&gt; on the &lt;em&gt;decrement counter&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A downvote is an upvote to the wrong notebook.&lt;/p&gt;

&lt;p&gt;If you showed this to someone without context they'd think it was a bug. It's the entire algorithm.&lt;/p&gt;

&lt;p&gt;Merging two replicas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;MergePN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PNCounter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the G-Counter merge twice — once on the increment counters, once on the decrement counters. Same "take the maximum per replica" logic. Nothing new to prove. Everything PN-Counter needs, it inherits from G-Counter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Looks Like on Reddit's Servers
&lt;/h2&gt;

&lt;p&gt;Let's trace a viral comment across three servers: US, EU, Asia.&lt;br&gt;
&lt;strong&gt;T=0:&lt;/strong&gt; Comment just posted. All counters at zero.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;US:   inc={US:0}, dec={US:0}  →  score: 0
EU:   inc={EU:0}, dec={EU:0}  →  score: 0
Asia: inc={As:0}, dec={As:0}  →  score: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;T=1:&lt;/strong&gt; Activity floods in. US gets 8 upvotes. EU gets 3 upvotes and 5 downvotes. Asia gets 2 downvotes. All happening simultaneously, all independent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;US:   inc={US:8}, dec={US:0}   →  local score: 8
EU:   inc={EU:3}, dec={EU:5}   →  local score: -2
Asia: inc={As:0}, dec={As:2}   →  local score: -2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;T=2:&lt;/strong&gt; US and EU sync.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inc = Merge({US:8}, {EU:3}) = {US:8, EU:3}
dec = Merge({US:0}, {EU:5}) = {US:0, EU:5}
Score = (8+3) - (0+5) = 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;US and EU now agree: score is &lt;strong&gt;+6&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;T=3:&lt;/strong&gt; Asia reconnects after a brief network partition and syncs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inc = {US:8, EU:3, As:0}  →  total: 11
dec = {US:0, EU:5, As:2}  →  total: 7
Score = 11 - 7 = 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final score: &lt;strong&gt;+4&lt;/strong&gt;. Every upvote counted. Every downvote counted. No vote lost during the partition. No coordination during any of the voting itself.&lt;/p&gt;

&lt;p&gt;This is the lie in the title: the +4 you see wasn't computed from one authoritative source. It was assembled from fragments of state scattered across three continents and merged after the fact.&lt;/p&gt;

&lt;p&gt;It's an approximation. A consistent, mathematically guaranteed approximation — but an approximation nonetheless.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-Off Worth Naming
&lt;/h2&gt;

&lt;p&gt;PN-Counter inherits G-Counter's main cost: every sync ships full state. Two maps, N entries each, across every gossip round. For tens of replicas this is fine. For thousands of edge nodes, the bandwidth adds up.&lt;/p&gt;

&lt;p&gt;The more important trade-off is &lt;strong&gt;eventual consistency&lt;/strong&gt;. When you're actively voting on a hot post, the score you see is the best approximation your nearest server has right now. Somewhere in Europe, a server might briefly disagree. They'll converge — they always do — but there's a window where different users see slightly different scores.&lt;/p&gt;

&lt;p&gt;For Reddit, this is entirely acceptable. Nobody's life depends on whether the karma score reads 1,042 or 1,047 for 200 milliseconds. The system is highly available, survives network partitions cleanly, and handles write throughput that would collapse a coordinated system.&lt;/p&gt;

&lt;p&gt;The trade-off is: exact consistency in exchange for scale and availability. Reddit made that trade. Most social platforms do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem PN-Counter Doesn't Solve
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting again — and where the next problem is quietly waiting.&lt;/p&gt;

&lt;p&gt;PN-Counter has no floor. No ceiling. Nothing prevents the score from going to -50,000 if enough downvotes arrive. And nothing stops it from going to +10,000,000 either.&lt;/p&gt;

&lt;p&gt;For Reddit karma, that's fine. But consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A &lt;strong&gt;distributed rate limiter&lt;/strong&gt; that needs to cap requests at 1,000 per minute. The counter must not go above 1,000, enforced across all replicas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An &lt;strong&gt;inventory system&lt;/strong&gt; where stock can't go below zero. You can't sell items you don't have.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;strong&gt;per-user karma floor&lt;/strong&gt; — some subreddits prevent karma from dropping below a certain value to protect new users from pile-ons.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;strong&gt;bounded&lt;/strong&gt; operations. And PN-Counter, with its two unbounded G-Counters, cannot enforce bounds without coordination.&lt;/p&gt;

&lt;p&gt;You'd think: just check the current value before decrementing, and reject the operation if you'd go below the floor.&lt;/p&gt;

&lt;p&gt;Try that across distributed replicas and you immediately reintroduce the problem we just spent this entire article solving. Two replicas both see the counter at 1 (the floor). Both receive a decrement. Both check locally — "1 is above 0, I can proceed." Both decrement. Counter is now -1 across the board. The floor was violated.&lt;/p&gt;

&lt;p&gt;This is the problem the Bounded Counter CRDT was designed for. It's the next piece in this series — and it's the most complex CRDT we'll cover, because enforcing limits without coordination requires a genuinely different approach.&lt;/p&gt;

&lt;p&gt;The short version: you can't do it with just max and subtraction. You need to distribute the permission to decrement itself.&lt;/p&gt;

&lt;p&gt;But that's next time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Thread Running Through All of This
&lt;/h2&gt;

&lt;p&gt;Two episodes in, the pattern is clear.&lt;/p&gt;

&lt;p&gt;Every CRDT solves a hard distributed problem by finding a constraint that eliminates ambiguity. G-Counter said: make it grow-only, and max is always correct. PN-Counter said: never decrement — maintain two grow-only counters and subtract at read time.&lt;/p&gt;

&lt;p&gt;The constraint is never a workaround. It's the design.&lt;/p&gt;

&lt;p&gt;When you next open Reddit and see a karma score, you're looking at the output of two grow-only counters being summed and subtracted on a server near you — a server that may not have talked to its peers in the last few hundred milliseconds and doesn't need to.&lt;/p&gt;

&lt;p&gt;The number is an approximation. But it's a principled one, built on math that guarantees eventual correctness without sacrificing the availability and performance that make Reddit usable at scale.&lt;/p&gt;

&lt;p&gt;That's not a lie. That's engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;This is Episode 2 of a series on CRDTs — the data structures powering Google Docs, Redis, Riak, and distributed infrastructure at scale.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Episode 1:&lt;/strong&gt; &lt;a href="https://dev.to/architagr/youtube-doesnt-actually-know-how-many-views-your-video-has-52fm"&gt;G-Counter — why YouTube's view count is always approximate&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Episode 2:&lt;/strong&gt; PN-Counter — Reddit karma and the two-notebook trick (you're here)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Episode 3:&lt;/strong&gt; G-Set — grow-only sets, and where they appear in the wild&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Episode 4:&lt;/strong&gt; LWW Registry — when "latest timestamp wins" is good enough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Episode 5:&lt;/strong&gt; OR-Set — the elegant fix for the "deleted but still there" problem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Episode 6:&lt;/strong&gt; Map CRDT — composing CRDTs into richer structures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Episode 7:&lt;/strong&gt; The full picture — when to reach for CRDTs and when to walk away&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Subscribe to the newsletter to get each episode as it drops.&lt;/p&gt;

&lt;p&gt;If you're building distributed systems and want to think through architecture decisions with someone who has spent years deep in Go and distributed infrastructure — I do 1:1 sessions on &lt;a href="https://topmate.io/archit_agarwal" rel="noopener noreferrer"&gt;Topmate&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>go</category>
      <category>systemdesign</category>
      <category>software</category>
    </item>
    <item>
      <title>YouTube Doesn't Actually Know How Many Views Your Video Has</title>
      <dc:creator>Archit Agarwal</dc:creator>
      <pubDate>Tue, 05 May 2026 11:26:39 +0000</pubDate>
      <link>https://dev.to/architagr/youtube-doesnt-actually-know-how-many-views-your-video-has-52fm</link>
      <guid>https://dev.to/architagr/youtube-doesnt-actually-know-how-many-views-your-video-has-52fm</guid>
      <description>&lt;p&gt;And that's not a bug. It's one of the most elegant algorithms in distributed systems.&lt;/p&gt;

&lt;p&gt;Every time you refresh a viral YouTube video and watch the counter tick up, you're looking at a lie.&lt;/p&gt;

&lt;p&gt;Not a malicious one. A mathematically precise, deliberately constructed one — built by some of the smartest distributed systems engineers on the planet, for a very good reason.&lt;/p&gt;

&lt;p&gt;The actual view count? Nobody knows it exactly. Not YouTube's servers. Not their databases. Not the team that built the counter.&lt;br&gt;
Here's what's actually going on, and why the algorithm behind it — called a &lt;strong&gt;G-Counter&lt;/strong&gt; — is one of the most satisfying ideas in computer science.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem With Counting at Scale
&lt;/h2&gt;

&lt;p&gt;Let's be honest about what YouTube is dealing with.&lt;/p&gt;

&lt;p&gt;A viral video gets 50 million plays in its first 24 hours. That's roughly 578 plays every second. These plays are happening from Los Angeles, London, Mumbai, São Paulo — simultaneously, all the time.&lt;br&gt;
YouTube doesn't run one server. It runs thousands, distributed across data centres on multiple continents. Every server is independently accepting "play" events.&lt;/p&gt;

&lt;p&gt;The naive solution: have one authoritative counter. Every time someone hits play, that event travels to the central counter, which increments and returns the new value.&lt;/p&gt;

&lt;p&gt;The problem: that central counter immediately becomes the bottleneck. At 578 writes per second, you'd need a heavily optimised database just for the counter. And when it goes down — because everything goes down eventually — your entire view counting system goes with it.&lt;/p&gt;

&lt;p&gt;The slightly less naive solution: use a distributed lock. Servers coordinate before writing. They take turns.&lt;/p&gt;

&lt;p&gt;The problem with locks at scale: latency multiplies, throughput collapses, and network partitions turn the whole thing into a mess. In distributed systems, the CAP theorem tells you this is a fundamental tradeoff — you can't have consistency, availability, and partition tolerance all at once. When you choose strong consistency (everyone agrees on the exact count), you sacrifice availability and performance.&lt;/p&gt;

&lt;p&gt;So YouTube made a different choice: &lt;strong&gt;stop trying to coordinate at all.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Insight That Changes Everything
&lt;/h2&gt;

&lt;p&gt;Here's the mental shift that makes G-Counters work.&lt;/p&gt;

&lt;p&gt;What does it mean for a view count to be "correct"?&lt;/p&gt;

&lt;p&gt;If the US server has recorded 1.2 million views, and the EU server has recorded 800 thousand views, and the Asia server has recorded 600 thousand views — and none of these servers have talked to each other recently — the total isn't ambiguous. It's &lt;strong&gt;2.6 million&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You just add them up.&lt;/p&gt;

&lt;p&gt;The reason this is safe is the key constraint: &lt;strong&gt;view counts can only go up&lt;/strong&gt;. Nobody un-watches a video. Nobody subtracts views. The counter is monotonically increasing.&lt;/p&gt;

&lt;p&gt;This constraint completely eliminates the problem of conflicting state. If the US server thinks it has 1.2M views and later discovers the EU server also thought the US had 1.1M views (because the EU server's information was slightly stale), there's no conflict to resolve — 1.2M is clearly more up-to-date, because the counter can only increase. You just take the higher number.&lt;/p&gt;

&lt;p&gt;That insight — that the &lt;strong&gt;maximum value is always the correct value for a grow-only counter&lt;/strong&gt; — is the foundation of the G-Counter algorithm.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Simpler Way to See It
&lt;/h2&gt;

&lt;p&gt;Before we look at the code, here's the mental model I find most useful.&lt;/p&gt;

&lt;p&gt;Imagine three friends — call them US, EU, and Asia — who are each counting birds in different sections of a massive park. The park is too big to shout across, so they can't coordinate in real time.&lt;/p&gt;

&lt;p&gt;Each person has their own notebook. The rule is simple: you can add tally marks, but you can never erase them (because you can't un-see a bird).&lt;/p&gt;

&lt;p&gt;At the end of the day, they meet up. US has 3 marks. EU has 5 marks. Asia has 2 marks.&lt;/p&gt;

&lt;p&gt;Total birds seen: 10.&lt;/p&gt;

&lt;p&gt;Here's what makes this work without any drama: even if they ran into each other in the middle of the day and shared their notebooks, there's no conflict possible. If EU had shared their notebook earlier and it showed 4 marks at the time, and now it shows 5 — 5 is just the more current number. Take the maximum. Move on.&lt;/p&gt;

&lt;p&gt;When you replace "friends" with "servers" and "notebook" with "replica state", you have a G-Counter.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Data Structure
&lt;/h2&gt;

&lt;p&gt;A G-Counter is, at its core, just a map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ReplicaId&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;GCounter&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ReplicaId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;GCounter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each replica (each server) owns exactly one key in the map. &lt;strong&gt;A replica only ever writes to its own key&lt;/strong&gt;. This invariant is everything — it's what makes the merge operation safe.&lt;br&gt;
Reading the total is simple addition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;GCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Incrementing means bumping your own key by 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;GCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;ReplicaId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GCounter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;newC&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GCounter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;newC&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;newC&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newC&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newC&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We copy the map to keep things immutable — each increment returns a new GCounter rather than mutating in place. This is a conscious design choice that makes reasoning about state considerably easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Merge Function — Where the Magic Lives
&lt;/h2&gt;

&lt;p&gt;This is the core of the whole algorithm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;GCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GCounter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each replica ID, we take the &lt;strong&gt;maximum value&lt;/strong&gt; seen across both maps.&lt;br&gt;
That's it. No locks. No consensus rounds. No leader election. 15 lines of Go.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why "Take the Maximum" Is Mathematically Safe
&lt;/h2&gt;

&lt;p&gt;Here's where things get interesting — because this works for a deeper reason than it might seem.&lt;/p&gt;

&lt;p&gt;For a merge function to be safe in a system where replicas can sync asynchronously, in any order, any number of times — it needs to satisfy three mathematical properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Commutativity:&lt;/strong&gt; &lt;code&gt;Merge(A, B)&lt;/code&gt; must equal &lt;code&gt;Merge(B, A)&lt;/code&gt;. It shouldn't matter which server initiates the sync.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Associativity:&lt;/strong&gt; &lt;code&gt;Merge(Merge(A, B), C)&lt;/code&gt; must equal &lt;code&gt;Merge(A, Merge(B, C))&lt;/code&gt;. It shouldn't matter how you group the merges.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Idempotency:&lt;/strong&gt; &lt;code&gt;Merge(A, A)&lt;/code&gt; must equal &lt;code&gt;A&lt;/code&gt;. If a server accidentally receives the same state twice — because of a network retry, for example — nothing should break.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The maximum function satisfies all three. Maximum(3, 5) equals Maximum(5, 3). Maximum(Maximum(3, 5), 7) equals Maximum(3, Maximum(5, 7)). Maximum(5, 5) equals 5.&lt;/p&gt;

&lt;p&gt;A data structure whose merge function has these three properties is called a &lt;strong&gt;Convergent Replicated Data Type (CvRDT)&lt;/strong&gt;, or a state-based CRDT. The "convergent" part is the guarantee: no matter what order messages arrive in, no matter how long servers are disconnected, they will always end up agreeing on the same state when they finally sync.&lt;/p&gt;

&lt;p&gt;This guarantee — &lt;strong&gt;eventual consistency without coordination&lt;/strong&gt; — is the entire value proposition of CRDTs.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Concrete Walk-Through
&lt;/h2&gt;

&lt;p&gt;Let's trace through exactly what happens with three servers.&lt;br&gt;
&lt;strong&gt;T=0&lt;/strong&gt;: All servers start at zero.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;US:   {US: 0}
EU:   {EU: 0}
Asia: {Asia: 0}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;T=1:&lt;/strong&gt; US receives 3 plays, EU receives 5, Asia receives 2. All independently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;US:   {US: 3}
EU:   {EU: 5}
Asia: {Asia: 2}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;T=2:&lt;/strong&gt; US and EU sync. &lt;code&gt;Merge({US:3}, {EU:5})&lt;/code&gt; → &lt;code&gt;{US:3, EU:5}&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;US:   {US: 3, EU: 5}  →  Value() = 8
EU:   {US: 3, EU: 5}  →  Value() = 8
Asia: {Asia: 2}        →  Value() = 2  (still hasn't synced)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;T=3: Asia comes back online after a brief network partition. It merges with US.&lt;br&gt;
&lt;code&gt;Merge({US:3, EU:5}, {Asia:2})&lt;/code&gt; → &lt;code&gt;{US:3, EU:5, Asia:2}&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;All servers: {US:3, EU:5, Asia:2}  →  Value() = 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every play counted. No coordination required during the counting itself. The merge handles everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Shows Up in the Real World
&lt;/h2&gt;

&lt;p&gt;G-Counters (or something very close to them) are embedded in more infrastructure than most engineers realise:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;YouTube and Twitch view counts&lt;/strong&gt; — the exact use case we opened with. Regional servers count locally, merge periodically. The displayed count is always approximate, always eventually consistent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redis in distributed mode&lt;/strong&gt; — Redis Enterprise uses CRDT-based counters when data is replicated across data centres. The INCR command on a distributed counter works precisely because of this algorithm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analytics pipelines&lt;/strong&gt; — tools like Segment and Amplitude track event counts at edge nodes before rolling up. The edge nodes are essentially running G-Counters before flushing to a central store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Riak&lt;/strong&gt; — the distributed database that helped popularise CRDTs in production, built native G-Counter support into its data model specifically for use cases like these.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gaming leaderboards&lt;/strong&gt; — any system tracking "total kills" or "total distance" across game sessions that can happen offline is solving the same problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-Off You Should Know
&lt;/h2&gt;

&lt;p&gt;G-Counters are simple and powerful, but they come with a cost worth understanding.&lt;/p&gt;

&lt;p&gt;Because this is a &lt;strong&gt;state-based CRDT&lt;/strong&gt; (also called CvRDT), every time two replicas sync, they exchange their entire state. With 3 replicas, that's a tiny map — 3 key-value pairs. With 1,000 replicas, every gossip round ships 1,000 entries over the wire, even if only one entry changed.&lt;/p&gt;

&lt;p&gt;This is the fundamental tension in state-based CRDTs: simplicity of reasoning versus efficiency of replication.&lt;/p&gt;

&lt;p&gt;The solution to this is &lt;strong&gt;Delta-state CRDTs&lt;/strong&gt;, where replicas only send the delta — what changed since the last sync — rather than the whole state. Same mathematical guarantees, much lower bandwidth. That's a topic for a future article.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraint Is the Solution
&lt;/h2&gt;

&lt;p&gt;Here's the thing I find most elegant about G-Counters.&lt;/p&gt;

&lt;p&gt;The constraint that initially seems limiting — you can only ever increment, never decrement — is exactly what makes the algorithm so clean. It eliminates an entire class of conflict. It makes the merge function trivially provable. It turns a hard distributed systems problem into a single line: take the max.&lt;/p&gt;

&lt;p&gt;This pattern shows up everywhere in good system design. When you can't have everything, the question becomes: what do I give up, and what do I get in return? YouTube gave up exact real-time accuracy on view counts. In return, they got a counter that scales to any number of servers, works through network partitions, needs no coordination, and never loses a view.&lt;/p&gt;

&lt;p&gt;That's a trade-off most engineering teams would take without hesitation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;This is Episode 1 of a series I'm writing on CRDTs — the data structures that power Google Docs, distributed databases, offline-first applications, and collaborative tools.&lt;/p&gt;

&lt;p&gt;Episode 2 covers &lt;strong&gt;PN-Counters&lt;/strong&gt; — what happens when you need to both increment and decrement, without conflicts. Spoiler: you just use two G-Counters and subtract. The simplicity is almost frustrating.&lt;br&gt;
The full series:&lt;/p&gt;

&lt;p&gt;Episode 1: G-Counter (you're here)&lt;br&gt;
Episode 2: PN-Counter — increment and decrement&lt;br&gt;
Episode 3: G-Set — grow-only sets&lt;br&gt;
Episode 4: LWW Registry — last-write-wins&lt;br&gt;
Episode 5: OR-Set — observed-remove sets&lt;br&gt;
Episode 6: Map CRDT — composing CRDTs into richer structures&lt;br&gt;
Episode 7: The full picture — when to use CRDTs and when not to&lt;/p&gt;

&lt;p&gt;Follow along on [your newsletter/Substack URL] to get each episode as it drops.&lt;/p&gt;

&lt;p&gt;If you're building distributed systems and want to think through architecture decisions with someone who has been in the weeds on this — I do 1:1 sessions on &lt;a href="https://topmate.io/archit_agarwal" rel="noopener noreferrer"&gt;Topmate&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>go</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>How to Build Heartbeats in Go: Let Your Goroutines Say 'Still Breathing!'</title>
      <dc:creator>Archit Agarwal</dc:creator>
      <pubDate>Wed, 21 May 2025 10:45:00 +0000</pubDate>
      <link>https://dev.to/architagr/how-to-build-heartbeats-in-go-let-your-goroutines-say-still-breathing-3mi8</link>
      <guid>https://dev.to/architagr/how-to-build-heartbeats-in-go-let-your-goroutines-say-still-breathing-3mi8</guid>
      <description>&lt;p&gt;&lt;strong&gt;Ever wonder how to make your Go services show signs of life… even when they’re bored out of their mind?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You’re not alone.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Imagine this:&lt;/em&gt; You’ve got a bunch of goroutines quietly waiting for work to show up. All seems peaceful. Until… boom — you discover one of them died hours ago, and no one told you. No logs, no panics, no error traces — just pure ghosting.&lt;/p&gt;

&lt;p&gt;Wanna avoid that silent death? Let’s teach our goroutines to breathe — or more specifically, &lt;strong&gt;send heartbeats&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🫀 Why Bother with Heartbeats?
&lt;/h2&gt;

&lt;p&gt;Ever had a background task silently die while your main process happily spins along? Or had to debug why a job queue worker went unresponsive in the middle of the night?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heartbeats&lt;/strong&gt; help answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Hey, is that goroutine still alive… or did it take an early retirement?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’re building anything slightly concurrent, heartbeat signals become your tiny, periodic signs of life from those goroutines. You can monitor them, restart them, or just breathe a little easier knowing things are ticking.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Let’s Build It!
&lt;/h2&gt;

&lt;p&gt;Let’s say you’ve got a worker goroutine that waits for signals to do some work. While it’s waiting, you also want it to occasionally let the outside world know:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Still alive, boss. Just waiting for the next gig.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s how we make that happen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;dowork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;pulseInterval&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;heartbeater&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
 &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
 &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heartbeater&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;pulse&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pulseInterval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;workGen&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pulseInterval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;pulse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;workGen&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="n"&gt;sendPulse&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;heartbeater&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
   &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// drop if nobody's listening&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;sendResult&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;pulse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
     &lt;span class="n"&gt;sendPulse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
   &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;pulse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sendPulse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;workGen&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sendResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{})&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}()&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;heartbeater&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function returns two channels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;heartbeater&lt;/code&gt;: a non-blocking signal that says, "I'm alive!"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;result&lt;/code&gt;: a channel that emits actual work when done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the main function, we simulate a 10-second lifetime for our job using &lt;code&gt;time.AfterFunc&lt;/code&gt;. We read from both the heartbeat and result channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  📡 The Main Function: Listening for Life
&lt;/h2&gt;

&lt;p&gt;Here's what that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
 &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AfterFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

 &lt;span class="n"&gt;pulseInterval&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;
 &lt;span class="n"&gt;heartbreater&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dowork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pulseInterval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;heartbreater&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"worker heartbeat stopped"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"worker heartbeat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"worker completed work"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}()&lt;/span&gt;

 &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see heartbeats printed every second, and actual work output every 3 seconds. After 10 seconds, the goroutine closes done, and everything wraps up gracefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 A Few Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Heartbeats&lt;/strong&gt; let your monitoring tools or orchestration layer know things are alive, even when no work is happening.&lt;/li&gt;
&lt;li&gt;Non-blocking sends (select { case ch &amp;lt;- val: default: }) are perfect for "if you're listening, here's a signal-otherwise, no big deal."&lt;/li&gt;
&lt;li&gt;Keep your goroutines polite: if they die, let them clean up after themselves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👀 Final Thoughts&lt;br&gt;
Implementing heartbeats in Go isn't just a cool concurrency trick - it's a must-have tool when you're building resilient systems. You don't want to wait until 3 AM to find out that your queue worker ghosted you. Be proactive. Make your goroutines send a little "I'm alive" wave now and then.&lt;br&gt;
Because of silent failures?&lt;br&gt;
They're not just frustrating - they're expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Full source code available on GitHub:
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/architagr/The-Weekly-Golang-Journal/tree/main/heart-beat" rel="noopener noreferrer"&gt;architagr/The-Weekly-Golang-Journal - heart beat&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Stay Connected!
&lt;/h2&gt;

&lt;p&gt;💡 Follow me on LinkedIn: &lt;a href="https://www.linkedin.com/in/architagarwal984/" rel="noopener noreferrer"&gt;Archit Agarwal&lt;/a&gt; &lt;br&gt;
🎥 Subscribe to my YouTube: &lt;a href="https://www.youtube.com/c/TheExceptionHandler" rel="noopener noreferrer"&gt;The Exception Handler&lt;/a&gt;&lt;br&gt;
 📬 Sign up for my newsletter: &lt;a href="https://www.linkedin.com/newsletters/the-weekly-golang-journal-7261403856079597568/" rel="noopener noreferrer"&gt;The Weekly Golang Journal&lt;/a&gt;&lt;br&gt;
✍️ Follow me on Medium: &lt;a href="https://medium.com/@architagr" rel="noopener noreferrer"&gt;@architagr&lt;/a&gt;&lt;br&gt;
👨‍💻 Join my subreddit: &lt;a href="https://www.reddit.com/r/GolangJournal" rel="noopener noreferrer"&gt;r/GolangJournal&lt;/a&gt;&lt;br&gt;
👨‍💻 Follow me on twitter: &lt;a href="https://x.com/architagr" rel="noopener noreferrer"&gt;@architagr&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>concurrency</category>
      <category>goroutine</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Silent Killers in Your Go App: Unhandled Errors</title>
      <dc:creator>Archit Agarwal</dc:creator>
      <pubDate>Wed, 14 May 2025 10:45:00 +0000</pubDate>
      <link>https://dev.to/architagr/the-silent-killers-in-your-go-app-unhandled-errors-2a76</link>
      <guid>https://dev.to/architagr/the-silent-killers-in-your-go-app-unhandled-errors-2a76</guid>
      <description>&lt;p&gt;Do you know what's scarier than a panic in a Golang application? 🤔&lt;br&gt;
An error that doesn't panic, doesn't log, &lt;strong&gt;doesn't show up&lt;/strong&gt; - but silently fails in production.&lt;br&gt;
These are like &lt;strong&gt;landmines&lt;/strong&gt; scattered across your codebase, patiently waiting to blow up at the worst possible time - like when you're sipping on a cold beer 🍺, celebrating a successful release.&lt;br&gt;
And then…&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📞 "Hey, the feature isn't working. Logs look clean. What's going on?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Oh no. That's your PM. On a weekend. With no error trace.&lt;br&gt;
I've been there. And yes, I left my beer mid-sip. 😞&lt;/p&gt;

&lt;p&gt;When I first started with Go, I didn't fully grasp that errors in Go are &lt;strong&gt;first-class citizens&lt;/strong&gt; - passed around just like any other value. If you don't handle them, they don't scream. They whisper. And that whisper can turn into a production outage.&lt;/p&gt;

&lt;p&gt;So this article is not just a rant. It's a &lt;strong&gt;survival guide&lt;/strong&gt; - to help you prevent these ghost errors from ruining your weekend or your system's reputation. 🧯&lt;/p&gt;

&lt;p&gt;Let's walk through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to structure your error objects for better debugging&lt;/li&gt;
&lt;li&gt;How to distinguish between expected and unexpected errors&lt;/li&gt;
&lt;li&gt;And how to turn unhandled errors into actionable alerts for your team&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Why Error Handling in Go Needs Your Full Attention
&lt;/h2&gt;

&lt;p&gt;Not all errors are created equal. Some are edge cases you anticipate. Some are wild cards. But all of them need structure and context if you want to trace them later.&lt;/p&gt;

&lt;p&gt;✅ Expected (or edge-case) errors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User requests a resource that doesn't exist&lt;/li&gt;
&lt;li&gt;Invalid query parameter&lt;/li&gt;
&lt;li&gt;Authorization failure&lt;/li&gt;
&lt;li&gt;Timeout or network delay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are normal. They happen. You handle them, maybe return a &lt;code&gt;400&lt;/code&gt; or &lt;code&gt;403&lt;/code&gt;, and move on.&lt;/p&gt;

&lt;p&gt;❌ Unexpected errors (the real troublemakers):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DB connection fails&lt;/li&gt;
&lt;li&gt;Memory exhaustion&lt;/li&gt;
&lt;li&gt;Filesystem full&lt;/li&gt;
&lt;li&gt;Internal services misbehaving&lt;/li&gt;
&lt;li&gt;Panic due to nil pointer dereference&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These should never happen - but if they do, &lt;strong&gt;you want to know immediately.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What Should Your Error Object Include?
&lt;/h2&gt;

&lt;p&gt;Think of your error like a black box recorder. When things go wrong, it should tell you &lt;strong&gt;exactly what happened and how to reproduce it&lt;/strong&gt;. Here's what it must include:&lt;/p&gt;
&lt;h4&gt;
  
  
  🧩 What happened?
&lt;/h4&gt;

&lt;p&gt;A clear description: e.g. "invalid DB credentials", "failed to marshal JSON", or "timeout while calling payment gateway".&lt;/p&gt;
&lt;h4&gt;
  
  
  📍 Where did it happen?
&lt;/h4&gt;

&lt;p&gt;Add stack traces or function names - Go makes this easy with tools like runtime.Caller() or libraries like pkg/errorsor xerrors.&lt;/p&gt;
&lt;h4&gt;
  
  
  ⏱️ When did it happen?
&lt;/h4&gt;

&lt;p&gt;Include request context: user ID, request ID, trace ID, timestamp - all of these help recreate the scenario and debug faster.&lt;/p&gt;
&lt;h4&gt;
  
  
  💬 What should the user see?
&lt;/h4&gt;

&lt;p&gt;Don't leak internals! Instead, return friendly, actionable messages:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Something went wrong. Please get in touch with support with Reference ID: xyz123."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  🔍 How can the devs debug it further?
&lt;/h4&gt;

&lt;p&gt;Include traceable metadata in logs or bug tracking systems so your team has breadcrumbs to follow. Reference IDS go a long way.&lt;/p&gt;

&lt;p&gt;In the next section, we'll build a real REST API example and demonstrate how to put this into practice,  gracefully handling, categorising, and propagating errors up to monitoring and alerting systems.&lt;/p&gt;

&lt;p&gt;But before that, just remember this:&lt;br&gt;
&lt;strong&gt;Handling errors isn't just about avoiding panics - it's about staying in control of your system even when it misbehaves.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Building a REST API in Go (Without Losing Your Mind Over Errors)
&lt;/h2&gt;

&lt;p&gt;So, imagine we're building a REST API server - nothing fancy, just your good old layered Go backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Router Layer&lt;/strong&gt;: Parses the HTTP request and routes it appropriately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Layer&lt;/strong&gt;: Decides whether to hit the DB, cache, or file system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence Layer&lt;/strong&gt;: Deals directly with the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's zoom into the persistence layer, where two common issues can ruin your day:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The database isn't reachable (classic).&lt;/li&gt;
&lt;li&gt;The data just… doesn't exist (also classic).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the second case, we create a custom error structure to wrap all the "not found" drama:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ObjectNotFoundError&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;ObjectType&lt;/span&gt;        &lt;span class="kt"&gt;string&lt;/span&gt;         &lt;span class="s"&gt;`json:"objectType"`&lt;/span&gt;
 &lt;span class="n"&gt;ObjectIdentifiers&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="s"&gt;`json:"objectIdentifier"`&lt;/span&gt;
 &lt;span class="n"&gt;StackTrace&lt;/span&gt;        &lt;span class="kt"&gt;string&lt;/span&gt;         &lt;span class="s"&gt;`json:"stackTrace"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ObjectNotFoundError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s not found, filter properties: [%v]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectIdentifiers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ObjectNotFoundError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;LogMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧠 What's Cool About This?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Error()&lt;/strong&gt; gives a user-friendly summary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LogMessage()&lt;/strong&gt; gives devs all the gritty details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ObjectType&lt;/strong&gt; tells us what was missing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ObjectIdentifiers&lt;/strong&gt; explains how we looked for it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;StackTrace&lt;/strong&gt; points us right to where the problem happened.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's see this in action in the persistence layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usrPer&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserPersistence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;sqlQuery&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"select * from users where id = %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;usrPer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseDb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;   &lt;span class="c"&gt;// --- 1️⃣&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ObjectNotFoundError&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;  &lt;span class="c"&gt;// --- 2️⃣&lt;/span&gt;
   &lt;span class="n"&gt;ObjectType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;ObjectIdentifiers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
   &lt;span class="n"&gt;StackTrace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="c"&gt;// else we will extract data from the data, for this we will hard code this return data&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user-%d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;CreatedDate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Breakdown
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;1️⃣ We forward the DB error if it happens.&lt;/li&gt;
&lt;li&gt;2️⃣ We simulate an invalid user ID scenario (I know validation doesn't belong here, but bear with me - this is just for the demo).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A small disclaimer:&lt;/strong&gt; I know this is not the right place for this validation, but I have only done this to explain how to gracefully propagate the error from the innermost layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now Let's Head Over to the Service Layer 🚪
&lt;/h2&gt;

&lt;p&gt;Here's where we catch and convert those deep, scary errors into something the client can actually digest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ServiceError&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;InnerError&lt;/span&gt;     &lt;span class="kt"&gt;error&lt;/span&gt;
 &lt;span class="n"&gt;HttpStatusCode&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
 &lt;span class="n"&gt;HttpResponse&lt;/span&gt;   &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrorResponse&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ServiceError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InnerError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ServiceError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetHttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ServiceError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetErrorResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrorResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpResponse&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also define a handy interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ILogMessageError&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kt"&gt;error&lt;/span&gt;
 &lt;span class="n"&gt;LogMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, here's the service logic in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;svc&lt;/span&gt; &lt;span class="n"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"get in the svc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userPersistenceObj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ILogMessageError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;logBug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"can not retrive user: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧪 What's Happening Here?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interface Check:&lt;/strong&gt; We see if the error implements &lt;code&gt;ILogMessageError&lt;/code&gt; - if yes, it's a known error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WrapError:&lt;/strong&gt; Transforms the error into a format with HTTP status and message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;logBug():&lt;/strong&gt; If it's an unexpected error, alert the engineering team. PagerDuty bells ring. People panic. You know the drill.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// handle all types of errors that we expect the persistence to return&lt;/span&gt;
&lt;span class="c"&gt;// or we can have a builder to build the ServiceError, and also follow&lt;/span&gt;
&lt;span class="c"&gt;// The open and closed principle, and single responsibility principal&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;svc&lt;/span&gt; &lt;span class="n"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;WrapError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="n"&gt;ILogMessageError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ServiceError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;objNotFound&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;persistence&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectNotFoundError&lt;/span&gt;
 &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ServiceError&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;objNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ServiceError&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;InnerError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrorResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ErrorCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;"usr-404"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ErrorDescription&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;logBug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c"&gt;// log a bug and page enginnering team about the error&lt;/span&gt;
 &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"logged a bug for error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finally, The Router - The Gatekeeper to the Client 🌐
&lt;/h2&gt;

&lt;p&gt;Here, we check whether the service-level error can be converted to an HTTP response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;IHttpResponseError&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;GetHttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
 &lt;span class="n"&gt;GetErrorResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrorResponse&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;UserRouter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userReq&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShouldBindUri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;userReq&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userServiceObj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;userReq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IHttpResponseError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetErrorResponse&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧩 Why This Works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Handled errors&lt;/strong&gt; bubble up in a predictable format with HTTP codes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unhandled errors&lt;/strong&gt; raise the flag to devs and get returned as generic 500s.&lt;/li&gt;
&lt;li&gt;Clients stay informed. Devs stay sane. Production stays alive. 😅&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🎯 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;And there you have it - an end-to-end error handling strategy that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeps your layers clean and decoupled&lt;/li&gt;
&lt;li&gt;Makes debugging a breeze with structured error logs&lt;/li&gt;
&lt;li&gt;Ensures your users never see a dreaded "500 Internal Server Error" without context&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔗 Full source code available on GitHub:
&lt;/h3&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/architagr/The-Weekly-Golang-Journal/tree/main/error-propagation" rel="noopener noreferrer"&gt;architagr/The-Weekly-Golang-Journal - error-propagation&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Stay Connected!
&lt;/h3&gt;

&lt;p&gt;💡 Follow me on LinkedIn: &lt;a href="https://www.linkedin.com/in/architagarwal984/" rel="noopener noreferrer"&gt;Archit Agarwal&lt;/a&gt; &lt;br&gt;
🎥 Subscribe to my YouTube: &lt;a href="https://www.youtube.com/c/TheExceptionHandler" rel="noopener noreferrer"&gt;The Exception Handler&lt;/a&gt;&lt;br&gt;
 📬 Sign up for my newsletter: &lt;a href="https://www.linkedin.com/newsletters/the-weekly-golang-journal-7261403856079597568/" rel="noopener noreferrer"&gt;The Weekly Golang Journal&lt;/a&gt;&lt;br&gt;
✍️ Follow me on Medium: &lt;a href="https://medium.com/@architagr" rel="noopener noreferrer"&gt;@architagr&lt;/a&gt;&lt;br&gt;
👨‍💻 Join my subreddit: &lt;a href="https://www.reddit.com/r/GolangJournal" rel="noopener noreferrer"&gt;r/GolangJournal&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Stop Spawning Infinite Goroutines: Use Tee-Channels in Go for Scalable Event Handling</title>
      <dc:creator>Archit Agarwal</dc:creator>
      <pubDate>Wed, 23 Apr 2025 10:44:00 +0000</pubDate>
      <link>https://dev.to/architagr/stop-spawning-infinite-goroutines-use-tee-channels-in-go-for-scalable-event-handling-5g2m</link>
      <guid>https://dev.to/architagr/stop-spawning-infinite-goroutines-use-tee-channels-in-go-for-scalable-event-handling-5g2m</guid>
      <description>&lt;p&gt;Imagine you're working on an app that allows users to sign up for free. But you also want to make sure that someone from the sales team follows up to nudge the user to subscribe. So you have two tasks after the user is successfully signed up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send a welcome email to the user.&lt;/li&gt;
&lt;li&gt;Notify the sales team on Slack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Naturally, your first version looks something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User signs up&lt;/li&gt;
&lt;li&gt;You send a welcome email&lt;/li&gt;
&lt;li&gt;Once that's successful, you notify the sales team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sounds clean.&lt;br&gt;
But here's the thing - you've now &lt;strong&gt;tied the success of the sales notification to the welcome email&lt;/strong&gt;, which is not just a coupling nightmare, it's also a recipe for "Oops, we never told the sales team."&lt;/p&gt;
&lt;h3&gt;
  
  
  ⚠️ The Goroutine Trap: Scaling Gone Wild
&lt;/h3&gt;

&lt;p&gt;So you think, "Let's parallelise it! Fire two goroutines!"&lt;br&gt;
Great. Until…&lt;br&gt;
You host a workshop, and suddenly, &lt;strong&gt;a hundred thousand users&lt;/strong&gt; are signing up.&lt;br&gt;
Now your server is firing &lt;em&gt;two goroutines per user&lt;/em&gt; and context switching like it's in a dance battle. CPU usage spikes, latency increases, and your devops team starts slacking you 😅.&lt;/p&gt;
&lt;h3&gt;
  
  
  💡 Observing the Pattern: The Tasks are Independent!
&lt;/h3&gt;

&lt;p&gt;Take a closer look. The welcome email and the Slack notification are independent. They don't need to wait on each other. They don't even need to happen in real-time.&lt;br&gt;
Which begs the question - why not &lt;strong&gt;broadcast&lt;/strong&gt; the event and let each downstream process consume it at its own pace?&lt;br&gt;
Enter: &lt;strong&gt;Tee-Channels in Go.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  🧵 What is a Tee-Channel in Go?
&lt;/h3&gt;

&lt;p&gt;A tee-channel is like a broadcast system for Go channels. One input channel, multiple output channels. Each downstream consumer listens to their dedicated output and does their thing.&lt;br&gt;
You don't spawn two goroutines per user anymore. You &lt;strong&gt;pipe the user once&lt;/strong&gt;, and split it out - cleanly.&lt;/p&gt;
&lt;h3&gt;
  
  
  👨‍💻 Let's Build It: A Tee-Channel Implementation in Go
&lt;/h3&gt;

&lt;p&gt;Here's a simple, scalable version that processes signups, and then concurrently (but safely) fans out to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send welcome emails&lt;/li&gt;
&lt;li&gt;Notify the marketing team&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Step 1: Define a simple DTO
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;id&lt;/span&gt;    &lt;span class="kt"&gt;int&lt;/span&gt;
 &lt;span class="n"&gt;name&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;
 &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Step 2: Create a helper to respect done context
&lt;/h4&gt;

&lt;p&gt;We'll use &lt;code&gt;orDone&lt;/code&gt; so that everything shuts down gracefully if the context is cancelled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;orDone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;valStream&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
   &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;valStream&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}()&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;valStream&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Implement the Tee-Channel logic
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;tee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
 &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;out1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;out2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;orDone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;o1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;out1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out2&lt;/span&gt;
   &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;o1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
     &lt;span class="n"&gt;o1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;o2&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
     &lt;span class="n"&gt;o2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}()&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what's happening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We listen to the input stream (&lt;code&gt;in&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Each input value is sent to both output channels exactly once.&lt;/li&gt;
&lt;li&gt;It's concurrency-safe and honours context cancellation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 4: Handlers for Email and Slack
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;orDone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"📧 Sending welcome email to"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"✅ Email handler done"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slackChannel&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;orDone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"📣 Notifying"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slackChannel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"team for user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"✅ Slack handler done"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 5: Putting It All Together
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;singnup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c"&gt;// Simulate saving to DB&lt;/span&gt;
 &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

 &lt;span class="n"&gt;notificationChannel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="c"&gt;// Split the input to two output handlers&lt;/span&gt;
 &lt;span class="n"&gt;ch1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;notificationChannel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="c"&gt;// Start handlers&lt;/span&gt;
 &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ch1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"marketing"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ch2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="c"&gt;// Simulate signups&lt;/span&gt;
 &lt;span class="n"&gt;singnup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Archit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"a@example.com"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;notificationChannel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;singnup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userDTO&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Lilly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"lilly@example.com"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;notificationChannel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Let handlers finish&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find this complete code in my GitHub : &lt;a href="https://github.com/architagr/The-Weekly-Golang-Journal/tree/main/tee-channel" rel="noopener noreferrer"&gt;https://github.com/architagr/The-Weekly-Golang-Journal/tree/main/tee-channel&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 A Few Pro Tips
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;orDone&lt;/code&gt; pattern ensures your goroutines stop listening when the context is cancelled.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tee()&lt;/code&gt; is the magic sauce - it splits the input stream cleanly.&lt;/li&gt;
&lt;li&gt;This is &lt;strong&gt;future-proof&lt;/strong&gt;. You can later plug more consumers like analytics, onboarding workflows, etc., without modifying the producer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🎯 When Should You Use Tee-Channels?
&lt;/h3&gt;

&lt;p&gt;✅ When multiple consumers need the same data&lt;br&gt;
✅ When you want to decouple event producers from consumers&lt;br&gt;
✅ When you want to avoid unbounded goroutine creation&lt;br&gt;
Not a good fit if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need real-time delivery with strict ordering guarantees&lt;/li&gt;
&lt;li&gt;You expect high fan-out (many consumers) - in that case, use a pub-sub system&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🧵 Final Thoughts: Small Pattern, Big Impact
&lt;/h3&gt;

&lt;p&gt;Honestly, this pattern looks simple. But in high-scale systems, it becomes a lifesaver. It gives you concurrency without chaos and scalability without the CPU meltdown.&lt;br&gt;
So next time someone signs up on your app - just tee it up! 😄&lt;/p&gt;

&lt;h3&gt;
  
  
  Stay Connected!
&lt;/h3&gt;

&lt;p&gt;💡 Follow me on LinkedIn: &lt;a href="https://www.linkedin.com/in/architagarwal984/" rel="noopener noreferrer"&gt;Archit Agarwal&lt;/a&gt; &lt;br&gt;
🎥 Subscribe to my YouTube: &lt;a href="https://www.youtube.com/c/TheExceptionHandler" rel="noopener noreferrer"&gt;The Exception Handler&lt;/a&gt;&lt;br&gt;
 📬 Sign up for my newsletter: &lt;a href="https://www.linkedin.com/newsletters/the-weekly-golang-journal-7261403856079597568/" rel="noopener noreferrer"&gt;The Weekly Golang Journal&lt;/a&gt;&lt;br&gt;
✍️ Follow me on Medium: &lt;a href="https://medium.com/@architagr" rel="noopener noreferrer"&gt;@architagr&lt;/a&gt;&lt;br&gt;
👨‍💻 Join my subreddit: &lt;a href="https://www.reddit.com/r/GolangJournal" rel="noopener noreferrer"&gt;r/GolangJournal&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>concurrency</category>
      <category>scalability</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>Master the Chain of Responsibility Pattern in Go with This Real-World Example</title>
      <dc:creator>Archit Agarwal</dc:creator>
      <pubDate>Wed, 09 Apr 2025 10:45:00 +0000</pubDate>
      <link>https://dev.to/architagr/master-the-chain-of-responsibility-pattern-in-go-with-this-real-world-example-1bd8</link>
      <guid>https://dev.to/architagr/master-the-chain-of-responsibility-pattern-in-go-with-this-real-world-example-1bd8</guid>
      <description>&lt;h2&gt;
  
  
  🧶 Intro: From Spaghetti to Structured
&lt;/h2&gt;

&lt;p&gt;Ever written a function that starts by fetching user data, dumps it into a file, zips the file, then emails it out - all in one go?&lt;br&gt;
It feels like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;exportEverything&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;fetchUsers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;parseUsers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;zipFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;emailZip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And sure, it works. Until the day something changes.&lt;/p&gt;

&lt;p&gt;Suddenly, you need to skip zipping for certain users, or plug in an S3 upload. You end up sprinkling conditionals everywhere. Before you know it, you've summoned a micro-monolith. And worse? It's not even testable.&lt;/p&gt;

&lt;p&gt;So… what do we do?&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 Enter: Chain of Responsibility Pattern
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Chain of Responsibility&lt;/strong&gt; is a behavioral design pattern that lets you pass requests down a chain of handlers. Each handler does its job and optionally passes the request forward.&lt;br&gt;
Think of it like a &lt;em&gt;tech support escalation&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Level 1 takes your call&lt;/li&gt;
&lt;li&gt;If they can't help, they pass it to Level 2&lt;/li&gt;
&lt;li&gt;If that fails, well… the manager steps in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Go, we can use this pattern to cleanly break up our export logic into small, reusable, testable chunks.&lt;/p&gt;
&lt;h2&gt;
  
  
  🛠️ The Real-World Example: Exporting User Data
&lt;/h2&gt;

&lt;p&gt;Let's say we have a process that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetches users from the DB&lt;/li&gt;
&lt;li&gt;Parses them into a file&lt;/li&gt;
&lt;li&gt;Zips that file&lt;/li&gt;
&lt;li&gt;Emails the zip to the requester&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using the &lt;strong&gt;Chain of Responsibility&lt;/strong&gt;, each step becomes a handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;exportStepHandler&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;exportRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
 &lt;span class="n"&gt;setNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;exportStepHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each handler implements this interface and decides what to do with the request. Here's a quick peek:&lt;/p&gt;

&lt;h4&gt;
  
  
  🏃 Fetch Users
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;exportFetchUsers&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;exportStepHandler&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;exportFetchUsers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;exportRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RequestId(%d): Users fetched from DB&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  🧻 Parse Into File
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;exportParseIntoFile&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;exportStepHandler&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;exportParseIntoFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;exportRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RequestId(%d): Users parsed into file&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  📦 Zip It Up
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;exportZipFiles&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;exportStepHandler&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;exportZipFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;exportRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RequestId(%d): file zipped&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  ✉️ Email the Zip
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;exportEmailZip&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;exportStepHandler&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;exportEmailZip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;exportRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RequestId(%d): Zip emailed&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  🧩 Connecting the Chain
&lt;/h4&gt;

&lt;p&gt;We then wire everything like a good old-fashioned factory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;InitExportData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;exportStepHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;emailStep&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;exportEmailZip&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

 &lt;span class="n"&gt;zipStep&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;exportZipFiles&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
 &lt;span class="n"&gt;zipStep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emailStep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="n"&gt;parseStep&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;exportParseIntoFile&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
 &lt;span class="n"&gt;parseStep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zipStep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="n"&gt;fetchStep&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;exportFetchUsers&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
 &lt;span class="n"&gt;fetchStep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parseStep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fetchStep&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  And then… 🧙 magic:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;exportChain&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;InitExportData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="n"&gt;exportChain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;exportRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;requestid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧠 Why This Pattern Rocks
&lt;/h2&gt;

&lt;p&gt;✅ Readable: Each handler does one thing - clean and simple.&lt;/p&gt;

&lt;p&gt;✅ Maintainable: Change one step without touching others.&lt;/p&gt;

&lt;p&gt;✅ Flexible: Easily rewire or skip steps depending on context.&lt;/p&gt;

&lt;p&gt;✅ Testable: Unit test each handler in isolation.&lt;/p&gt;

&lt;p&gt;✅ Extensible: Want to log something or upload to S3? Just add a new handler!&lt;/p&gt;

&lt;h2&gt;
  
  
  🧵 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;If you've ever struggled with long, procedural workflows in Go, this pattern is a game-changer.&lt;br&gt;
The &lt;strong&gt;Chain of Responsibility&lt;/strong&gt; gives you a structure that scales. It turns a rigid flow into a flexible, testable, and modular pipeline.&lt;br&gt;
So next time you think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hmm, maybe I'll just add another if…"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Take a deep breath. Then chain it up. 🪢&lt;/p&gt;

&lt;h2&gt;
  
  
  Stay Connected!
&lt;/h2&gt;

&lt;p&gt;💡 Follow me on LinkedIn: &lt;a href="https://www.linkedin.com/in/architagarwal984/" rel="noopener noreferrer"&gt;Archit Agarwal&lt;/a&gt; &lt;br&gt;
🎥 Subscribe to my YouTube: &lt;a href="https://www.youtube.com/c/TheExceptionHandler" rel="noopener noreferrer"&gt;The Exception Handler&lt;/a&gt;&lt;br&gt;
 📬 Sign up for my newsletter: &lt;a href="https://www.linkedin.com/newsletters/the-weekly-golang-journal-7261403856079597568/" rel="noopener noreferrer"&gt;The Weekly Golang Journal&lt;/a&gt;&lt;br&gt;
✍️ Follow me on Medium: &lt;a href="https://medium.com/@architagr" rel="noopener noreferrer"&gt;@architagr&lt;/a&gt;&lt;br&gt;
👨‍💻 Join my subreddit: &lt;a href="https://www.reddit.com/r/GolangJournal" rel="noopener noreferrer"&gt;r/GolangJournal&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>designpatterns</category>
      <category>backenddevelopment</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>The Strategy Pattern in Go: A Simple Guide to Writing Extensible Code</title>
      <dc:creator>Archit Agarwal</dc:creator>
      <pubDate>Wed, 02 Apr 2025 10:45:00 +0000</pubDate>
      <link>https://dev.to/architagr/the-strategy-pattern-in-go-a-simple-guide-to-writing-extensible-code-3e6o</link>
      <guid>https://dev.to/architagr/the-strategy-pattern-in-go-a-simple-guide-to-writing-extensible-code-3e6o</guid>
      <description>&lt;p&gt;Imagine you're running a coffee shop ☕, and customers ask for their order receipts in different formats - some want their receipts &lt;strong&gt;printed&lt;/strong&gt;, some in &lt;strong&gt;PDF sent over Email&lt;/strong&gt;, and one guy asks for a &lt;strong&gt;receipt over SMS&lt;/strong&gt;. Now, imagine that you have written this billing software to support a printed receipt.🫣&lt;br&gt;
Every time a new type of receipt is requested, you rewrite your entire billing system. Sounds painful, right? Well, that's precisely what happened in our Go code when &lt;code&gt;UserService&lt;/code&gt; was initially responsible for exporting users as JSON and CSV, and now we onboard a client who wants this in XML. Let's look at the initial &lt;code&gt;UserService&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Initial UserService: Without using Strategy Design Pattern
&lt;/h2&gt;

&lt;p&gt;Imagine you have a &lt;code&gt;UserService&lt;/code&gt; that fetches user data and allows exporting it in different formats. Right now, you support &lt;strong&gt;JSON and CSV&lt;/strong&gt;, but what if tomorrow you need &lt;strong&gt;XML, PDF, or Parquet&lt;/strong&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="n"&gt;Err_NotSupportedExportType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Not Supported export Type"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;IUserPersistence&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;ListUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paginationObj&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pagination&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserModel&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;UserService&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;userPersistence&lt;/span&gt; &lt;span class="n"&gt;IUserPersistence&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;InitUserService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPersistence&lt;/span&gt; &lt;span class="n"&gt;IUserPersistence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;userPersistence&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userPersistence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userSvc&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paginationObj&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pagination&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userSvc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userPersistence&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paginationObj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userSvc&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ExportUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paginationObj&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pagination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="n"&gt;exportConfig&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExportConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

 &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;userSvc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paginationObj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;exportConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;enums&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExportType_Json&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
 &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;enums&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExportType_CSV&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;sb&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id,name,email&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%d,%s,%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
 &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Err_NotSupportedExportType&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Problem with This Approach
&lt;/h2&gt;

&lt;p&gt;With the current implementation, every time you add a new export format, you &lt;strong&gt;modify&lt;/strong&gt; the &lt;code&gt;ExportUsers&lt;/code&gt; method. This violates two key &lt;strong&gt;SOLID principles&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single Responsibility Principle (SRP)&lt;/strong&gt; - &lt;code&gt;UserService&lt;/code&gt; should focus only on fetching users, not handling export logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open/Closed Principle (OCP)&lt;/strong&gt; - The class should be &lt;strong&gt;open for extension but closed for modification&lt;/strong&gt;. Right now, adding a new export type means &lt;strong&gt;editing existing code&lt;/strong&gt;, increasing the risk of breaking something.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Issues with this approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💔 &lt;strong&gt;&lt;code&gt;UserService&lt;/code&gt; becomes a bottleneck.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;💀 &lt;strong&gt;Every new format requires modifying existing code.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;💥 &lt;strong&gt;One small change could break everything.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;😭 Teams adding &lt;strong&gt;new export formats&lt;/strong&gt; need to &lt;strong&gt;touch your code&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;😫 Testing becomes harder as the service &lt;strong&gt;grows&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Can We Fix This?
&lt;/h2&gt;

&lt;p&gt;Instead of making &lt;code&gt;UserService&lt;/code&gt; &lt;strong&gt;aware of every export format&lt;/strong&gt;, we need a &lt;strong&gt;pluggable solution&lt;/strong&gt;. This is where the &lt;strong&gt;Strategy Pattern&lt;/strong&gt; shines! ✨&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 Introducing the Strategy Pattern
&lt;/h2&gt;

&lt;p&gt;Think of it like choosing a shipping method on Amazon: You pick &lt;strong&gt;Standard Shipping, Express, or Same-Day&lt;/strong&gt; - but Amazon doesn't care how it's delivered. The delivery company handles that.&lt;br&gt;
Similarly, our UserService should just &lt;strong&gt;fetch users&lt;/strong&gt; and let the &lt;strong&gt;export strategy&lt;/strong&gt; decide how to format them.&lt;/p&gt;

&lt;p&gt;Are you ready to start the code repair 👩🏻‍🔧?&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Define a Common Export Interface
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Strategy interface&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;IUserExport&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Implement Concrete Strategies
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// CSV Export: Concrete Strategy&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;CsvExportStrategy&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CsvExportStrategy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;sb&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
 &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHeader&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRow&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// JSON Export: Concrete Strategy&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;JSONExportStrategy&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;JSONExportStrategy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Create a Strategy Builder
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="n"&gt;Err_NotSupportedExportType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Not Supported export Type"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;UserExportBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExportConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUserExport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;enums&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExportType_CSV&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;CsvExportStrategy&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;enums&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExportType_Json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;JSONExportStrategy&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%w, %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Err_NotSupportedExportType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Update UserService 🥁
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userSvc&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ExportUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paginationObj&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pagination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="n"&gt;exportConfig&lt;/span&gt; &lt;span class="n"&gt;exportstrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IUserExport&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

 &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;userSvc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paginationObj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;exportConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, &lt;code&gt;ExportUsers&lt;/code&gt; does not care &lt;strong&gt;how&lt;/strong&gt; data is exported. It only retrieves users and sends the data to the correct export strategy.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 5: Adding a New Format (XML)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// XML Export: Concrete Strategy&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;XMLExportStrategy&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;XMLExportStrategy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;xmlData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xmlData&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 6: Update the Strategy Builder
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;UserExportBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExportConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUserExport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;enums&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExportType_CSV&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;CsvExportStrategy&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
 &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;enums&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExportType_Json&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;JSONExportStrategy&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
 &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;enums&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExportType_Xml&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;XMLExportStrategy&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
 &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%w, %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Err_NotSupportedExportType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find this code on my Github: &lt;a href="https://github.com/architagr/design_patterns/tree/main/golang/behavioral/strategy" rel="noopener noreferrer"&gt;https://github.com/architagr/design_patterns/tree/main/golang/behavioral/strategy&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Final Thoughts &amp;amp; Takeaways
&lt;/h2&gt;

&lt;p&gt;Before: Every new format meant editing &lt;code&gt;UserService&lt;/code&gt;, risking bugs.&lt;br&gt;
After: Adding new formats is just &lt;strong&gt;creating a new strategy&lt;/strong&gt;, with no changes to the core logic.&lt;br&gt;
&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
If your &lt;code&gt;if-else&lt;/code&gt; statements are growing &lt;strong&gt;faster than your stress levels&lt;/strong&gt;, it's time for the &lt;strong&gt;Strategy Pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stay Connected!
&lt;/h3&gt;

&lt;p&gt;💡 Follow me on LinkedIn: &lt;a href="https://www.linkedin.com/in/architagarwal984/" rel="noopener noreferrer"&gt;Archit Agarwal&lt;/a&gt; &lt;br&gt;
🎥 Subscribe to my YouTube: &lt;a href="https://www.youtube.com/c/TheExceptionHandler" rel="noopener noreferrer"&gt;The Exception Handler&lt;/a&gt;&lt;br&gt;
 📬 Sign up for my newsletter: &lt;a href="https://www.linkedin.com/newsletters/the-weekly-golang-journal-7261403856079597568/" rel="noopener noreferrer"&gt;The Weekly Golang Journal&lt;/a&gt;&lt;br&gt;
✍️ Follow me on Medium: &lt;a href="https://medium.com/@architagr" rel="noopener noreferrer"&gt;@architagr&lt;/a&gt;&lt;br&gt;
👨‍💻 Join my subreddit: &lt;a href="https://www.reddit.com/r/GolangJournal" rel="noopener noreferrer"&gt;r/GolangJournal&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>designpatterns</category>
      <category>cleancode</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Never Miss an Update: Master the Observer Pattern in Go with Real-World Examples</title>
      <dc:creator>Archit Agarwal</dc:creator>
      <pubDate>Wed, 26 Mar 2025 10:45:00 +0000</pubDate>
      <link>https://dev.to/architagr/never-miss-an-update-master-the-observer-pattern-in-go-with-real-world-examples-2dlh</link>
      <guid>https://dev.to/architagr/never-miss-an-update-master-the-observer-pattern-in-go-with-real-world-examples-2dlh</guid>
      <description>&lt;h2&gt;
  
  
  Why Should You Care About the Observer Pattern?
&lt;/h2&gt;

&lt;p&gt;Have you ever felt the &lt;strong&gt;FOMO&lt;/strong&gt; when you miss out on an important update? Imagine a world where you must &lt;strong&gt;constantly refresh your email&lt;/strong&gt; to check for new messages. Sounds painful, right? 😩&lt;/p&gt;

&lt;p&gt;Well, that’s exactly how inefficient polling works in software. Instead of constantly checking, wouldn’t it be better if you just &lt;strong&gt;got notified automatically&lt;/strong&gt; when something changed?&lt;/p&gt;

&lt;p&gt;That’s where the &lt;strong&gt;Observer Pattern&lt;/strong&gt; comes in! This pattern lets different parts of your system subscribe to updates and automatically receive notifications when an event occurs. It’s the &lt;strong&gt;backbone of event-driven programming&lt;/strong&gt; and is used in real-world systems like:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Live sports score updates&lt;/strong&gt;&lt;br&gt;
✅ &lt;strong&gt;Stock market price alerts&lt;/strong&gt;&lt;br&gt;
✅ &lt;strong&gt;Job portal notifications&lt;/strong&gt; &lt;br&gt;
✅ &lt;strong&gt;E-commerce product price drop alerts&lt;/strong&gt; &lt;br&gt;
✅ &lt;strong&gt;Hashtag-based content subscription (which we’ll build today!)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this article, we’ll &lt;strong&gt;demystify&lt;/strong&gt; the Observer Pattern and implement a &lt;strong&gt;Hashtag Subscription System&lt;/strong&gt; in Golang, where users can subscribe to specific hashtags and receive notifications when new articles are published.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Observer Pattern?
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Observer Pattern&lt;/strong&gt; is a &lt;strong&gt;behavioural design pattern&lt;/strong&gt; where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Subject (Publisher)&lt;/strong&gt; maintains a list of &lt;strong&gt;Observers (Subscribers)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Whenever the subject’s state changes, it &lt;strong&gt;notifies all registered observers&lt;/strong&gt; automatically.&lt;/li&gt;
&lt;li&gt;Observers can &lt;strong&gt;subscribe or unsubscribe&lt;/strong&gt; dynamically without affecting the subject’s core logic.
This pattern &lt;strong&gt;decouples&lt;/strong&gt; objects, making systems more modular and scalable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementing the Observer Pattern in Go
&lt;/h2&gt;

&lt;p&gt;Let’s build a &lt;strong&gt;Hashtag Subscription System&lt;/strong&gt;, where:&lt;/p&gt;

&lt;p&gt;📌 Users &lt;strong&gt;subscribe&lt;/strong&gt; to hashtags they’re interested in. &lt;br&gt;
📌 When an article is published under a hashtag, &lt;strong&gt;subscribers get notified.&lt;/strong&gt;&lt;br&gt;
📌 Users can &lt;strong&gt;unsubscribe&lt;/strong&gt; from hashtags at any time.&lt;br&gt;
📌 Users can &lt;strong&gt;block specific authors&lt;/strong&gt; if they don’t want to see their content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Define the Observer Interface (Users)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;

&lt;span class="c"&gt;// Observer Interface (Subscribers)&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;IHashtagObserver&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articleID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each user who wants to receive hashtag updates must implement the &lt;code&gt;Notify&lt;/code&gt; method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Define the Subject (Hashtag Manager)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Hashtag acts as a subject (Publisher)&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Hashtag&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;observers&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;IHashtagObserver&lt;/span&gt;
 &lt;span class="n"&gt;name&lt;/span&gt;      &lt;span class="kt"&gt;string&lt;/span&gt;
 &lt;span class="n"&gt;articles&lt;/span&gt;  &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Register an observer (User)&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Hashtag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="n"&gt;IHashtagObserver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Deregister an observer (User)&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Hashtag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Deregister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Publish a new article&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Hashtag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;NewArticlePublished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articleID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;📢 Notifying subscribers of #%s about new article (ID: %s) by %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;articleID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articleID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Notify all observers&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Hashtag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articleID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articleID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;Hashtag Manager&lt;/strong&gt; maintains a list of subscribers and notifies them when a new article is published.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Implement the Concrete Observer (User)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// User struct acts as an Observer&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;id&lt;/span&gt;            &lt;span class="kt"&gt;string&lt;/span&gt;
 &lt;span class="n"&gt;name&lt;/span&gt;          &lt;span class="kt"&gt;string&lt;/span&gt;
 &lt;span class="n"&gt;email&lt;/span&gt;         &lt;span class="kt"&gt;string&lt;/span&gt;
 &lt;span class="n"&gt;slackUserID&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt;
 &lt;span class="n"&gt;hashtags&lt;/span&gt;      &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;
 &lt;span class="n"&gt;blockedAuthor&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Add an author to the blocked list&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;AddBlockedAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blockedAuthor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Remove an author from the blocked list&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RemoveBlockedAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blockedAuthor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Subscribe to a hashtag&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;AddHashtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagName&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hashtags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
 &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHashtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Unsubscribe from a hashtag&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RemoveHashtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagName&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hashtags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHashtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deregister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Receive notifications for new articles&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articleID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blockedAuthor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"🚫 %s skipped notification for article (ID: %s) - blocked author: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;articleID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;📨 Sending notification to %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"📧 Email sent to %s for new article (ID: %s) by %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;articleID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slackUserID&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"💬 Slack notification sent to %s for new article (ID: %s) by %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slackUserID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;articleID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each user can &lt;strong&gt;subscribe/unsubscribe&lt;/strong&gt; from hashtags dynamically!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Bringing It All Together
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;tagCleanCode&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Clean Code"&lt;/span&gt;
  &lt;span class="n"&gt;tagSoftwareDevelopment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Software Development"&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddNewHashtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagSoftwareDevelopment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddNewHashtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagCleanCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="c"&gt;// Create users and subscribe them to hashtags&lt;/span&gt;
 &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"alice_slack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tagSoftwareDevelopment&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bob@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bob_slack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tagSoftwareDevelopment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tagCleanCode&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;user3&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Charlie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"charlie@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"charlie_slack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tagSoftwareDevelopment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tagCleanCode&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"author1"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

 &lt;span class="c"&gt;// Publish new articles under hashtags&lt;/span&gt;
 &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHashtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagSoftwareDevelopment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewArticlePublished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"article1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"author2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHashtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagSoftwareDevelopment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewArticlePublished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"article2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"author1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Charlie blocked author1&lt;/span&gt;
 &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHashtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagCleanCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewArticlePublished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"article3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"author1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="c"&gt;// Charlie unsubscribes from "Clean Code" hashtag&lt;/span&gt;
 &lt;span class="n"&gt;user3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveHashtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagCleanCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHashtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagCleanCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewArticlePublished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"article4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"author2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Charlie won't be notified&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Expected Output
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📢 Notifying subscribers of #Software Development about new article (ID: article1) by author2

📨 Sending notification to Alice
📧 Email sent to alice@example.com for new article (ID: article1) by author2
💬 Slack notification sent to alice_slack for new article (ID: article1) by author2

📨 Sending notification to Bob
📧 Email sent to bob@example.com for new article (ID: article1) by author2
💬 Slack notification sent to bob_slack for new article (ID: article1) by author2

📨 Sending notification to Charlie
📧 Email sent to charlie@example.com for new article (ID: article1) by author2
💬 Slack notification sent to charlie_slack for new article (ID: article1) by author2

📢 Notifying subscribers of #Software Development about new article (ID: article2) by author1

📨 Sending notification to Alice
📧 Email sent to alice@example.com for new article (ID: article2) by author1
💬 Slack notification sent to alice_slack for new article (ID: article2) by author1

📨 Sending notification to Bob
📧 Email sent to bob@example.com for new article (ID: article2) by author1
💬 Slack notification sent to bob_slack for new article (ID: article2) by author1
🚫 Charlie skipped notification for article (ID: article2) - blocked author: author1

📢 Notifying subscribers of #Clean Code about new article (ID: article3) by author1

📨 Sending notification to Bob
📧 Email sent to bob@example.com for new article (ID: article3) by author1
💬 Slack notification sent to bob_slack for new article (ID: article3) by author1
🚫 Charlie skipped notification for article (ID: article3) - blocked author: author1

📢 Notifying subscribers of #Clean Code about new article (ID: article4) by author2

📨 Sending notification to Bob
📧 Email sent to bob@example.com for new article (ID: article4) by author2
💬 Slack notification sent to bob_slack for new article (ID: article4) by author2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom! 🎉 Alice and Bob got their notifications instantly and Charlie also was able to block a particular author! No polling, no wasted CPU cycles. Just clean, efficient event-driven programming.&lt;/p&gt;

&lt;p&gt;You can find this complete working code on my &lt;a href="https://github.com/architagr/design_patterns/tree/main/golang/behavioral/observer" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;The Observer Pattern&lt;/strong&gt; helps create event-driven, decoupled systems.&lt;br&gt;
✅ It's used in &lt;strong&gt;stock updates, job alerts, social media notifications, and more&lt;/strong&gt;.&lt;br&gt;
✅ Our &lt;strong&gt;Hashtag Subscription System&lt;/strong&gt; shows how users can subscribe, unsubscribe, and receive notifications in real-time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This is just the tip of the iceberg. You can extend this pattern for &lt;strong&gt;WebSockets, message brokers&lt;/strong&gt;, or even &lt;strong&gt;real-time dashboards&lt;/strong&gt;! 🚀&lt;/p&gt;

&lt;h4&gt;
  
  
  Stay Connected!
&lt;/h4&gt;

&lt;p&gt;💡 Follow me here on &lt;a href="https://www.linkedin.com/in/architagarwal984/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; for more insights on software development and architecture.&lt;br&gt;
🎥 Subscribe to &lt;a href="https://www.youtube.com/c/TheExceptionHandler" rel="noopener noreferrer"&gt;my YouTube channel&lt;/a&gt; for in-depth tutorials.&lt;br&gt;
📬 Sign up for my newsletter, &lt;a href="https://www.linkedin.com/newsletters/the-weekly-golang-journal-7261403856079597568/" rel="noopener noreferrer"&gt;The Weekly Golang Journal&lt;/a&gt;, for exclusive content.&lt;br&gt;
✍️ Follow &lt;a href="https://medium.com/@architagr" rel="noopener noreferrer"&gt;me on Medium&lt;/a&gt; for detailed articles.&lt;br&gt;
👨‍💻 Join the discussion on my subreddit, r/GolangJournal, and be part of the community!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Stop Writing Rigid Code! Master the Decorator Pattern in Go for Ultimate Flexibility</title>
      <dc:creator>Archit Agarwal</dc:creator>
      <pubDate>Wed, 12 Mar 2025 10:45:00 +0000</pubDate>
      <link>https://dev.to/architagr/stop-writing-rigid-code-master-the-decorator-pattern-in-go-for-ultimate-flexibility-3mp6</link>
      <guid>https://dev.to/architagr/stop-writing-rigid-code-master-the-decorator-pattern-in-go-for-ultimate-flexibility-3mp6</guid>
      <description>&lt;p&gt;Imagine you're a software developer building an ice cream vending machine for Disneyland. The menu was initially fixed - customers could only order predefined flavors with no customizations (boring, right? 😒). You wrote simple code to calculate costs based on these fixed options.&lt;/p&gt;

&lt;p&gt;But this is Disneyland! Nobody wants a plain old scoop. People want &lt;strong&gt;triple scoops, extra chocolate sauce, stacked wafer cones, sprinkles, and more.&lt;/strong&gt; You realize you can't hardcode every possible combination, or your codebase will become a nightmare! 🫠&lt;/p&gt;

&lt;p&gt;So, how do you handle dynamic customizations &lt;strong&gt;without rewriting everything&lt;/strong&gt;? Enter the &lt;strong&gt;Decorator Pattern&lt;/strong&gt;, a lifesaver for scenarios where you need to &lt;strong&gt;dynamically extend an object's functionality without modifying its structure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's first explore the &lt;strong&gt;problem with a naive approach&lt;/strong&gt; and then &lt;strong&gt;refactor it using the Decorator Pattern&lt;/strong&gt; for a flexible and scalable solution. 🚀&lt;/p&gt;

&lt;h3&gt;
  
  
  The Traditional Approach: Hardcoding Ice Cream Variants
&lt;/h3&gt;

&lt;p&gt;Imagine our first attempt at coding the vending machine. A simple function creates a &lt;strong&gt;Butterscotch Ice Cream:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;CreateButterscotchIcecream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scoops&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;IceCream&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ingredients&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;IceCreamIngredient&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;BASE_PLAIN_CONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;scoops&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ingredients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ingredients&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FLAVOUR_BUTTERSCOTCH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;ingredients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ingredients&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TOPPING_SPRINKLES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateIceCreamRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Ingrediants&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ingredients&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we add &lt;strong&gt;Vanilla Ice Cream:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;CreateVanillaIcecream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scoops&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;IceCream&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ingredients&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;IceCreamIngredient&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;BASE_PLAIN_CONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;scoops&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ingredients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ingredients&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FLAVOUR_VANILLA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateIceCreamRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Ingrediants&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ingredients&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works fine for fixed flavours, but &lt;strong&gt;what if customers want to mix and match&lt;/strong&gt;? What if someone wants &lt;strong&gt;vanilla with chocolate chips&lt;/strong&gt; or a &lt;strong&gt;chocolate cone with butterscotch scoops&lt;/strong&gt;? We can't keep adding new functions for every combination. 😵‍💫&lt;/p&gt;

&lt;h4&gt;
  
  
  The Problem? 🚨
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Hardcoding every variation is &lt;strong&gt;not scalable&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;code becomes repetitive&lt;/strong&gt; and difficult to maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is no flexibility&lt;/strong&gt; to create custom orders dynamically.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Time to &lt;strong&gt;refactor using the Decorator Pattern!&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing the Decorator Pattern 🎩✨
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Decorator Pattern&lt;/strong&gt; allows us to dynamically extend an object's functionality &lt;strong&gt;without modifying its structure&lt;/strong&gt;. Instead of hardcoding different types of ice cream, we use &lt;strong&gt;composition over inheritance&lt;/strong&gt; to wrap functionalities around each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy476r0tjd86pjd16q5kz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy476r0tjd86pjd16q5kz.gif" alt="" width="640" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Think of your &lt;strong&gt;manager adding their name to your presentation after changing the font size&lt;/strong&gt; - that's the Decorator Pattern at work. 😂&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Define a Common Interface
&lt;/h4&gt;

&lt;p&gt;We start by creating an interface that all ice cream components (base, flavours, and toppings) will implement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;IIceCreamIngredient&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GetPreparationSteps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;GetCost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Implement the Base Ingredient (Chocolate Cone)
&lt;/h4&gt;

&lt;p&gt;Now, let's implement the Chocolate Cone decorator, which wraps around another ingredient:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;BaseChocolateCone&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;ExistingIngrediant&lt;/span&gt; &lt;span class="n"&gt;IIceCreamIngrediant&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ingrediant&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BaseChocolateCone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetPreperationSteps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"Chocolate Cone"&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ingrediant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExistingIngrediant&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ingrediant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExistingIngrediant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPreperationSteps&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ingrediant&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BaseChocolateCone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetCost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;oldCost&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ingrediant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExistingIngrediant&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;oldCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ingrediant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExistingIngrediant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetCost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;oldCost&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;✅ Breakdown:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If this ingredient is added on top of something else, &lt;strong&gt;it appends the steps&lt;/strong&gt; to the existing preparation list.&lt;/li&gt;
&lt;li&gt;If it's the &lt;strong&gt;first ingredient&lt;/strong&gt;, it starts the list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost calculation&lt;/strong&gt; is handled dynamically by summing up all wrapped ingredients.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Step 3: Implement More Decorators
&lt;/h4&gt;

&lt;p&gt;Let's implement a &lt;strong&gt;Butterscotch Flavor&lt;/strong&gt; decorator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;FlavourButterscotch&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;ExistingIngrediant&lt;/span&gt; &lt;span class="n"&gt;IIceCreamIngrediant&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ingrediant&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;FlavourButterscotch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetPreperationSteps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"1 scoop Butterscotch"&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ingrediant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExistingIngrediant&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ingrediant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExistingIngrediant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPreperationSteps&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ingrediant&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;FlavourButterscotch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetCost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;oldCost&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ingrediant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExistingIngrediant&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;oldCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ingrediant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExistingIngrediant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetCost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;oldCost&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, we can now &lt;strong&gt;chain ingredients dynamically!&lt;/strong&gt; 🎉&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 4: Dynamically Creating Ice Cream Orders 🍨
&lt;/h4&gt;

&lt;p&gt;Now, let's take a user's order and create their dream ice cream dynamically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;iceCream&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;BaseChocolateCone&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ExistingIngredient&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;FlavourButterscotch&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ExistingIngredient&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;FlavourVanilla&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Steps to prepare your ice cream:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;iceCream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPreparationSteps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Step %d: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Total Cost: $%d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iceCream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetCost&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Steps to prepare your ice cream:
Step 1: Chocolate Cone
Step 2: 1 scoop Butterscotch
Step 3: 1 scoop Vanilla
Total Cost: $20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;✅ Now, any combination is possible dynamically!&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No hardcoding different combinations&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easily scalable&lt;/strong&gt; for new flavours &amp;amp; toppings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follows the Open-Closed Principle&lt;/strong&gt; (extend behavior without modifying existing code)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complete Code can be found on my GitHub &lt;a href="https://github.com/architagr/design_patterns/tree/main/golang/structural/decorator" rel="noopener noreferrer"&gt;https://github.com/architagr/design_patterns/tree/main/golang/structural/decorator&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Use Cases of the Decorator Pattern
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Logging Middleware&lt;/strong&gt; (Wrap API requests to log data)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication Middleware&lt;/strong&gt; (Add security checks dynamically)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limiting&lt;/strong&gt; (Restrict API calls per user)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching Mechanism&lt;/strong&gt; (Cache responses dynamically)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring &amp;amp; Metrics Collection&lt;/strong&gt; (Track execution time)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption &amp;amp; Compression&lt;/strong&gt; (Wrap data processing functions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature Flags&lt;/strong&gt; (Enable/disable features at runtime)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment Processing&lt;/strong&gt; (Add discounts/taxes dynamically)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Validation&lt;/strong&gt; (Validate user input before processing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Scraping Pipelines&lt;/strong&gt; (Apply transformation layers dynamically)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Wrapping Up 🎁
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Decorator Pattern&lt;/strong&gt; is a powerful technique in Go that allows you to &lt;strong&gt;dynamically compose behaviours&lt;/strong&gt; while keeping your codebase clean and scalable. Next time you face a scenario where functionalities need to be &lt;strong&gt;added dynamically without modifying the base code think Decorators!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Have you used the &lt;strong&gt;Decorator Pattern&lt;/strong&gt; in your projects? Share your experience in the comments! 👇&lt;/p&gt;

&lt;p&gt;Stay Connected!&lt;/p&gt;

&lt;p&gt;💡 &lt;a href="https://www.linkedin.com/in/architagarwal984/" rel="noopener noreferrer"&gt;Follow me here on LinkedIn&lt;/a&gt; for more insights on software development and architecture.&lt;/p&gt;

&lt;p&gt;🎥 Subscribe to my &lt;a href="https://lnkd.in/gauaRed7" rel="noopener noreferrer"&gt;YouTube channel&lt;/a&gt; for in-depth tutorials. &lt;/p&gt;

&lt;p&gt;📬 Sign up for my newsletter, &lt;a href="https://lnkd.in/g8DzK7Ts" rel="noopener noreferrer"&gt;The Weekly Golang Journal&lt;/a&gt;, for exclusive content. &lt;/p&gt;

&lt;p&gt;✍️ &lt;a href="https://lnkd.in/gXSMeXxm" rel="noopener noreferrer"&gt;Follow me on Medium&lt;/a&gt; for detailed articles&lt;/p&gt;

&lt;p&gt;👨‍💻 Join the discussion on my subreddit, r/GolangJournal, and be part of the community!&lt;/p&gt;

</description>
      <category>go</category>
      <category>designpatterns</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
