<?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: Adam - The Developer ✨</title>
    <description>The latest articles on DEV Community by Adam - The Developer ✨ (@adamthedeveloper).</description>
    <link>https://dev.to/adamthedeveloper</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002243%2F1dc09100-5c18-4e4d-b1c3-a8fb023506cc.png</url>
      <title>DEV Community: Adam - The Developer ✨</title>
      <link>https://dev.to/adamthedeveloper</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adamthedeveloper"/>
    <language>en</language>
    <item>
      <title>My favorite article of the week. for someone who's where he is today by breaking things, painfully manually writing CRUD apps and having no choice but to learn how to read people's code, this hits too close to home.</title>
      <dc:creator>Adam - The Developer ✨</dc:creator>
      <pubDate>Wed, 29 Jul 2026 16:12:45 +0000</pubDate>
      <link>https://dev.to/adamthedeveloper/my-favorite-article-of-the-week-for-someone-whos-where-he-is-today-by-breaking-things-painfully-18gg</link>
      <guid>https://dev.to/adamthedeveloper/my-favorite-article-of-the-week-for-someone-whos-where-he-is-today-by-breaking-things-painfully-18gg</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nazar-boyko/the-junior-developer-pipeline-is-broken-and-ai-broke-it-1aai" class="crayons-story__hidden-navigation-link"&gt;The Junior Developer Pipeline Is Broken... And AI Broke It&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/nazar-boyko/the-junior-developer-pipeline-is-broken-and-ai-broke-it-1aai" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Deletes the career ladder&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nazar-boyko" class="crayons-avatar  crayons-avatar--l  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1875383%2F1b3f5dc9-df1c-4551-9f6e-e3b6234b3d6c.gif" alt="nazar-boyko profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nazar-boyko" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nazar Boyko
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nazar Boyko
                
              
              &lt;div id="story-author-preview-content-4240237" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nazar-boyko" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1875383%2F1b3f5dc9-df1c-4551-9f6e-e3b6234b3d6c.gif" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nazar Boyko&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nazar-boyko/the-junior-developer-pipeline-is-broken-and-ai-broke-it-1aai" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 27&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nazar-boyko/the-junior-developer-pipeline-is-broken-and-ai-broke-it-1aai" id="article-link-4240237"&gt;
          The Junior Developer Pipeline Is Broken... And AI Broke It
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/career"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;career&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nazar-boyko/the-junior-developer-pipeline-is-broken-and-ai-broke-it-1aai" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;258&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nazar-boyko/the-junior-developer-pipeline-is-broken-and-ai-broke-it-1aai#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              205&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>beginners</category>
      <category>career</category>
      <category>codenewbie</category>
    </item>
    <item>
      <title>Understanding Over Origin</title>
      <dc:creator>Adam - The Developer ✨</dc:creator>
      <pubDate>Tue, 28 Jul 2026 09:36:21 +0000</pubDate>
      <link>https://dev.to/adamthedeveloper/understanding-over-origin-4685</link>
      <guid>https://dev.to/adamthedeveloper/understanding-over-origin-4685</guid>
      <description>&lt;p&gt;Many of developers communities are asking the wrong question.&lt;/p&gt;

&lt;p&gt;Not because they're wrong to worry about low-effort work, no, but the filter they're using doesn't actually separate maintained engineering from generated slop. It just separates projects based on which tools were involved in their creation.&lt;/p&gt;

&lt;p&gt;Imagine trying to share a work that you're really proud of, with the help of AI assisting it, the idea's yours but your work gets flagged, rejected, or buried under comments like &lt;em&gt;"no you didn't"&lt;/em&gt; and &lt;em&gt;"I hate how there's so many AI projects these days."&lt;/em&gt; Someone definitely felt righteous after saying this, believing they're protecting themselves from low-effort slop. &lt;/p&gt;

&lt;p&gt;Except that's not actually what they're protecting against. They're protecting against &lt;em&gt;a label&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Distinction
&lt;/h2&gt;

&lt;p&gt;I read &lt;a class="mentioned-user" href="https://dev.to/madsendev"&gt;@madsendev&lt;/a&gt; 's article regarding &lt;a href="https://dev.to/madsendev/i-built-something-good-with-ai-now-some-developer-communities-dont-want-to-see-it-20mo"&gt;Open Vectorizer&lt;/a&gt; and Madsen wants to share the project with other people and in hope that someone would like to contribute as well.&lt;/p&gt;

&lt;p&gt;Apparently, difference between "AI-generated" and "maintained engineering" has become invisible to gatekeepers who've simplified the filter down to a single binary question.&lt;/p&gt;

&lt;p&gt;the wrong question.&lt;/p&gt;

&lt;p&gt;" AI or no AI "? ( they didn't literally ask this but that's the filter in practice )&lt;/p&gt;

&lt;p&gt;Here's why: imagine two projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project A:&lt;/strong&gt; Someone spends two years redesigning a vectorization algorithm, investigates whether machine learning could improve it, decides against it, implements a deterministic approach, discovers their own benchmark was inflated (and fixes it anyway), publishes reproducible results, and actively maintains the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project B:&lt;/strong&gt; Someone types "make me a music streaming app" into an AI prompt, publishes whatever emerges without testing it, and disappears.&lt;/p&gt;

&lt;p&gt;Both used AI. Both are labeled "AI-generated." One belongs in a developer community. The other is exactly what communities should be filtering out.&lt;/p&gt;

&lt;p&gt;Guess which one gets rejected? yah, both get rejected. The luckiest you can get is being rejected later because the gatekeeper was busy arguing with you and changing their community name to: " i miss what was it like to program on punch cards and a ciggy in my mouth " ahhh yes, the identity crisis.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Using "AI-Generated" as a Quality Filter
&lt;/h2&gt;

&lt;p&gt;The gatekeeping argument seems reasonable at first glance. But it has real problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The dismissal:&lt;/strong&gt; &lt;em&gt;"no you didn't&lt;/em&gt; (via &lt;a class="mentioned-user" href="https://dev.to/deammer"&gt;@deammer&lt;/a&gt;). This response requires ignoring the actual technical work, but Madsen rewrote an entire algorithm pipeline, tested it against established tools, and caught a bug that made his own benchmarks look better, then published the worse numbers anyway. That's not  someone slapping a prompt onto Claude and calling it a day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The broader concern:&lt;/strong&gt; &lt;a class="mentioned-user" href="https://dev.to/blakebeckcoding"&gt;@blakebeckcoding&lt;/a&gt; points out something real, unvetted AI-generated code has introduced security vulnerabilities. That's a legitimate worry about maintainability and accountability. Where the argument breaks down is treating that as sufficient reason to reject &lt;em&gt;every&lt;/em&gt; AI-assisted project before evaluating its technical merits. The concern about low-quality code is valid. The heuristic of "reject based on tool choice" doesn't actually address it. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think of it differently:&lt;/strong&gt; we don't reject all Rust packages because Rust makes memory safety easier, nor do we assume all C projects are insecure because C makes memory errors possible. Language choice affects probabilities, but it doesn't determine quality. The same applies to AI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The nostalgia play:&lt;/strong&gt; &lt;em&gt;"Dev.to used to be good"&lt;/em&gt; (same dude, you know who, bro's just nostalgic). There's an assumption that developer communities were higher-quality before AI tooling arrived. But humans have been publishing terrible code since the 1980s, spamming them has always been a problem and it isn't new, AI just makes it easier and cheaper to spam.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem (And Why It Requires Actual Work)
&lt;/h2&gt;

&lt;p&gt;Developer communities &lt;em&gt;are&lt;/em&gt; being flooded and it's not the AI-generated projects specifically but &lt;em&gt;low-effort projects&lt;/em&gt;, period. and like I said up there, the flood's rising fast at an overwhelming rate because AI made it cheaper to produce them.&lt;/p&gt;

&lt;p&gt;The solution, though? It's not "ban the AI ones."&lt;/p&gt;

&lt;p&gt;It's understanding what actually separates signal from noise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can the maintainer explain the architecture, or does it sound like they're reading stack overflow?&lt;/li&gt;
&lt;li&gt;Are there meaningful tests?&lt;/li&gt;
&lt;li&gt;Can claims be independently reproduced?&lt;/li&gt;
&lt;li&gt;Are benchmarks transparent?&lt;/li&gt;
&lt;li&gt;Are weaknesses disclosed?&lt;/li&gt;
&lt;li&gt;Does the maintainer actually review changes and take responsibility?&lt;/li&gt;
&lt;li&gt;Will this be maintained in six months, or is it a one-off lab experiment?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions work equally well for human-written code. And they're &lt;em&gt;expensive to verify&lt;/em&gt;, they require actual reading, thinking, and judgment.&lt;/p&gt;

&lt;p&gt;"Was AI used?" is cheap. One checkbox. Feels principled. Lets moderators move on.&lt;/p&gt;

&lt;p&gt;"Is this maintained engineering?" requires actually engaging with the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pointed Out Irony
&lt;/h2&gt;

&lt;p&gt;In the comment thread, &lt;a class="mentioned-user" href="https://dev.to/unitbuilds"&gt;@unitbuilds&lt;/a&gt; delivered the most sensible take on all of this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Software development is an orchestration process where the ultimate value is the working, audited, and tested software—not just human typing time."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And then:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If that's the kind of comments you leave, go to X and join the cesspool, or Reddit, they'll love you there. Please keep Dev.to a safe space for all developers."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;hits good, doesn't it? it's not defending AI in the abstract. It's defending &lt;em&gt;communities staying communities&lt;/em&gt;, places where people can share work and get feedback, not face purity tests based on which tools they used.&lt;/p&gt;

&lt;p&gt;The person who said they used 99% AI to ship a 3000-line game while working full-time deserves to share that. So does someone who used GitHub Copilot for routine completions. So does the Open Vectorizer author who made architectural decisions with AI assistance but wouldn't ship something they couldn't explain.&lt;/p&gt;

&lt;p&gt;What none of them deserve is dismissal based on a label that tells you nothing about the quality of the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Historical Perspective
&lt;/h2&gt;

&lt;p&gt;This is something that might seem beside the point but isn't: software development has &lt;em&gt;always&lt;/em&gt; worked this way.&lt;/p&gt;

&lt;p&gt;We moved from assembly to C and nobody said &lt;em&gt;"You didn't really code because you're using a compiler."&lt;/em&gt; We moved from manual memory management to garbage collection. From raw SQL to ORMs. From writing Dockerfiles by hand to templates. From Stack Overflow copy-paste to IDE refactoring to GitHub Copilot autocomplete.&lt;/p&gt;

&lt;p&gt;Each step increased abstraction. Each step generated concern that developers were getting lazy, that standards were dropping, that the craft was being diluted.&lt;/p&gt;

&lt;p&gt;Each time, the question that actually mattered wasn't &lt;em&gt;"What abstraction level did you use?"&lt;/em&gt; It was &lt;em&gt;"Do you understand what was generated? Can you defend it? Will you maintain it?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's been the real standard all along.&lt;/p&gt;

&lt;p&gt;AI is just the next step on that continuum. It's more aggressive, more visible, and it generates more mediocre output faster. But it's not fundamentally different from any other tool that lets developers offload rote work to focus on decisions that matter.&lt;/p&gt;

&lt;p&gt;The question isn't whether AI should be accepted.&lt;/p&gt;

&lt;p&gt;In short, people have been saying software development is dead since the first high level programming language was invented... just, circling around the same anxiety cycle over and over.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Good Moderation Requirements
&lt;/h2&gt;

&lt;p&gt;no, it's not "open the floodgates." it's about what you're really filtering&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good moderation would:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Require disclosure of substantial AI involvement (transparency matters)&lt;/li&gt;
&lt;li&gt;Judge projects on reproducibility, test coverage, and maintainer accountability&lt;/li&gt;
&lt;li&gt;Fast-track projects with public benchmarks or active bug resolution&lt;/li&gt;
&lt;li&gt;Catch obvious slop &lt;em&gt;by its lack of depth&lt;/em&gt;, not its origin story&lt;/li&gt;
&lt;li&gt;Create space for people who are learning while filtering out low-effort republishing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bad moderation does what's happening now:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blanket bans that assume all AI-assisted work is equivalent to prompt-and-publish&lt;/li&gt;
&lt;li&gt;Dismissive comments that never engage with technical substance&lt;/li&gt;
&lt;li&gt;Rejection based on the presence of a tool, not the absence of rigor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DEV's shift toward requiring disclosure rather than banning AI-assisted content is great. It puts the burden on the creator to be honest, then lets the community decide based on the actual work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Matters
&lt;/h2&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/unitbuilds"&gt;@unitbuilds&lt;/a&gt; had it right: a developer community's job is to share work and get substantive feedback, not police the method.&lt;/p&gt;

&lt;p&gt;The questions that separate good engineering from slop are straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can you explain the architectural decisions?&lt;/li&gt;
&lt;li&gt;What broke, and how did you fix it?&lt;/li&gt;
&lt;li&gt;Are benchmarks reproducible?&lt;/li&gt;
&lt;li&gt;Will you maintain this?&lt;/li&gt;
&lt;li&gt;Are you willing to be corrected?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They work regardless of whether code was typed by a human, generated by Claude, or mixed. Because they're about understanding and accountability - what has always determined quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engineering should be evaluated on understanding, correctness, maintainability, testing, and accountability. Not keystrokes.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The World's Oldest Communication Protocol Is Music</title>
      <dc:creator>Adam - The Developer ✨</dc:creator>
      <pubDate>Fri, 24 Jul 2026 03:52:53 +0000</pubDate>
      <link>https://dev.to/adamthedeveloper/the-worlds-oldest-communication-protocol-is-music-3njg</link>
      <guid>https://dev.to/adamthedeveloper/the-worlds-oldest-communication-protocol-is-music-3njg</guid>
      <description>&lt;p&gt;This is going to be a very different article from what I usually write. No technical discussions, architecture deep dives, or engineering practices today. Instead, we're talking about something much older than software itself: music.&lt;/p&gt;

&lt;p&gt;We treat language like it's the default mode of human communication, like it's the &lt;em&gt;real&lt;/em&gt; and only thing used to communicate, everything else is secondary, emotional, aesthetic, nice to have.&lt;/p&gt;

&lt;p&gt;But language is actually the outlier. It's the new protocol layered on top of something much older.&lt;/p&gt;

&lt;p&gt;Music is the original standard and we've basically forgotten how to read it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Protocol Stack
&lt;/h2&gt;

&lt;p&gt;Think of communication like a network stack. Language is high-level. It's TCP/IP. Built on assumptions, needs learning, breaks the second you cross a boundary. You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A shared vocabulary&lt;/li&gt;
&lt;li&gt;Syntactic understanding&lt;/li&gt;
&lt;li&gt;Cultural context&lt;/li&gt;
&lt;li&gt;Years of study if you actually want fluency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's powerful but It's also fragile. And it's &lt;em&gt;recent&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Written language is a few thousand years old. Spoken language is older, sure, but both are late abstractions compared to the hundreds of thousands of years humans have been syncing bodies to shared sound. Relative to that timeline? Language is yesterday's patch.&lt;/p&gt;

&lt;p&gt;Music? That's the lower-level protocol. The physical layer everything else runs on.&lt;/p&gt;

&lt;p&gt;A Japanese teenager at a Michael Jackson concert doesn't need to speak English. She doesn't need to understand what "Man in the Mirror" means as a concept. She also doesn't need a music degree.  &lt;/p&gt;

&lt;p&gt;Music isn't &lt;em&gt;zero&lt;/em&gt;-cost. Genre, culture, convention still shape how we hear it. But the entry barrier for emotional communication is way lower. A rhythm can hit urgency, celebration, sadness, or tension long before anyone understands the formal structure behind it.&lt;/p&gt;

&lt;p&gt;Her nervous system speaks that fluently. And so does everyone else in that stadium.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Protocol Works
&lt;/h2&gt;

&lt;p&gt;Here's what happens when the song starts: 70,000 people stop being individuals and start being a distributed system synchronizing to the same signal.&lt;/p&gt;

&lt;p&gt;The bass hits. Her heartbeat starts matching it. Involuntarily. Her neurons fire in sync with the guy three rows over. The girl next to her moves when the rhythm says move — not because she decided to, but because the protocol sits lower than conscious decision-making.&lt;/p&gt;

&lt;p&gt;This isn't mystical. This isn't the "universal language of music" in the poetic sense. This is just biology.&lt;/p&gt;

&lt;p&gt;Babies respond to rhythm before they understand words. Crowds clap together without a conductor. Soldiers march to drums. Rituals across cultures use singing and percussion to pull a group into the same tempo. The pattern shows up everywhere because the hardware is shared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The protocol specs, if we're being nerdy about it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses mostly rhythm, tempo, and frequency&lt;/li&gt;
&lt;li&gt;Hits the nervous system, not the cognitive layer&lt;/li&gt;
&lt;li&gt;Needs way less shared context than language&lt;/li&gt;
&lt;li&gt;Backward compatible across human brains for 300,000+ years&lt;/li&gt;
&lt;li&gt;Works at scale (70,000 synchronizations at once)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Language, meanwhile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preprocessing (learning)&lt;/li&gt;
&lt;li&gt;Translation (if you're cross-language)&lt;/li&gt;
&lt;li&gt;Cognitive overhead (your brain has to &lt;em&gt;think&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;High latency (meaning takes time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Music? Low preprocessing. No translation step. Near-zero latency. Your body gets the signal before your mind finishes negotiating what it means.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why We Don't See This
&lt;/h2&gt;

&lt;p&gt;We're obsessed with meaning. We assume communication = transfer of semantic content. You encode an idea into words, I decode it, boom: we've connected.&lt;/p&gt;

&lt;p&gt;So when we see that girl screaming English lyrics she doesn't understand, we quietly reframe it. "Oh, the &lt;em&gt;message&lt;/em&gt; transcends language. She understands on a &lt;em&gt;deeper level&lt;/em&gt;."&lt;/p&gt;

&lt;p&gt;Nah. She's not understanding the message at all. She's on a different protocol entirely. Language is offline. The lower-level stuff is handling it.&lt;/p&gt;

&lt;p&gt;We miss this because we've built an entire civilization on top of language. We think the higher-level protocol is the "real" one. Which is like saying TCP/IP is more real than the electrical signals it runs on.&lt;/p&gt;

&lt;p&gt;Music isn't poetry. It isn't transcendence. It's infrastructure. It's what everything else runs on.&lt;/p&gt;

&lt;p&gt;And no, it's not &lt;em&gt;better&lt;/em&gt; than language. It solves a different problem. Music synchronizes systems. Language coordinates them.&lt;/p&gt;

&lt;p&gt;A drumbeat can make people march together. It won't tell them why they're marching, what they believe, or what to do when the song ends. Language is powerful because it encodes abstractions. Music is powerful because it doesn't need to.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Uncomfortable Part
&lt;/h2&gt;

&lt;p&gt;Here's the part that gets weird: you can sync humans at massive scale without them sharing &lt;em&gt;any&lt;/em&gt; values, beliefs, or understanding.&lt;/p&gt;

&lt;p&gt;That stadium full of people moving together? They don't agree on anything. They might hate each other politically. They might speak no common language. They might have zero common ground.&lt;/p&gt;

&lt;p&gt;But for three minutes, their nervous systems are linked. That's a connection. Just not the kind we like to talk about.&lt;/p&gt;

&lt;p&gt;We want connection to require understanding. We want it to mean something. We want it to prove that underneath all our differences, we're fundamentally united in shared meaning.&lt;/p&gt;

&lt;p&gt;What music actually shows us is simpler and weirder: humans are pattern-matching machines wired to sync with other humans. You don't need shared values for that. You don't need agreement. You don't need understanding.&lt;/p&gt;

&lt;p&gt;You just need a rhythm everyone can feel.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means
&lt;/h2&gt;

&lt;p&gt;Language will always need translation and will always be high-friction, high-latency, high-failure-rate across boundaries and... that's not a bug, it's what gives it power, precision requires friction.&lt;/p&gt;

&lt;p&gt;But for raw synchronization, getting lots of humans moving together, feeling together, breathing together, language is overkill. Also unreliable.&lt;/p&gt;

&lt;p&gt;Music is the protocol that works when nothing else does.&lt;/p&gt;

&lt;p&gt;An orchestra where nobody shares a language. A protest with thousands of strangers chanting. A stadium of people from different countries, different beliefs, different everything, all moving as one.&lt;/p&gt;

&lt;p&gt;These aren't examples of music "transcending barriers." They're examples of a lower-level protocol just... working, while the higher-level ones fail.&lt;/p&gt;

&lt;p&gt;We've built everything on top of language. We've made it the primary. And we've kind of forgotten what it's sitting on.&lt;/p&gt;

&lt;p&gt;Language lets us share thoughts. Music lets us share states.&lt;/p&gt;

&lt;p&gt;And every time a girl who doesn't speak English screams an English song in perfect sync with 70,000 other people, she's reminding us: the infrastructure was always there. We just stopped noticing it.&lt;/p&gt;

&lt;p&gt;The oldest communication protocol is still the most reliable one. We just got distracted by the newer layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  And sooooo, Here's my favorite song of the week
&lt;/h2&gt;

&lt;p&gt;✨🎶 &lt;strong&gt;Love Hurts&lt;/strong&gt; — Nazareth&lt;/p&gt;

&lt;h2&gt;
  
  
    &lt;iframe src="https://www.youtube.com/embed/Vj2AlaQcW40"&gt;
  &lt;/iframe&gt;

&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Drop your favorite music down below, let's hear what gets your system in sync!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>music</category>
      <category>distributedsystems</category>
      <category>productivity</category>
      <category>learning</category>
    </item>
    <item>
      <title>Stop Saying You Want Ownership Mindset</title>
      <dc:creator>Adam - The Developer ✨</dc:creator>
      <pubDate>Tue, 14 Jul 2026 05:59:52 +0000</pubDate>
      <link>https://dev.to/adamthedeveloper/stop-saying-you-want-ownership-mindset-34nf</link>
      <guid>https://dev.to/adamthedeveloper/stop-saying-you-want-ownership-mindset-34nf</guid>
      <description>&lt;p&gt;My 2nd article this week, I'm supposed to keep it to just once per week but whateverrr, I've had this in drafts for a while now, let's get straight into it.&lt;/p&gt;

&lt;p&gt;Here's the thing about "ownership mindset" that nobody on the management side wants to admit: you love the slogan. You hate the behavior.&lt;/p&gt;

&lt;p&gt;You want engineers who care. Who think about scale. Who ask "wait, will this melt in four months?" Who treat the product like it's theirs.&lt;/p&gt;

&lt;p&gt;Cool. Then someone &lt;em&gt;actually does that&lt;/em&gt; in a meeting, and suddenly they're "being difficult," "not a team player," "too negative," or my personal favorite — "can you just build what I asked for?"&lt;/p&gt;

&lt;p&gt;I've sat through this movie enough times that I can recite the script.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Script
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Manager / PM / whoever is currently wearing the "decider" hat:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We need someone with real ownership on this. Someone who cares whether the product actually works.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Someone raises a real concern about the architecture. Not vibes. Constraints. Data model. Failure modes. The stuff that will wake someone up at in the middle of the night later.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Same person, 90 seconds later:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why are you pushing back so hard? Why are you being so difficult? We're already behind. Just ship it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So which is it?&lt;/p&gt;

&lt;p&gt;You asked for ownership. They gave you ownership. Now you're mad that ownership came with opinions.&lt;/p&gt;




&lt;h2&gt;
  
  
  You're Not Confused. You're Convenient.
&lt;/h2&gt;

&lt;p&gt;I used to think this was a communication problem. It's not. It's incentives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You feel the delay today. You don't feel the outage later.&lt;/strong&gt; Design pushback slows the thing you want shipped &lt;em&gt;this sprint&lt;/em&gt;. The mess it prevents shows up next quarter, under someone else's KPI. Of course you optimize for the pain that's in the room.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hierarchy feels like efficiency when you're on top of it.&lt;/strong&gt; A senior decision getting questioned doesn't feel like engineering. It feels like disrespect. Even when the question is "this will corrupt data under concurrent writes." You wanted a yes. You got a reason. Those feel different to the ego.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance feels like progress.&lt;/strong&gt; An engineer saying "yeah, I'll build it" gives you a hit of motion. An engineer saying "we should rethink the data model" feels like friction. Your brain does lazy math: less friction = better leadership. No. Less friction = quieter failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And "ownership" means two different things depending on who's talking.&lt;/strong&gt; When you say it, you often mean "make sure it ships and don't bother me." When I hear it, I mean "make sure it doesn't screw us." Those conflict. You keep using the same word like that's my problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Quiet Engineer Is Not Owning Anything
&lt;/h2&gt;

&lt;p&gt;Here's the part that should piss you off more than it does: the engineer who just implements what you ask, every time, with a smile, is not demonstrating ownership.&lt;/p&gt;

&lt;p&gt;They're optimizing for approval.&lt;br&gt;
They're minimizing confrontation.&lt;br&gt;
They're following orders.&lt;/p&gt;

&lt;p&gt;None of that is ownership. That's liability transfer.&lt;/p&gt;

&lt;p&gt;When it breaks, they've got the perfect line: "I built exactly what was specified." Clean hands. Diffused responsibility. You got the compliant builder you rewarded.&lt;/p&gt;

&lt;p&gt;The person who says "this is going to bite us" is the one actually treating the outcome like theirs. They're spending social capital in a room that usually punishes that spend. If you punish them, don't act shocked when the next three people learn to shut up.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Ownership Actually Looks Like From Our Side
&lt;/h2&gt;

&lt;p&gt;It's not being a dick in design reviews. It's this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concerns come early.&lt;/strong&gt; During design. Not three weeks into implementation when changing direction costs a sprint and a relationship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reasoning, not vibes.&lt;/strong&gt; "This N+1 dies at 10k users on our current growth curve" beats "this feels off." If an engineer can't explain the failure mode, push back. If they can — listen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternatives, not just no.&lt;/strong&gt; "What if we index on user_id?" is ownership. "This won't work, good luck" is theater.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowing when to escalate vs. when to build-and-learn.&lt;/strong&gt; Sometimes you need to ship something imperfect to learn. Sometimes you're walking into a known footgun. A good owner can tell you which meeting you're in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Staying for the outcome.&lt;/strong&gt; "I told you so" is not ownership. Owning it means if you were wrong, you help clean it up. If you were right and ignored, you still help clean it up — and you remember who ignored you.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Get When You Train That Out of People
&lt;/h2&gt;

&lt;p&gt;I've watched this culture settle in. It's not subtle.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Engineers who optimize for "not getting blamed" instead of "building something that lasts"&lt;/li&gt;
&lt;li&gt;Debt that accumulates in silence because speaking up is career-limiting&lt;/li&gt;
&lt;li&gt;Smart people quietly updating their LinkedIn&lt;/li&gt;
&lt;li&gt;Meltdowns that somehow still surprise leadership&lt;/li&gt;
&lt;li&gt;"Team player" becoming code for "doesn't question bad decisions"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You buy short-term speed. You pay medium-term chaos. Then you schedule a retrospective where everyone agrees "we should have spoken up earlier," and nobody asks why they stopped.&lt;/p&gt;




&lt;h2&gt;
  
  
  If You Actually Want It
&lt;/h2&gt;

&lt;p&gt;Stop asking for ownership like it's a personality trait. Build the conditions where it's safe to exercise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate design talk from implementation talk.&lt;/strong&gt; Let people think out loud about whether something will work before you've already committed the sprint. Thinking hard and shipping fast aren't enemies. Collapsing them into one meeting is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reward the pushback you say you want.&lt;/strong&gt; If someone raised a legitimate concern and you overrode them, make that a &lt;em&gt;visible decision&lt;/em&gt;. Don't pretend the conversation didn't happen. If they were right later, say so. Out loud. In front of people. If they were wrong, they learn the domain. Either way, the signal is: speaking up was the job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be specific.&lt;/strong&gt; "Take ownership" is useless. Do you mean "ship this by Friday even if it's dirty"? Then say that. Do you mean "this has to survive 10x traffic"? Say that. Vague slogans produce people guessing what will get them yelled at least.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listen when we push back.&lt;/strong&gt; Not because we're always right — we're not. Because we live in the codebase you only see through tickets. That's not attitude. That's the job you hired us for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normalize disagreement.&lt;/strong&gt; The best teams I've been on argue about design and still trust each other the next morning. Disagreement isn't personal. Making it personal is how you get yes-men and broken systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;You probably do want engineers with an ownership mindset.&lt;/p&gt;

&lt;p&gt;What you don't want, and keep accidentally selecting for, is engineers who smile, implement, and let you walk into the wall so they can stay popular.&lt;/p&gt;

&lt;p&gt;Ownership is annoying. It asks inconvenient questions. It slows the "just ship it" meeting. It makes seniors defend decisions they wanted to treat as settled.&lt;/p&gt;

&lt;p&gt;If that sounds exhausting, good. That's the cost of building something that doesn't fall apart the second real users touch it.&lt;/p&gt;

&lt;p&gt;You don't get "people who care" and "people who never push back." Pick one.&lt;/p&gt;

&lt;p&gt;And if you pick compliance, at least stop calling it ownership. That word still means something to the rest of us.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>programming</category>
      <category>architecture</category>
      <category>software</category>
    </item>
    <item>
      <title>I Could Review It. I Couldn’t Write It.</title>
      <dc:creator>Adam - The Developer ✨</dc:creator>
      <pubDate>Mon, 13 Jul 2026 07:52:12 +0000</pubDate>
      <link>https://dev.to/adamthedeveloper/i-could-review-it-i-couldnt-write-it-3gfj</link>
      <guid>https://dev.to/adamthedeveloper/i-could-review-it-i-couldnt-write-it-3gfj</guid>
      <description>&lt;p&gt;...now, I pride myself on being good at my job, I'm fast at catching mistakes, I'm efficient at reviewing code and I'm quick to spot patterns across systems. But ever since I knew how to use AI ( maybe around mid 2024, I was still doing the old trad way when i started in 2018 ), I've been letting AI write ALOT of my code.&lt;/p&gt;

&lt;p&gt;And I thought I was still learning.&lt;/p&gt;

&lt;p&gt;Then one day I sat down to write an HTTP handler in Go from scratch, and I froze... and I can't describe how scary this was.&lt;/p&gt;

&lt;p&gt;Not because I don't understand HTTP or because I don't know Go because I recognize these patterns instantly:&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;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;handleJobNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;ResponseWriter&lt;/span&gt;&lt;span class="p"&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;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can review it, spot mistakes, discuss the design. But build it from scratch? I found myself thinking: "Wait... is it &lt;code&gt;http.HandleFunc&lt;/code&gt;? &lt;code&gt;ServeMux&lt;/code&gt;? How do I start the server?"&lt;/p&gt;

&lt;p&gt;That's when I realized something was wrong, I realized the trap I'd fallen into and I had to do something about it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recognition is not Production
&lt;/h2&gt;

&lt;p&gt;This biggest illusion that AI makes much easier to fall into is the thinking that goes:&lt;br&gt;&lt;br&gt;
"I understand this" and it feels &lt;em&gt;exactly&lt;/em&gt; like "I can produce this."&lt;/p&gt;

&lt;p&gt;I also wanna clarify that AI isn't the first one to inven this problem, Stack Overflow, copy-pasted frameworks and tutorials all created it first and AI's just accelarating the distance.&lt;/p&gt;

&lt;p&gt;Recognizing a solution is just one skill. Your brain does pattern matching beautifully and way effortlessly than you think once you get used to it, you see &lt;code&gt;http.HandleFunc&lt;/code&gt;, think "yep, that's how you register routes," and feel confident.But try retriving that knowledge from scratch, constructing it... that's a whole other level, those are completely different neural pathways because the ability to review code, design &amp;amp; architecture aren't the same as writing it from memory and it should never be mistaken.  &lt;/p&gt;

&lt;p&gt;I got really good at reviewing, too good I guess, I am fast &amp;amp; efficient, I could critique architecture, spot edge cases, ask the right questions and basically start a whole drunken bar vibe conversation with it but you know what scares me despite being able to do all of this? I couldn't write an HTTP handler from memory.&lt;/p&gt;

&lt;p&gt;I WAS practicing &lt;em&gt;some&lt;/em&gt; skills. just some. just not the ones I thought.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Gap and I Started A Series
&lt;/h2&gt;

&lt;p&gt;There's also a dangerous confidence trap here: convincing yourself you're still learning because you're the "architect" or "reviewer", you're making decisions, you understand the system.&lt;/p&gt;

&lt;p&gt;But how would you feel if knowing you can't write what the AI wrote, not without looking. that's the gap.&lt;/p&gt;

&lt;p&gt;Anyway, a few months ago, I started writing distributed systems algorithms in Go from scratch, each algorithm belonging to their own repo. No AI. I write entirely, then ask it to review and I tell you it's very rewarding!  &lt;/p&gt;

&lt;p&gt;I am doing the series for 2 reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This will help me strengthen my skill in Go, I love Go but it's also the least used language of mine too but a very important one.&lt;/li&gt;
&lt;li&gt;I hope to help others understand algorithms in distributed systems where it's written with as much as simplicity as I could but also not leaving out edge cases and failure modes. Many developers work with one unknowingly and wonder why their simple code causes data corruption as soon as someone adds another instance of the backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will be releasing the article one by one each after I'm done where I will document every hell and tasmanian devil I would've had gone through, no filters with the exception of bad languages.&lt;/p&gt;




&lt;h2&gt;
  
  
  You Can't Download Experience
&lt;/h2&gt;

&lt;p&gt;Great software doesn't come from perfect code, It comes from &lt;em&gt;surviving&lt;/em&gt; bad code.&lt;/p&gt;

&lt;p&gt;Engineering intuition is built from mistakes, failed designs, debugging at 2 AM, edge cases that shouldn't exist, trade-offs that haunt you for years. The final code is just the artifact. The scar tissue is the education.&lt;/p&gt;

&lt;p&gt;Take a system like PostgreSQL represents decades of accumulated engineering decisions.&lt;/p&gt;

&lt;p&gt;I'm picking on Postgres specifically because I saw someone tried to rewrite Postgres with Rust nearly entirely with agents. It's not a bad thing, in fact, it's improving Postgres tremendously: with Rust, it's moving from the historic &lt;em&gt;process-per-connection&lt;/em&gt; to &lt;em&gt;thread-per-connection,&lt;/em&gt; massive performance gain and some of the long-standing postgres pain points.&lt;/p&gt;

&lt;p&gt;You can rewrite it. You can reproduce the behavior. But you won't reproduce the journey that created that understanding—the bugs fixed, the performance lessons learned, the constraints that forced particular designs.&lt;/p&gt;

&lt;p&gt;Letting an agent write everything and you get the artifact without the scar tissue.&lt;/p&gt;

&lt;p&gt;And expertise lives in the scar tissue.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Principle
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The skills you practice will grow. The skills you outsource will weaken.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I outsourced writing and my writing muscle atrophied. I got better at reviewing and architecture, but lost something important.&lt;/p&gt;

&lt;p&gt;Now I'm deliberate: distributed systems algorithms in Go, I write. from scratch.&lt;/p&gt;

&lt;p&gt;Not because I am better than AI but because I'm choosing which muscles to exercise and the choice matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'm Not Saying
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AI is not bad. I use it daily. Heck, I still use it for work because businesses' deadlines now are unrealistic standards compared to what they were a few years ago.&lt;/li&gt;
&lt;li&gt;Code review is still critical work.&lt;/li&gt;
&lt;li&gt;AI is still my thinking partner and my force multiplier, I still consult it from time to time when making decisions, it builds me up and makes me a better developer and I make decisions faster because I'm not the only one thinking when no one's around.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I'm saying is: &lt;strong&gt;Be intentional about which parts of expertise you allow to change.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every tool reshapes what engineers need to know. The goal isn't to preserve manual typing forever. The goal is to ensure that the parts of engineering &lt;em&gt;you want to own&lt;/em&gt; are still exercised by you.&lt;/p&gt;

&lt;p&gt;If you want to stay sharp at writing, you have to pick code you're not going to outsource.&lt;/p&gt;

&lt;p&gt;Because expertise follows exercise. Always.&lt;/p&gt;

&lt;p&gt;The question isn't whether you can write without AI.&lt;/p&gt;

&lt;p&gt;The question is: &lt;em&gt;Will you?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>tools</category>
    </item>
    <item>
      <title>You Never Eliminate a Bottleneck — You Just Move It</title>
      <dc:creator>Adam - The Developer ✨</dc:creator>
      <pubDate>Mon, 06 Jul 2026 09:59:27 +0000</pubDate>
      <link>https://dev.to/adamthedeveloper/you-never-eliminate-a-bottleneck-you-just-move-it-41ia</link>
      <guid>https://dev.to/adamthedeveloper/you-never-eliminate-a-bottleneck-you-just-move-it-41ia</guid>
      <description>&lt;p&gt;I once spent quite some time optimizing a database query that was bringing our API to its knees. We shaved 200ms off response time with careful indexing and query rewriting. Success, right?&lt;/p&gt;

&lt;p&gt;Wrong.&lt;/p&gt;

&lt;p&gt;Within a week, a different bottleneck surfaced. The network became the constraint. We optimized that. Then client-side rendering became slow. Then memory usage spiked. Each "fix" just moved the problem downstream.&lt;/p&gt;

&lt;p&gt;Eventually, I learned something that changed how I approach performance work: &lt;strong&gt;bottlenecks aren't anomalies to eliminate, they're laws of nature baked into how systems work.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Bottlenecks Are Inevitable
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Theory of Constraints
&lt;/h3&gt;

&lt;p&gt;Every system has a weakest link. Think of water flowing through a pipeline, the narrowest section determines your throughput, no matter how wide the rest of the pipe is.&lt;/p&gt;

&lt;p&gt;Software systems work the same way. At any given moment, &lt;em&gt;something&lt;/em&gt; is the binding constraint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is it the CPU?&lt;/li&gt;
&lt;li&gt;The database?&lt;/li&gt;
&lt;li&gt;Network latency?&lt;/li&gt;
&lt;li&gt;Disk I/O?&lt;/li&gt;
&lt;li&gt;Memory?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you optimize that constraint, you don't eliminate constraints altogether. &lt;strong&gt;The constraint simply moves to the next-slowest component.&lt;/strong&gt; The system's total throughput increases, but a new bottleneck emerges.&lt;/p&gt;

&lt;h3&gt;
  
  
  Amdahl's Law: The Math That Proves It
&lt;/h3&gt;

&lt;p&gt;Even with infinite parallelization, there's a mathematical ceiling. Amdahl's Law tells us:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9oe009j5ib6k2usdm1rl.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9oe009j5ib6k2usdm1rl.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If just &lt;strong&gt;10% of your code runs sequentially&lt;/strong&gt;, you can never speed up more than 10x—no matter if you have 1,000 CPUs. That sequential 10% becomes an eternal bottleneck.&lt;/p&gt;

&lt;p&gt;For our social media app, some processes &lt;em&gt;must&lt;/em&gt; happen in order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate user&lt;/li&gt;
&lt;li&gt;Fetch permissions&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Then&lt;/em&gt; fetch feed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can't parallelize that without breaking safety. So authentication becomes a permanent bottleneck, though a small one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Great Trade-Off Game
&lt;/h2&gt;

&lt;p&gt;Here's the dirty secret: optimizing for one thing almost always hurts something else. You're not solving problems; you're &lt;strong&gt;choosing which constraint to live with.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Optimize For&lt;/th&gt;
&lt;th&gt;What Improves&lt;/th&gt;
&lt;th&gt;What Gets Worse&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fast responses&lt;/td&gt;
&lt;td&gt;CPU/memory usage spikes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;More requests/sec&lt;/td&gt;
&lt;td&gt;Resource exhaustion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cheaper infrastructure&lt;/td&gt;
&lt;td&gt;Performance degrades&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consistency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Strong guarantees&lt;/td&gt;
&lt;td&gt;Write latency increases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Availability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Always online&lt;/td&gt;
&lt;td&gt;Coordination overhead explodes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pick one. You're implicitly de-prioritizing others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: The Classic API Scaling
&lt;/h3&gt;

&lt;p&gt;Let's say I worked on an API that handled 10 requests/second. Bottleneck? The database.&lt;/p&gt;

&lt;p&gt;We added caching.&lt;/p&gt;

&lt;p&gt;Now: 100 requests/second. Bottleneck? Memory, our cache grew too big.&lt;/p&gt;

&lt;p&gt;We sharded the cache.&lt;/p&gt;

&lt;p&gt;Now: 500 requests/second. Bottleneck? Network bandwidth between nodes.&lt;/p&gt;

&lt;p&gt;We optimized serialization.&lt;/p&gt;

&lt;p&gt;Now: 1,000 requests/second. Bottleneck? Client-side parsing of responses.&lt;/p&gt;

&lt;p&gt;We gzipped responses.&lt;/p&gt;

&lt;p&gt;Now: 2,000 requests/second. Bottleneck? The upstream service that feeds our API.&lt;/p&gt;

&lt;p&gt;At each step, we "solved" the problem. But we never eliminated bottlenecks—we choreographed a dance, moving them from place to place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed Systems Make It Worse
&lt;/h2&gt;

&lt;p&gt;In a distributed system, bottlenecks hide in shadows and move in unpredictable ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network RTT (round-trip time) is a physical law.&lt;/strong&gt; You can't send data faster than light travels. For a global system, that's roughly 50-150ms of minimum latency per hop. Every extra network call multiplies this cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leader election is expensive.&lt;/strong&gt; When a leader fails in your distributed system, electing a new one takes time. During that window, writes are blocked. The leader election itself becomes the bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quorum writes create coordination overhead.&lt;/strong&gt; To ensure consistency across replicas, you need a quorum (majority) to acknowledge a write. That coordination, waiting for responses from multiple nodes, is itself a bottleneck that throttles your write throughput.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hot partitions kill performance.&lt;/strong&gt; If all your traffic hits one partition (shard) of data, that partition becomes the bottleneck. You optimized everywhere else, but one hot partition brings the whole system down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-region latency compounds.&lt;/strong&gt; Replicate data to multiple regions for resilience? Now reads and writes must cross regions. A single cross-region call might be 100ms. Add three of them together? You've hit a bottleneck that optimization can't touch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fan-out requests multiply latency.&lt;/strong&gt; Your service calls 10 microservices in parallel. The slowest one determines your response time. You optimize 9 of them, but the 10th — now &lt;em&gt;that's&lt;/em&gt; your bottleneck.&lt;/p&gt;

&lt;p&gt;There is no way around these trade-offs. They're mathematical, physical. Bottlenecks in distributed systems aren't bugs, they're features of reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Practical Mindset Shift
&lt;/h2&gt;

&lt;p&gt;The question shouldn't be: "How do I eliminate bottlenecks?"&lt;/p&gt;

&lt;p&gt;It should be: "Which bottleneck am I willing to live with, and how do I make sure it's the right one?"&lt;/p&gt;

&lt;h3&gt;
  
  
  Actionable practices:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Profile first, optimize later&lt;/strong&gt;&lt;br&gt;
Use a profiler. Find the actual bottleneck, not the one you &lt;em&gt;think&lt;/em&gt; exists. 90% of optimization effort goes to wrong places because developers guess.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Optimize strategically&lt;/strong&gt;&lt;br&gt;
Focus on the binding constraint. Optimizing something that's 5% of your runtime is pointless if the database is 80% of your runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Plan for the next bottleneck&lt;/strong&gt;&lt;br&gt;
When you fix something, ask: "What becomes slow now?" You can't prevent it, but you can prepare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Make trade-offs consciously&lt;/strong&gt;&lt;br&gt;
Don't accidentally sacrifice something important. If you add aggressive caching, you're choosing performance over freshness. Make sure that's actually what you want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Accept limits gracefully&lt;/strong&gt;&lt;br&gt;
Some bottlenecks are laws of physics (network latency, disk I/O). Design around them instead of fighting them. Use caching for latency. Use batching for throughput.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Some of the best engineers I know aren't the ones who "eliminate" bottlenecks. They're the ones who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand where bottlenecks live&lt;/li&gt;
&lt;li&gt;Move them strategically&lt;/li&gt;
&lt;li&gt;Stay ahead of the game by anticipating the next constraint&lt;/li&gt;
&lt;li&gt;Optimize for what actually matters (user experience, cost, reliability)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your job isn't to break the laws of systems theory. It's to work within them intelligently.&lt;/p&gt;

&lt;p&gt;The bottleneck never disappears. You just get better at dancing around it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Eliyahu Goldratt&lt;/strong&gt;: &lt;a href="https://www.leanproduction.com/theory-of-constraints/" rel="noopener noreferrer"&gt;The Theory of Constraints&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gene Amdahl&lt;/strong&gt;: &lt;a href="https://www.geeksforgeeks.org/computer-organization-architecture/computer-organization-amdahls-law-and-its-proof/" rel="noopener noreferrer"&gt;Amdahl's Law (1967)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brendan Gregg&lt;/strong&gt;: Systems Performance (book) + his incredible &lt;a href="https://www.brendangregg.com/" rel="noopener noreferrer"&gt;blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eric Brewer&lt;/strong&gt;: &lt;a href="https://www.geeksforgeeks.org/dbms/the-cap-theorem-in-dbms/" rel="noopener noreferrer"&gt;CAP Theorem&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Illusion of the Clean Slate</title>
      <dc:creator>Adam - The Developer ✨</dc:creator>
      <pubDate>Tue, 30 Jun 2026 06:28:20 +0000</pubDate>
      <link>https://dev.to/adamthedeveloper/the-illusion-of-the-clean-slate-2di0</link>
      <guid>https://dev.to/adamthedeveloper/the-illusion-of-the-clean-slate-2di0</guid>
      <description>&lt;p&gt;Every engineer has fantasized about it: starting over. Throwing out the old system and building something clean. No legacy constraints. No accumulated compromises. Just pure, intentional design.&lt;/p&gt;

&lt;p&gt;It never works that way.&lt;/p&gt;

&lt;p&gt;You can delete all the code. You can architect from scratch. You can make the best technical decisions possible. But you can't delete the organizational memory. You can't unlearn what the last system taught you. You can't escape the patterns that already run through the business, the workflows people have shaped themselves around, the problems you've already paid the cost of understanding.&lt;/p&gt;

&lt;p&gt;The new system will look clean. But it will be haunted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What rewrites actually inherit
&lt;/h2&gt;

&lt;p&gt;A rewrite isn't a fresh start. It's archaeology pretending to be innovation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The constraints don't go away.&lt;/strong&gt; The old system wasn't overcomplicated because engineers were bad. It was overcomplicated because of customer requirements, regulatory expectations, performance demands, and edge cases that took years to discover. A fresh rewrite finds all those edge cases again. Slower this time, because you don't have documentation—you have broken customers and escalations. The system gets layers of protection again, but now it looks like paranoia instead of learned caution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The organizational memory becomes invisible.&lt;/strong&gt; Someone fought for that data model three years ago. There was a reason. A business rule that couldn't be violated. A data consistency requirement that cost a quarter to figure out. The new system doesn't have the battle scars that explain &lt;em&gt;why&lt;/em&gt; things are the way they are. So they get rebuilt differently, until they hit the same requirement at 2am on a Saturday.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workflow is already baked in.&lt;/strong&gt; Users have shaped their behavior around the old system. Sales has built their pitch around certain capabilities. Support has written documentation and runbooks. Customers have automation that depends on specific behaviors. The new system is technically cleaner, but it forces change on everyone else first. The rewrite isn't done until the new system works like the old one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The code patterns stick around.&lt;/strong&gt; Teams find themselves solving the same problems the same ways. Not because the architecture forces it, but because it's familiar. Because there's institutional knowledge about how to do X in a way that doesn't break. Because someone senior knows the pitfalls. The "clean" codebase starts looking like the old one within six months, just with better error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ghost of the old system
&lt;/h2&gt;

&lt;p&gt;sometimes the best decision in a rewrite is to do it exactly like the previous system.&lt;/p&gt;

&lt;p&gt;Not because the old code was good. But because a &lt;em&gt;lot of people understand it&lt;/em&gt;. The business logic is tested by production. The failure modes are known. The performance characteristics are predictable. Starting fresh means starting ignorant, and ignorance costs money.&lt;/p&gt;

&lt;p&gt;A truly clean slate would require starting ignorant &lt;em&gt;and&lt;/em&gt; willing to pay the cost of ignorance. Most organizations can't afford that. So the rewrite becomes "the old thing, but better implemented"—which is fine, maybe even better, but it's not what anyone was imagining when they said "let's start over."&lt;/p&gt;

&lt;p&gt;The ghost isn't a bad thing. It's just unavoidable. Organizations have memory whether they codify it or not. That memory shapes everything built inside them, even when you're trying to escape it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Understanding this changes how you approach a rewrite. You're not escaping the past. You're translating it.&lt;/p&gt;

&lt;p&gt;The best rewrites don't pretend to be clean slates. They acknowledge what they're inheriting. They talk to users about why the old system worked the way it did. They preserve the tacit knowledge that's been learned the hard way. They choose: what patterns do we keep because they're actually good, versus what patterns do we change because we have time to do it right.&lt;/p&gt;

&lt;p&gt;The worst rewrites assume they're smarter than everyone who came before. They rebuild against the patterns of the old system instead of with understanding of them. They rediscover the same edge cases as new bugs instead of as features. They become legacy before they're finished.&lt;/p&gt;

&lt;p&gt;A rewrite isn't a clean slate. It's a translation. And translation is hard because you have to understand both languages, the constraints of the old world and the possibilities of the new one. You have to honor what worked without being trapped by it.&lt;/p&gt;

&lt;p&gt;The fantasy of the clean slate is tempting because it promises escape. But the only way to actually learn something is to carry it forward, consciously, even when you're trying to leave it behind.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>software</category>
    </item>
    <item>
      <title>Conduit: The Gateway I Built to Forget About</title>
      <dc:creator>Adam - The Developer ✨</dc:creator>
      <pubDate>Wed, 24 Jun 2026 07:37:59 +0000</pubDate>
      <link>https://dev.to/adamthedeveloper/conduit-the-gateway-i-built-to-forget-about-2ann</link>
      <guid>https://dev.to/adamthedeveloper/conduit-the-gateway-i-built-to-forget-about-2ann</guid>
      <description>&lt;p&gt;I've been trying to keep my publishing schedule to one article a week, but this one felt too special to wait so I had Claude helping me generate a portion of this so I can publish it quick.&lt;/p&gt;

&lt;p&gt;The article today might be a little boring depending on how much you're into systems and distributed architecture. And if you're already thinking, "Great, another API gateway," I can't really blame you.&lt;/p&gt;

&lt;p&gt;But bear with me for a bit: Conduit isn't just another request-forwarding proxy. Along the way, we'll dive into programmable gateways, sandboxed runtimes, Unix sockets, and some of the engineering decisions that made building it surprisingly fun.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Built a small Go API gateway in front of three services with two different auth schemes (custom HMAC sessions, and JWT).&lt;/li&gt;
&lt;li&gt;It worked. It also became unmaintainable: every routing exception meant editing Go and redeploying.&lt;/li&gt;
&lt;li&gt;Rewrote it as &lt;strong&gt;Conduit&lt;/strong&gt;: Go stays on the network path (routing, proxying, CORS, SSRF guards). A sandboxed &lt;strong&gt;Deno runtime&lt;/strong&gt; runs policy as plain TypeScript plugins, talking to Go over a Unix socket with a strict snapshot-and-patch protocol.&lt;/li&gt;
&lt;li&gt;Net result: auth, logging, and routing rules became files I could edit and save, no recompiling the gateway.&lt;/li&gt;
&lt;li&gt;Full FAQ/defense-of-design-decisions Q&amp;amp;A is &lt;a href="https://a-thedeveloper.vercel.app/blog/conduit" rel="noopener noreferrer"&gt;here&lt;/a&gt;, not in this post.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Three services, one public API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;chat-service&lt;/strong&gt;: real-time messaging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;user-service&lt;/strong&gt;: profiles, sessions, identity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;admin-service&lt;/strong&gt;: internal tooling, elevated operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;User and chat traffic ran a custom session + HMAC scheme: &lt;code&gt;Authorization: Session &amp;lt;id&amp;gt;&lt;/code&gt;, signed with &lt;code&gt;X-Signature&lt;/code&gt;, &lt;code&gt;X-Timestamp&lt;/code&gt;, &lt;code&gt;X-Nonce&lt;/code&gt;, backed by Redis for sessions and replay protection. Not JWT. Not OAuth. Something built and iterated on for months.&lt;/p&gt;

&lt;p&gt;Admin traffic was JWT, verified at the edge, then forwarded with trusted &lt;code&gt;X-Gateway-Admin-&lt;/code&gt;* headers so the admin service skipped re-validation.&lt;/p&gt;

&lt;p&gt;Different trust models. Different header shapes. Different public-path exceptions. All of it had to land on one hostname, because clients don't care how many services you run behind it.&lt;/p&gt;

&lt;p&gt;So I built the obvious thing: a small Go gateway. Service-prefixed paths picked the upstream. A &lt;code&gt;route.Select()&lt;/code&gt; function picked the auth mode. A growing stack of prefix conditionals decided which paths were public:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/admin-service/*  → admin upstream   + JWT (except login, refresh, external-api)
/user-service/*   → user upstream    + HMAC (except auth, device, internal, …)
/chat-service/*   → chat upstream    + HMAC for users, JWT for /api/v1/admin/*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It shipped. It worked. It was ugly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the ugliness actually lived
&lt;/h2&gt;

&lt;p&gt;Not in reliability. Requests hit the right upstream, JWT/HMAC validation worked, sessions slid TTL correctly. The ugliness was in &lt;strong&gt;extensibility&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="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Select&lt;/span&gt;&lt;span class="p"&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;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Backend&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;h&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;applyAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sessionStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nonceStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proxy&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;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean on paper. But &lt;code&gt;adminAuth()&lt;/code&gt;, &lt;code&gt;userAuth()&lt;/code&gt;, and &lt;code&gt;chatAuth()&lt;/code&gt; were each a wall of &lt;code&gt;strings.HasPrefix&lt;/code&gt; branches, each one carrying tribal knowledge about which paths were exceptions. Every new public-path exception meant editing Go and redeploying the gateway. Figuring out &lt;em&gt;why&lt;/em&gt; a path behaved a certain way meant reading three functions and cross-referencing a README the size of a small service.&lt;/p&gt;

&lt;p&gt;I wasn't failing at microservices. I was failing at &lt;strong&gt;boundary discipline&lt;/strong&gt;: policy had nowhere clean to live except deeper inside the gateway's Go source.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every new exception made the gateway more specific instead of more general.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That gateway didn't get thrown away. It proved the routing model was right. It just proved the &lt;em&gt;implementation&lt;/em&gt; model needed to change. That became Conduit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design goals (the actual constraints, not aspirations)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Freedom of implementation.&lt;/strong&gt; If you can write TypeScript, you can extend the gateway. No DSL, no Lua config, no plugin marketplace format: drop a file in &lt;code&gt;./plugins&lt;/code&gt;, export an object with lifecycle hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GatewayContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../runtime/shared/types.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;beforeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GatewayContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;missing token&lt;/span&gt;&lt;span class="dl"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Low operational overhead.&lt;/strong&gt; No control plane, no Kubernetes requirement, no sidecar ceremony. Default setup is one Go binary supervising a Deno runtime, reading a JSON config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run ./cmd/conduit &lt;span class="nt"&gt;-config&lt;/span&gt; conduit.config.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Explicit separation of concerns.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Go gateway&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP ingress, routing, upstream proxying, CORS, body limits, timeouts, SSRF guards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deno runtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sandboxed plugin execution in a warm worker pool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plugins&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Business policy: auth, logging, transforms, route control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Go never gets to know about plugin internals. The runtime never owns routing. Plugins never touch the network boundary directly. These invariants are written down because violating them produces cross-language bugs that tests don't reliably catch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Predictable failure modes.&lt;/strong&gt; A misbehaving plugin shouldn't take down the gateway. Hook timeouts log and continue. Worker crashes replace the isolate, not the process. If the entire runtime is unreachable, &lt;code&gt;failPolicy&lt;/code&gt; decides: &lt;code&gt;closed&lt;/code&gt; (503, no proxy) or &lt;code&gt;open&lt;/code&gt; (skip plugins, proxy anyway), configurable per route.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Invisible in production.&lt;/strong&gt; The bar I actually cared about: deploy a plugin when policy changes, glance at structured logs when something's off, otherwise forget the thing exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: two processes, one socket
&lt;/h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgdbudfy2h1yrsaee8yar.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgdbudfy2h1yrsaee8yar.png" alt=" " width="799" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Per request:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client hits the Go gateway.&lt;/li&gt;
&lt;li&gt;CORS preflight (&lt;code&gt;OPTIONS&lt;/code&gt;) is answered in Go; plugins never see it.&lt;/li&gt;
&lt;li&gt;Go builds a serializable context snapshot from the request.&lt;/li&gt;
&lt;li&gt;Every plugin's &lt;code&gt;beforeRequest&lt;/code&gt; hook runs, in filename order.&lt;/li&gt;
&lt;li&gt;Go proxies once to the matched (or plugin-selected) upstream.&lt;/li&gt;
&lt;li&gt;The upstream response gets attached to context.&lt;/li&gt;
&lt;li&gt;Every plugin's &lt;code&gt;afterResponse&lt;/code&gt; hook runs, same order.&lt;/li&gt;
&lt;li&gt;Go writes the response to the client.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Upstream services have zero awareness Conduit exists: no SDK, no self-registration. Multi-service routing is config, not code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"routes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/user-service/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"upstream"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://user-service:2001"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/admin-service/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"upstream"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://admin-service:2002"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/chat-service/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"upstream"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://chat-service:2003"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One plugin chain, many backends, policy applied uniformly at the edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual hard part: getting context across the process boundary
&lt;/h2&gt;

&lt;p&gt;The real problem with the first gateway wasn't routing logic. It was &lt;em&gt;where state lived&lt;/em&gt;. Mutable request objects passed through middleware created hidden coupling, and that gets worse the moment you're crossing a process boundary: you cannot hand Deno a live &lt;code&gt;http.Request&lt;/code&gt; and hope for the best.&lt;/p&gt;

&lt;p&gt;Conduit's contract:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go &lt;strong&gt;snapshots&lt;/strong&gt; the request into serializable JSON.&lt;/li&gt;
&lt;li&gt;The snapshot crosses a length-prefixed frame over a Unix socket to Deno.&lt;/li&gt;
&lt;li&gt;The plugin operates on a cloned, hook-local context with a frozen inbound request.&lt;/li&gt;
&lt;li&gt;The plugin returns a &lt;strong&gt;patch&lt;/strong&gt;: a minimal delta, not a mutated object.&lt;/li&gt;
&lt;/ol&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftk257re0smggaasx6zxz.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftk257re0smggaasx6zxz.png" alt=" " width="800" height="791"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why patch instead of full context on return:&lt;/strong&gt; sending the complete context back from every hook means re-serializing headers, identity, and potentially large bodies, twice per request, per plugin. The patch engine instead sends only what changed: omitted fields are unchanged, present fields overwrite, &lt;code&gt;null&lt;/code&gt; tombstones a deletion. A logging plugin that sets one &lt;code&gt;state&lt;/code&gt; field ships a few dozen bytes back, not the whole request graph.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why bodies never fully cross the wire:&lt;/strong&gt; anything above 64 KiB stays in a per-request &lt;code&gt;BodyStore&lt;/code&gt; in Go. Plugins get a &lt;code&gt;stream://&lt;/code&gt; reference token, not raw bytes. Megabytes stay in Go; metadata crosses the socket.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Plugins don't own the request. They propose changes to it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence is the whole design. Reject/forward signals, the patch diffing rules, the frozen snapshot: all of it exists to make "propose, don't own" safe and cheap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The plugin model
&lt;/h2&gt;

&lt;p&gt;Plugins stay deliberately small:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hook&lt;/th&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;onLoad&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Once at startup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;beforeRequest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Before upstream proxying&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;afterResponse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;After upstream response is captured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;onError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When a hook in &lt;em&gt;this&lt;/em&gt; plugin throws unexpectedly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;ctx.reject()&lt;/code&gt; is intentional control flow, not an error; &lt;code&gt;onError&lt;/code&gt; is for actual surprises.&lt;/p&gt;

&lt;p&gt;Plugins load in filename order, full stop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins/
  001-auth.ts           ← runs first
  002-logging.ts        ← sees ctx.user from auth
  003-header-rewrite.ts
  004-route-control.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No dependency graph, no hidden priority system. &lt;code&gt;001-auth.ts&lt;/code&gt; runs before &lt;code&gt;002-logging.ts&lt;/code&gt; because of the filename, and that's grep-able at 3am.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ctx&lt;/code&gt; surface is narrow on purpose:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Member&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ctx.request&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read-only inbound snapshot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ctx.response&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Outbound builder (&lt;code&gt;setStatus&lt;/code&gt;, &lt;code&gt;setHeader&lt;/code&gt;, &lt;code&gt;setBody&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ctx.user&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Identity set by auth plugins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ctx.state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-request key/value bag shared across hooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ctx.log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Structured logging with trace correlation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ctx.reject(status, msg)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stop pipeline, return HTTP error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ctx.forward(url)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Proxy to an alternate upstream (validated by the gateway)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ctx.services&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;cache&lt;/code&gt; helpers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No global singletons, no ambient mutable request object. Auth sets &lt;code&gt;ctx.user&lt;/code&gt;; logging reads it. That's the entire coordination contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened when I actually ran it
&lt;/h2&gt;

&lt;p&gt;The first deployment was personal: three services behind one port, HMAC checks in one plugin file, JWT checks in another, logging in a third. The prefix-matching spaghetti became three files I could read top to bottom.&lt;/p&gt;

&lt;p&gt;Then it spread quietly: internal tools needing a stable hostname, admin surfaces needing stricter gating, experimental routes I could add without redeploying backends.&lt;/p&gt;

&lt;p&gt;The moment I knew it worked wasn't a benchmark. It was forgetting it was running, then stopping the Docker container to debug something unrelated and watching everything break. That's the success criterion I actually cared about: not feature parity with Kong, invisibility under normal operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes, stated plainly
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Plugin hook exceeds timeout (default 100ms)&lt;/td&gt;
&lt;td&gt;Warning logged; request continues proxying&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plugin worker crashes&lt;/td&gt;
&lt;td&gt;Isolate replaced; gateway process survives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entire Deno runtime unavailable&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;failPolicy: "closed"&lt;/code&gt; → 503; &lt;code&gt;"open"&lt;/code&gt; → proxy without plugins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ctx.reject(401)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pipeline stops; remaining hooks skipped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid &lt;code&gt;ctx.forward()&lt;/code&gt; target&lt;/td&gt;
&lt;td&gt;Logged, ignored; original route used&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upstream failure&lt;/td&gt;
&lt;td&gt;Standard &lt;code&gt;502&lt;/code&gt; propagated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Hook timeout and runtime death are different failure classes on purpose. A slow logger shouldn't look like a dead security layer.&lt;/p&gt;

&lt;p&gt;The riskiest failure mode isn't a crash, though. It's &lt;strong&gt;silent semantic change&lt;/strong&gt;: a plugin that mis-sets &lt;code&gt;ctx.user&lt;/code&gt; or swallows a header keeps the system at &lt;code&gt;200 OK&lt;/code&gt; while behavior quietly drifts underneath it. That's why every log line carries &lt;code&gt;[trace:&amp;lt;id&amp;gt;]&lt;/code&gt;, and why &lt;code&gt;reject&lt;/code&gt; and thrown errors are treated as distinct signals.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it's not
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Not Envoy: no xDS, no WASM filter ecosystem at scale.&lt;/li&gt;
&lt;li&gt;Not Kong: no admin UI, no plugin marketplace.&lt;/li&gt;
&lt;li&gt;Not a service mesh.&lt;/li&gt;
&lt;li&gt;Not for multi-gigabyte streaming responses. &lt;code&gt;afterResponse&lt;/code&gt; buffers the body so plugins can inspect/rewrite it, which costs memory proportional to response size.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it gains in exchange: a codebase readable in an afternoon, plugins you edit and save instead of recompile, no cloud dependency to define policy, and bounded IPC cost (one warm Unix socket, patches instead of full payloads, body references instead of raw bytes).&lt;/p&gt;

&lt;p&gt;It's lean, not free. Four plugins across two phases is up to eight round trips per request. Sub-millisecond on a local socket. Wrong tool if you need per-byte stream processing at scale; right tool if you've got a handful of services, evolving edge policy, and a team that already writes TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  What stuck
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Abstraction is only good if it disappears in daily use.&lt;/strong&gt; The pieces that survived are the ones I stopped thinking about. The pieces that died were baked into Go: the &lt;code&gt;adminAuth()&lt;/code&gt;/&lt;code&gt;userAuth()&lt;/code&gt;/&lt;code&gt;chatAuth()&lt;/code&gt; prefix ladders that worked fine until they needed to change without a redeploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-process plugin systems are viable if state transfer is strict.&lt;/strong&gt; Patches, body references, frozen snapshots. Not optimization trivia; the actual precondition for Go and Deno cooperating without shared-memory bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure design shaped operability more than any plugin API did.&lt;/strong&gt; &lt;code&gt;failPolicy&lt;/code&gt;, hook timeouts, and the &lt;code&gt;reject&lt;/code&gt;/&lt;code&gt;onError&lt;/code&gt; split mattered more than feature count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Simple" means boundary discipline, not low line count.&lt;/strong&gt; Go owns the network. Deno owns execution. Plugins own policy proposals. Upstream owns business logic. Nobody reaches across.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;This wasn't built to be a product. It was a response to a system that stopped fitting in my head: three services, two auth models, one endpoint, and a gateway that grew a new &lt;code&gt;strings.HasPrefix&lt;/code&gt; branch every time something shipped.&lt;/p&gt;

&lt;p&gt;The goal was never to out-build Envoy or Kong. It was to build something I could forget existed until the day I needed to reason about it. At that point, reasoning about it should be easy: read the plugin files, read the config, follow one request through Go → IPC → Deno → upstream → back.&lt;/p&gt;

&lt;p&gt;If you're standing where I was (auth diverging, routing logic leaking into every handler), you might not need another service. You might need a place where policy can live without infecting everything else.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run ./cmd/conduit &lt;span class="nt"&gt;-config&lt;/span&gt; conduit.config.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;beforeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* policy */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;afterResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* transforms */&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;Full design-decision Q&amp;amp;A (why Deno over Node/Lua/WASM, scaling, "when is this the wrong choice," etc.) is in my site, not &lt;a href="https://a-thedeveloper.vercel.app/blog/conduit" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to Claude for helping with my wording, grammars...etc and it also added its own intro at the very top. I was so excited to share Conduit that I forgot to review it added a very LLM-generic intro for me that just got me flagged by Sloan.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>typescript</category>
      <category>go</category>
    </item>
    <item>
      <title>When Software Started Writing Software: A Developer’s History of AI</title>
      <dc:creator>Adam - The Developer ✨</dc:creator>
      <pubDate>Mon, 22 Jun 2026 08:06:29 +0000</pubDate>
      <link>https://dev.to/adamthedeveloper/when-software-started-writing-software-a-developers-history-of-ai-4p9n</link>
      <guid>https://dev.to/adamthedeveloper/when-software-started-writing-software-a-developers-history-of-ai-4p9n</guid>
      <description>&lt;p&gt;If you've shipped software in the last three years, you've probably watched your job description quietly rewrite itself. You went from writing code, to writing code &lt;em&gt;with&lt;/em&gt; an autocomplete, to writing code &lt;em&gt;with&lt;/em&gt; a collaborator, to increasingly writing a spec and watching an agent write, test, and ship the code for you.&lt;/p&gt;

&lt;p&gt;That didn't happen overnight. It's the latest chapter in a 70-year story that started with researchers trying to teach machines to play checkers. Let's walk through it, not as a dry timeline, but as the story of how "intelligence" kept getting redefined every time machines got good at the last definition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1. The Symbolic Era: Intelligence as Logic (1950s–1980s)&lt;/li&gt;
&lt;li&gt;2. Statistics Quietly Eats Symbols (1990s–2000s)&lt;/li&gt;
&lt;li&gt;3. Deep Learning Breaks the Ceiling (2012–2017)&lt;/li&gt;
&lt;li&gt;4. The Transformer and the Birth of "General-ish" Intelligence (2017–2022)&lt;/li&gt;
&lt;li&gt;5. From Chatbot to Coworker: The Agentic Turn (2023–Today)&lt;/li&gt;
&lt;li&gt;So What Changed, Really?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. The Symbolic Era: Intelligence as Logic (1950s–1980s)
&lt;/h2&gt;

&lt;p&gt;The founding bet of AI, made official at the 1956 Dartmouth Workshop, was simple and audacious: thought is computation. If you could represent knowledge as symbols and rules, and manipulate those symbols correctly, you'd get intelligence.&lt;/p&gt;

&lt;p&gt;This gave us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Logic Theorist (1956)&lt;/strong&gt;: proved mathematical theorems by searching through logical statements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ELIZA (1966)&lt;/strong&gt;: pattern-matched your sentences back at you and convinced people it understood them. The first chatbot, and the first time humans projected understanding onto a system that had none.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expert systems (1970s–80s)&lt;/strong&gt;: programs like &lt;a href="https://en.wikipedia.org/wiki/MYCIN" rel="noopener noreferrer"&gt;MYCIN&lt;/a&gt; encoded a domain expert's rules as &lt;code&gt;IF-THEN&lt;/code&gt; statements. MYCIN could diagnose bacterial infections about as well as a human specialist, using a few hundred hand-written rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was &lt;strong&gt;weak, narrow intelligence&lt;/strong&gt; in the most literal sense: a system that was a genius in one box and knew nothing outside it. The fatal flaw was scale, every rule had to be written by hand by a human expert. Knowledge didn't generalize, and it didn't learn from data. When funding agencies realized these systems couldn't handle the messiness of the real world, the money dried up. This was the &lt;strong&gt;first AI winter&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Statistics Quietly Eats Symbols (1990s–2000s)
&lt;/h2&gt;

&lt;p&gt;While "AI" was a dirty word in grant applications, a different idea was gaining ground: instead of &lt;em&gt;telling&lt;/em&gt; a machine the rules, show it examples and let it find the rules itself.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Neural networks&lt;/strong&gt; had existed since the 1950s (the perceptron) but were widely written off after Minsky and Papert detailed their mathematical limits in &lt;a href="https://en.wikipedia.org/wiki/Perceptrons_(book)" rel="noopener noreferrer"&gt;Perceptrons (1969)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backpropagation&lt;/strong&gt;, popularized in 1986, gave multi-layer networks a real way to learn and yet networks still mostly lost to simpler methods for another two decades. The algorithm existing wasn't enough; there wasn't enough labeled data or compute to let it show what it could do. It sat half-revived, a promising idea nobody could afford to run at scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support Vector Machines, decision trees, and Bayesian methods&lt;/strong&gt; dominated practical machine learning, spam filters, recommendation engines, fraud detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IBM's Deep Blue beat Garry Kasparov in 1997&lt;/strong&gt;, but largely through brute-force search over chess positions, not learning. Still narrow, still impressive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This era's intelligence was &lt;em&gt;statistical&lt;/em&gt; rather than &lt;em&gt;logical&lt;/em&gt;: pattern recognition over labeled data. It worked well on narrow, well-defined tasks but needed mountains of hand-labeled examples and feature engineering done by humans. The "intelligence" was still mostly in the human designing the features the model was just fitting a curve.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Deep Learning Breaks the Ceiling (2012–2017)
&lt;/h2&gt;

&lt;p&gt;The ingredients for the next leap had been sitting around for a decade: more data (the internet), more compute (GPUs originally built for video games), and algorithmic tricks for training deeper networks without them collapsing into noise. None of this looked inevitable at the time. Most of the field had moved on from neural networks; deep nets were a fringe bet kept alive by a small number of labs who kept getting told they were wasting their careers.&lt;/p&gt;

&lt;p&gt;The spark was &lt;strong&gt;&lt;a href="https://papers.nips.cc/paper_files/paper/2012/hash/c399862d3b9d6b76c8436e924a68c45b-Abstract.html" rel="noopener noreferrer"&gt;AlexNet (2012)&lt;/a&gt;&lt;/strong&gt;, a deep convolutional neural network that crushed the ImageNet image-classification competition, slashing the error rate compared to the next-best approach. That one result told the field something important: stack enough layers, feed them enough data, and the network finds its own features, no human feature engineering required. It wasn't a smooth continuation of the field's direction; it was closer to a coup. Within a couple of years, techniques that had been a punchline became the default starting point for almost every computer vision paper.&lt;/p&gt;

&lt;p&gt;What followed was a five-year sprint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://arxiv.org/abs/1301.3781" rel="noopener noreferrer"&gt;Word2Vec (2013)&lt;/a&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;a href="https://aclanthology.org/D14-1162/" rel="noopener noreferrer"&gt;GloVe (2014)&lt;/a&gt;&lt;/strong&gt;: words became vectors, and "meaning" became something you could do arithmetic on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://arxiv.org/abs/1406.2661" rel="noopener noreferrer"&gt;Generative Adversarial Networks (2014)&lt;/a&gt;&lt;/strong&gt;: two networks competing, one generating fakes, one detecting them, together learning to produce eerily convincing images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nature.com/articles/nature16961" rel="noopener noreferrer"&gt;AlphaGo (2016)&lt;/a&gt;&lt;/strong&gt;: beat the world's best Go player (the board game, not &lt;code&gt;go run main.go&lt;/code&gt; energy) using deep learning plus reinforcement learning plus tree search, on a game once thought too intuitive for machines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these was still task-bound, a vision model couldn't write a sentence, a Go-playing model couldn't recognize a cat — but the skill inside each one was no longer handed to it by a human; the model discovered its own representation of the problem. That shift in mechanism, more than any single result, is what made the next jump possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Transformer and the Birth of "General-ish" Intelligence (2017–2022)
&lt;/h2&gt;

&lt;p&gt;In 2017, a Google paper titled &lt;a href="https://arxiv.org/abs/1706.03762" rel="noopener noreferrer"&gt;"Attention Is All You Need"&lt;/a&gt; introduced the &lt;strong&gt;transformer architecture&lt;/strong&gt;. Instead of processing text sequentially like older recurrent networks, transformers let every word attend to every other word at once. It was a better way to model sequences and it turned out to scale beautifully.&lt;/p&gt;

&lt;p&gt;That architectural choice, combined with the realization that you could pretrain a single giant model on a huge slice of the internet and then adapt it to almost any language task, produced the GPT lineage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPT (2018) → GPT-2 (2019) → GPT-3 (2020)&lt;/strong&gt;: each generation showed that scaling up parameters and data kept producing qualitatively new abilities, not just marginal accuracy gains. GPT-3 could write code, translate, summarize, and hold a conversation, despite never being explicitly trained to do any of those things.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instruction tuning and RLHF (2021–2022)&lt;/strong&gt;: raw language models predict the next token; they don't inherently want to be &lt;em&gt;helpful&lt;/em&gt;. Techniques like reinforcement learning from human feedback turned raw next-token predictors into assistants that follow instructions and refuse harmful ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://chat.openai.com/" rel="noopener noreferrer"&gt;ChatGPT (November 2022)&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://twitter.com/sama/status/1598038815599661056" rel="noopener noreferrer"&gt;Sam Altman announced it on X&lt;/a&gt; with the energy of a minor release: "today we launched ChatGPT. try talking with it here." Understated copy for the moment the old era ended and a new history began. Research left the lab; your non-technical relatives showed up. A hundred million users in two months. The Turing Test stopped being a thought experiment and became something people ran into accidentally over breakfast.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the point where AI stopped meaning "deep in one box, blank everywhere else" and started meaning &lt;em&gt;compositional&lt;/em&gt;: a single set of weights that could combine skills it was never explicitly trained to combine. One model could write a sonnet, debug Python, and explain the sonnet's meter not because it was three different systems, but because language turned out to be a surprisingly good universal interface to a huge range of human tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. From Chatbot to Coworker: The Agentic Turn (2023–Today)
&lt;/h2&gt;

&lt;p&gt;A language model that just answers questions is a powerful autocomplete. The next phase of the story is about giving that model &lt;strong&gt;hands&lt;/strong&gt;: the ability to call tools, write and execute code, browse the web, remember state across steps, and chain its own reasoning into multi-step plans.&lt;/p&gt;

&lt;p&gt;A few threads converged here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool use / function calling&lt;/strong&gt; let models stop just describing actions and start taking them, querying a database, hitting an API, running a calculation, instead of guessing at the answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval-augmented generation (RAG)&lt;/strong&gt; gave models access to information beyond their training data, grounding answers in real documents instead of frozen memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chain-of-thought and reasoning models&lt;/strong&gt; showed that letting a model "think out loud" before answering and eventually training it specifically to reason longer on hard problems, produced dramatically better results on math, logic, and multi-step planning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentic frameworks&lt;/strong&gt; stitched these into loops: plan → act → observe → revise. Wrapped in orchestration code, retry logic, and well-designed tools, a model could chase a goal across many steps instead of answering once and stopping. Left alone long enough, it still drifts or takes wrong turns, the scaffolding exists to catch that. "Agent" describes a system, not a self-sufficient mind.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-agent orchestration&lt;/strong&gt;: models that spin up &lt;em&gt;other&lt;/em&gt; model instances to parallelize work, each with a narrower role, then combine results. The specialist of the symbolic era is back, except now it's a transformer playing a role inside a swarm coordinated by another transformer, instead of a human-written rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the world a developer in 2026 actually lives in. Code isn't just suggested line-by-line; it's planned, written across multiple files, tested, and debugged in a loop that needs less hands-on steering than it used to, though still real review, real guardrails, and real human judgment about when to trust the output. The same pattern shows up outside coding: research agents that browse, synthesize, and cite sources across dozens of pages; operations agents that read a ticket, check a calendar, and draft a response; design agents that take a brief and return a working prototype. None of this is autonomy in the strong sense yet. It's interactive capability, a model in a loop with tools and a feedback signal — and it's powerful precisely because of how tightly that loop is engineered, not in spite of it.&lt;/p&gt;

&lt;p&gt;Easy to miss when you're staring at capability charts: every jump here was also an economics story. Expert systems died because expert time didn't scale. Statistical ML rode cheap labeled data and storage. Deep learning rode gaming GPUs. LLMs rode internet-scale data and transformer parallelism. Agents are having their moment because inference got cheap enough to run a model in a loop hundreds of times per task without laughing. The recurring question isn't "can we build it?" but it's "can we afford to run it enough times to be useful?" That bottleneck moved; it didn't disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  So What Changed, Really?
&lt;/h2&gt;

&lt;p&gt;If you zoom out, the history of AI is a story about &lt;em&gt;where the intelligence lives&lt;/em&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Era&lt;/th&gt;
&lt;th&gt;Where the "smarts" lived&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Symbolic AI&lt;/td&gt;
&lt;td&gt;In rules a human expert wrote by hand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Statistical ML&lt;/td&gt;
&lt;td&gt;In features a human engineer chose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deep learning&lt;/td&gt;
&lt;td&gt;In representations the model learned itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large language models&lt;/td&gt;
&lt;td&gt;In patterns learned from most of the public internet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agentic systems&lt;/td&gt;
&lt;td&gt;In the model's own planning, tool use, and self-correction across time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each era didn't replace the last so much as absorb it. Today's agents still do statistical pattern matching under the hood; they still occasionally fail in the brittle, overconfident ways the old expert systems did, just less often and less predictably.&lt;/p&gt;

&lt;p&gt;If you compress the five eras above, there are really only three discontinuities that mattered:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hand-coded intelligence&lt;/strong&gt;: rules a human wrote (symbolic AI).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learned representations&lt;/strong&gt;: patterns a model found in data, with steadily less human-chosen structure (statistical ML → deep learning → LLMs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive systems&lt;/strong&gt;: models that act, observe consequences, and revise, instead of just outputting a single answer (agents).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The five-era version tells a better story; the three-version compression is what actually changed underneath.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shift one: who writes the rules — human or data. &lt;/li&gt;
&lt;li&gt;Shift two: passive answer vs. active loop. Most of "AI got so much better" traces to one of those.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a straight march toward AGI. It's researchers repeatedly asking "what if the machine decided this part?" and finding that worked, once the economics finally allowed it.&lt;/p&gt;

&lt;p&gt;Whether the next chapter is "agents that reliably run entire businesses" or "another winter while the hype outpaces the engineering" is genuinely an open question and depending on who you ask, both are already happening at once.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>There Is No Perfect Solution in Software Development: Every Decision is a Tradeoff</title>
      <dc:creator>Adam - The Developer ✨</dc:creator>
      <pubDate>Mon, 15 Jun 2026 06:22:48 +0000</pubDate>
      <link>https://dev.to/adamthedeveloper/there-is-no-perfect-solution-in-software-development-every-decision-is-a-tradeoff-136l</link>
      <guid>https://dev.to/adamthedeveloper/there-is-no-perfect-solution-in-software-development-every-decision-is-a-tradeoff-136l</guid>
      <description>&lt;p&gt;Most bad decisions in software engineering aren't made because the engineer chose wrong between two clear options. They're made because the engineer didn't realize they were making a choice at all.&lt;/p&gt;

&lt;p&gt;You optimized for readability without noticing that particular loop runs a million times per second. You built for perfect flexibility without realizing you'd never actually need it. You shipped fast and acknowledged the technical debt—until the debt became someone else's problem, and by then it was too late to refactor.&lt;/p&gt;

&lt;p&gt;The pattern: teams usually aren't choosing between two good options. They're choosing between one safe default and one speculative optimization, and calling it a tradeoff to feel better about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What Every Engineer Needs to Know About Tradeoffs&lt;/li&gt;
&lt;li&gt;
Classic Tradeoffs You'll Face

&lt;ul&gt;
&lt;li&gt;Performance vs. Readability&lt;/li&gt;
&lt;li&gt;Flexibility vs. Simplicity&lt;/li&gt;
&lt;li&gt;Speed to Market vs. Technical Debt&lt;/li&gt;
&lt;li&gt;Scalability vs. Cost&lt;/li&gt;
&lt;li&gt;Security vs. Convenience&lt;/li&gt;
&lt;li&gt;The CAP Theorem: What It Actually Means&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The Pattern: How Good Decisions Actually Get Made&lt;/li&gt;
&lt;li&gt;How to Make Better Tradeoff Decisions&lt;/li&gt;
&lt;li&gt;The Mark of Experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Every Engineer Needs to Know About Tradeoffs
&lt;/h2&gt;

&lt;p&gt;There is no perfect solution. Every architecture, every algorithm, every design decision buys you something at the cost of something else. This isn't pessimism, it's the foundation of good engineering judgment.&lt;/p&gt;

&lt;p&gt;But here's the catch: not all tradeoffs are 50/50. In practice, experienced engineers often default heavily one way and only deviate with evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Classic Tradeoffs You'll Face
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Performance vs. Readability
&lt;/h3&gt;

&lt;p&gt;Here's what usually wins: readability. Unless you have a proven bottleneck backed by profiling data, optimize for clarity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Faster but harder to maintain&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&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;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;length&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;offset&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Slower but immediately clear&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&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="nx"&gt;source&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&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;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second version is probably fine. Premature optimization is still the root of all evil. The first example should only exist if: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;you've profiled&lt;/li&gt;
&lt;li&gt;you found this function is actually slow&lt;/li&gt;
&lt;li&gt;you've measured that the optimization makes a meaningful difference&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;All three conditions are rarer than you'd think.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Flexibility vs. Simplicity
&lt;/h3&gt;

&lt;p&gt;"Flexibility" often looks like smart future-proofing until you realize you'll never actually need it.&lt;/p&gt;

&lt;p&gt;You build a generic plugin system because "we might need it." You abstract everything into interfaces because "we'll probably want to swap implementations." You create configuration options for scenarios that never materialize. Meanwhile, your simple code that handles exactly one thing is getting buried under layers of generality.&lt;/p&gt;

&lt;p&gt;Ship the simple thing. If you actually need multiple use cases later, refactoring from concrete to generic is almost always easier than refactoring from over-engineered to usable. The exception: if you're building a library or platform that multiple teams depend on, flexibility becomes a real requirement, not speculation.&lt;/p&gt;

&lt;p&gt;Most over-engineering is just ego disguised as foresight.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed to Market vs. Technical Debt
&lt;/h3&gt;

&lt;p&gt;Ship in two weeks with known compromises? Or spend two months building something maintainable? &lt;/p&gt;

&lt;p&gt;Both answers are right in different contexts. A startup with three months of runway and a saturated market needs speed. A fintech system handling billions in transactions needs stability. There's no universal answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scalability vs. Cost
&lt;/h3&gt;

&lt;p&gt;Premature scalability is usually just waste unless you have strong signals of growth.&lt;/p&gt;

&lt;p&gt;You can architect for 100x your current traffic and be right. You can also architect for today's load and be right. The difference is that one cost money now, and the other might cost money later. Most teams choose wrong because they're optimizing for an imagined future instead of the constraints they actually face.&lt;/p&gt;

&lt;p&gt;The right call: scale when you have evidence that growth is coming, not because it &lt;em&gt;might&lt;/em&gt; happen. Growth that doesn't materialize? You've spent months and money on infrastructure that will never be used. Growth that does materialize and catches you off-guard? That's painful, but you'll fix it. The fix is usually cheaper than over-engineering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you are unsure whether you need scalability, you don't.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Security vs. Convenience
&lt;/h3&gt;

&lt;p&gt;Require 2FA, complex passwords, and proof of identity? That's more secure. Users will also hate you.&lt;/p&gt;

&lt;p&gt;Frictionless auth is delightful for users and a security nightmare. You're balancing two legitimate concerns.&lt;/p&gt;

&lt;h3&gt;
  
  
  The CAP Theorem: What It Actually Means
&lt;/h3&gt;

&lt;p&gt;Everything above applies to a single application you control end to end. This one is different — it belongs to &lt;strong&gt;distributed systems&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A distributed system is software spread across multiple machines that coordinate over a network. Your API talking to one database is distributed in the loose sense, but CAP really starts to matter when the same data lives in more than one place: a primary with read replicas, a Redis cluster, a multi-region deployment. The moment you have copies of data on separate nodes, connected by a network that can fail, you inherit tradeoffs you do not get on a single server.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;CAP theorem&lt;/strong&gt; names the central one. The textbook version says: "Pick any two of Consistency, Availability, and Partition Tolerance."&lt;/p&gt;

&lt;p&gt;Here's what actually happens: In any real distributed system, &lt;strong&gt;partition tolerance is not optional&lt;/strong&gt;. Network failures will happen. So the real choice is between &lt;strong&gt;Consistency and Availability when the network breaks&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CP systems&lt;/strong&gt; fail safely when they can't guarantee consistency. A banking app might do this, it's better to be down than serve wrong balances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AP systems&lt;/strong&gt; stay up and serve whatever data they have. A social feed might do this, slightly stale likes are better than a 503.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code doesn't need to be complicated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CP: fail rather than serve wrong data&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT balance FROM accounts WHERE id = $1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// If database is unreachable, the request fails. That's the design.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// AP: serve cached data with a staleness flag&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;stale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT balance FROM accounts WHERE id = $1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&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="na"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Partition: return whatever we have cached&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;stale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Choose based on what breaks is worse: being wrong (CP) or being unavailable (AP).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern: How Good Decisions Actually Get Made
&lt;/h2&gt;

&lt;p&gt;Once you recognize that a tradeoff exists, you're already halfway to making a good decision. You can be intentional about what you're trading and why. You can explain it to your team. You can revisit it later if your constraints change.&lt;/p&gt;

&lt;p&gt;Without that awareness? You end up optimizing for readability on a loop that runs a million times per second. You build flexibility you'll never need. You ship fast and defer the cost to future-you.&lt;/p&gt;

&lt;p&gt;Here's what separates functional teams from dysfunctional ones: functional teams argue about &lt;em&gt;which&lt;/em&gt; tradeoff they're making. Dysfunctional teams don't realize there's a choice at all, and they compound the costs by pretending it was inevitable.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Make Better Tradeoff Decisions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Name the tradeoff explicitly&lt;/strong&gt;&lt;br&gt;
Don't say "should we use Redis?" Say "are we optimizing for speed or operational simplicity?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Understand your constraints&lt;/strong&gt;&lt;br&gt;
What actually matters in your context? A library used by millions needs different tradeoffs than an internal tool used by five people.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Make it reversible if possible (but know the real limits)&lt;/strong&gt;&lt;br&gt;
Refactoring from concrete to generic is usually easier than the reverse. Local code changes are easy to undo. But reversibility has hard boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;True reversibility&lt;/strong&gt;: Internal implementation details, local code scope, nothing that touches user-facing behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;False reversibility&lt;/strong&gt;: Anything that becomes business-critical, anything customers build workflows around, anything that spreads to multiple teams. A quick hack that ships and immediately gets embedded in product behavior doesn't refactor cleanly six months later. The team has built processes around it. Customers depend on it. Other engineers have written code that relies on it. Reversibility was an illusion from the moment the code touched production.&lt;/p&gt;

&lt;p&gt;Know which category your decision falls into before you ship it as temporary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Document your reasoning&lt;/strong&gt;&lt;br&gt;
Future you (and your teammates) will thank you. "We chose simple over performant here because X" is gold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Revisit your assumptions&lt;/strong&gt;&lt;br&gt;
Your constraints change. What was the right tradeoff six months ago might be wrong now. That's not failure—that's growth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mark of Experience
&lt;/h2&gt;

&lt;p&gt;Here's what changes as you get better at this:&lt;/p&gt;

&lt;p&gt;Seniors aren't less stressed, they're stressed about the right things. They don't waste energy trying to eliminate uncertainty. They can't. Uncertainty is part of the job. Instead, they focus on &lt;strong&gt;reducing risk and keeping decisions reversible where possible&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They ask good questions: "What happens if we're wrong about this?" "What would make us want to undo this decision?" "Do we have evidence this is actually a bottleneck?" They make explicit choices, document the tradeoffs, and move forward without second-guessing.&lt;/p&gt;

&lt;p&gt;The skill isn't knowing everything. It's &lt;strong&gt;making conscious tradeoffs and living with the consequences&lt;/strong&gt; without pretending there was ever a perfect choice.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>software</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The Hidden Machinery Behind Background Jobs</title>
      <dc:creator>Adam - The Developer ✨</dc:creator>
      <pubDate>Tue, 09 Jun 2026 07:27:48 +0000</pubDate>
      <link>https://dev.to/adamthedeveloper/the-hidden-machinery-behind-background-jobs-1g5a</link>
      <guid>https://dev.to/adamthedeveloper/the-hidden-machinery-behind-background-jobs-1g5a</guid>
      <description>&lt;p&gt;This will be a boring article today, but I wanted to write it after talking to a developer who genuinely believed background jobs are the key to faster software.&lt;/p&gt;

&lt;p&gt;They’re not hard to add. They’re easy to write, easy to deploy, and deceptively minimal, depending on how much complexity you’re willing to ignore elsewhere.&lt;/p&gt;

&lt;p&gt;Let’s talk about something most of us use every day but rarely think about.&lt;/p&gt;

&lt;p&gt;You enqueue a job (or, in my language, “drop something into a queue”). Your API returns &lt;code&gt;202 Accepted&lt;/code&gt; in 3 milliseconds. It feels good, responsive, non-blocking, event-driven. All the right words.&lt;/p&gt;

&lt;p&gt;But between &lt;code&gt;queue.enqueue()&lt;/code&gt; and a worker picking it up, a lot happens across your app, the OS, the network stack, and the broker. Most of it is invisible. All of it matters when something breaks at 2am.&lt;/p&gt;

&lt;p&gt;This is that journey, layer by layer. By the end, you’ll have a clearer picture of what “fast” and “non-blocking” actually mean when a queue is involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What We Think Is Happening&lt;/li&gt;
&lt;li&gt;Layer 1: Your Application Code&lt;/li&gt;
&lt;li&gt;Layer 2: The Network Stack and the Kernel&lt;/li&gt;
&lt;li&gt;
Layer 3: Inside the Broker

&lt;ul&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;RabbitMQ&lt;/li&gt;
&lt;li&gt;Kafka&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Layer 4: The Event Loop, and What Is Actually Powering It

&lt;ul&gt;
&lt;li&gt;How It Evolved&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Layer 5: The Worker, Acknowledgments, and What "At Least Once" Actually Means&lt;/li&gt;
&lt;li&gt;Layer 6: The Things That Quietly Go Wrong&lt;/li&gt;
&lt;li&gt;What "Fast" Actually Means&lt;/li&gt;
&lt;li&gt;The Full Journey, Summarized&lt;/li&gt;
&lt;li&gt;Closing Thoughts&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What We Think Is Happening
&lt;/h2&gt;

&lt;p&gt;Most of us carry a mental model that looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Producer  →  [ Queue ]  →  Worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean. Simple. And not wrong, exactly, just kinda incomplete. The full picture looks more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Producer
  → serialize payload to bytes
  → write bytes to TCP socket
  → kernel buffers → NIC → wire
  → broker receives bytes
  → broker deserializes, routes, stores message
  → broker sends ACK to producer
  → worker connects and subscribes
  → broker reads message from storage
  → broker sends to worker over TCP
  → worker deserializes → your function runs
  → worker sends ACK back to broker
  → broker marks message delivered, removes it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of those arrows is real work. Let's walk through each one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 1: Your Application Code
&lt;/h2&gt;

&lt;p&gt;Here is a fairly typical job enqueue in TypeScript using BullMQ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Queue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bullmq&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailQueue&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;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emails&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6379&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;emailQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sendWelcome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;usr_abc123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;welcome-v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Job enqueued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Returns in ~3ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;await&lt;/code&gt; resolves fast. But a few things happened before it did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serialization.&lt;/strong&gt; Your payload object got converted to a flat sequence of bytes. In BullMQ's case, this is JSON. Every nested field, every string, every number, reduced to a string that can cross a process boundary. Your clean TypeScript object does not survive the trip; its data does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Message wrapping.&lt;/strong&gt; The payload gets wrapped in an envelope before it goes anywhere. Job ID, timestamp, retry count, delay, priority, queue name. Depending on the broker, this metadata overhead can be comparable in size to your actual payload for small messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connection reuse.&lt;/strong&gt; BullMQ does not open a new TCP connection to Redis for every job. It keeps a pool of connections alive. Enqueuing borrows one of those connections, writes the message bytes, and returns it. If every connection in the pool is busy, your &lt;code&gt;await&lt;/code&gt; waits here. Not in some clean async queue, but blocked, waiting for a connection to free up.&lt;/p&gt;

&lt;p&gt;So even before a byte has left your machine, your "fast, non-blocking enqueue" has already done serialization, object allocation, pool management, and potentially some waiting. Still fast. Just not free.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 2: The Network Stack and the Kernel
&lt;/h2&gt;

&lt;p&gt;When the library writes to the socket, your application hands control to the OS kernel. This boundary has a name: a &lt;strong&gt;syscall&lt;/strong&gt;. Roughly speaking, it is your code saying "I need to do something I am not allowed to do myself" and the kernel taking over.&lt;/p&gt;

&lt;p&gt;On Linux, that write looks like this at the system level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// What your queue library is ultimately triggering&lt;/span&gt;
&lt;span class="kt"&gt;ssize_t&lt;/span&gt; &lt;span class="n"&gt;bytes_written&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message_length&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 happen in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your bytes are &lt;strong&gt;copied from your process's memory into the kernel's TCP send buffer&lt;/strong&gt;. This is a separate memory region. Once copied, your code cannot touch those bytes anymore.&lt;/li&gt;
&lt;li&gt;The kernel's TCP stack breaks the data into segments, adds sequence numbers and checksums, and hands them to the network driver.&lt;/li&gt;
&lt;li&gt;The NIC DMA-transfers the data to its own buffer and puts it on the wire.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;the important part here: &lt;code&gt;write()&lt;/code&gt; returns before the data is confirmed received by the broker. It returns when the kernel has accepted the bytes. The actual network transfer happens asynchronously behind your process's back.&lt;/p&gt;

&lt;p&gt;When your enqueue call returns quickly, you have confirmed the kernel accepted your bytes. Not that the broker received them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 3: Inside the Broker
&lt;/h2&gt;

&lt;p&gt;This is where things differ meaningfully depending on which broker you are using. Let's look at three common ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redis
&lt;/h3&gt;

&lt;p&gt;Redis works entirely in memory. When the message arrives over TCP, Redis's event loop reads the bytes from the socket, deserializes the command, and modifies an in-memory data structure. For a BullMQ job, this is an entry in a Redis sorted set or list, depending on job type.&lt;/p&gt;

&lt;p&gt;No disk is touched in the default path. This is why Redis is fast. It is also the catch: a crash or restart with default settings means everything in memory is gone. Your enqueued jobs, vanished, with no error and no log entry.&lt;/p&gt;

&lt;p&gt;Redis does support persistence (RDB snapshots, AOF logging), but these are asynchronous by default. There is always a small window between when you enqueue and when that data is safely written to disk. That window is your risk to size and accept consciously.&lt;/p&gt;

&lt;h3&gt;
  
  
  RabbitMQ
&lt;/h3&gt;

&lt;p&gt;RabbitMQ has a more layered approach to storage. A message arrives via AMQP, gets deserialized, passes through the exchange's routing logic (which queue does this message belong to, based on binding keys and patterns), and lands in the target queue.&lt;/p&gt;

&lt;p&gt;Each message carries more weight than it looks. A payload of 1KB actually occupies roughly 2KB in RabbitMQ's memory once internal metadata is factored in. The internal database keeps an in-memory copy of all data, even on disk nodes.&lt;/p&gt;

&lt;p&gt;For persistent messages, RabbitMQ writes to disk before sending the ACK back to your producer. If memory pressure climbs above a configurable threshold, RabbitMQ starts paging messages to disk. If it keeps climbing, RabbitMQ does something that surprises most people who have not read the docs: it &lt;strong&gt;blocks publishers&lt;/strong&gt;. Stops reading from their TCP sockets entirely. Your producer's &lt;code&gt;write()&lt;/code&gt; call hangs. Your enqueue appears to freeze. The queue is not broken. It is telling you it is full and it cannot accept more until workers catch up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kafka
&lt;/h3&gt;

&lt;p&gt;Kafka treats the problem differently at a fundamental level. A message is not stored in a data structure in memory. It is &lt;strong&gt;appended sequentially to a log file on disk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sequential disk writes on modern SSDs and NVMe drives are extremely fast because they avoid seek times entirely. This is where much of Kafka's throughput comes from. Consumer progress is tracked as a simple integer, an offset into that log file. To read the next message, a consumer increments its offset and fetches. To replay messages, it resets the offset.&lt;/p&gt;

&lt;p&gt;This model means multiple independent consumers can read the same data, message replay is trivially possible, and retention is time-based rather than acknowledgment-based. It also means Kafka is disk-heavy and its throughput degrades with larger messages. NVMe storage is not optional for serious Kafka deployments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 4: The Event Loop, and What Is Actually Powering It
&lt;/h2&gt;

&lt;p&gt;This is the part that ties everything together. Your broker, your async worker, your web framework: all of them are built on the same foundational mechanism.&lt;/p&gt;

&lt;p&gt;To understand it, we need to understand the problem it exists to solve.&lt;/p&gt;

&lt;p&gt;A server that handles connections by blocking on a socket read can only handle one connection per thread. To handle 10,000 concurrent connections, you would need 10,000 threads. A thread typically needs 1 to 8 MB of stack space. That is a lot of RAM spent on processes that are almost entirely idle, just waiting for data.&lt;/p&gt;

&lt;p&gt;The solution is &lt;strong&gt;I/O multiplexing&lt;/strong&gt;: a single thread monitors thousands of file descriptors and reacts only when one actually has data.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Evolved
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;select()&lt;/code&gt; was Unix's first answer. You pass the kernel a bitmask of file descriptors to watch. The kernel scans every one of them on every call to see which are ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// kernel scans ALL of these on every single call&lt;/span&gt;
&lt;span class="n"&gt;fd_set&lt;/span&gt; &lt;span class="n"&gt;read_fds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;FD_ZERO&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;read_fds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;FD_SET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock1&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;read_fds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;FD_SET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock2&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;read_fds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ... up to 1024 max&lt;/span&gt;

&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_fd&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;read_fds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&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;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// returns, then you scan the whole bitmask yourself to find who is ready&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is O(n) per call, where n is your total number of watched file descriptors. With thousands of connections, this burns meaningful CPU just on scanning. Plus there is a hard ceiling of 1,024 descriptors. Fine for an early-nineties Unix workstation. Not fine for anything built in the last twenty years.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;poll()&lt;/code&gt; removed the 1,024 limit but kept the linear scan. A step forward, not a solution.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;epoll()&lt;/code&gt; is the modern Linux answer. The philosophy shifts: instead of asking the kernel "which of these are ready right now?", you register your file descriptors once and say "notify me when something changes."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// create an epoll instance&lt;/span&gt;
&lt;span class="c1"&gt;// the kernel allocates a red-black tree and a ready list internally&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;epfd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;epoll_create1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// register a file descriptor once&lt;/span&gt;
&lt;span class="c1"&gt;// kernel inserts it into the red-black tree — O(log n)&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;epoll_event&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EPOLLIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// wake me when data is available to read&lt;/span&gt;
&lt;span class="n"&gt;ev&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;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket_fd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;epoll_ctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EPOLL_CTL_ADD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket_fd&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;ev&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The event loop — this is what Node.js, Redis, Nginx all do at their core&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;epoll_event&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MAX_EVENTS&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// process sleeps here, consuming zero CPU&lt;/span&gt;
    &lt;span class="c1"&gt;// Kernel wakes it when at least one FD has data&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;epoll_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MAX_EVENTS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// n is only the count of *ready* file descriptors, not total registered ones&lt;/span&gt;
    &lt;span class="k"&gt;for&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;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&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;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="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&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;When &lt;code&gt;epoll_create1()&lt;/code&gt; is called, the kernel allocates two internal structures: a &lt;strong&gt;red-black tree&lt;/strong&gt; for all registered file descriptors (insertion and lookup in O(log n)), and a &lt;strong&gt;linked list&lt;/strong&gt; for descriptors that currently have data ready.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;epoll_wait()&lt;/code&gt; is called, the process is suspended and consumes no CPU.&lt;/p&gt;

&lt;p&gt;When a network packet arrives for one of your registered sockets, the NIC fires an interrupt. The kernel processes the incoming data and a &lt;strong&gt;callback&lt;/strong&gt; moves that socket's file descriptor from the red-black tree to the ready list. &lt;code&gt;epoll_wait()&lt;/code&gt; wakes up and returns only the descriptors that are ready.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;select&lt;/code&gt;, the kernel scans your entire list on every call regardless of how many are actually ready. With &lt;code&gt;epoll&lt;/code&gt;, the kernel does the bookkeeping internally and only tells you about the ones you care about. For a server with 10,000 connected clients where 3 of them sent data right now, &lt;code&gt;select&lt;/code&gt; scans 10,000, &lt;code&gt;epoll&lt;/code&gt; returns 3.&lt;/p&gt;

&lt;p&gt;This is why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single-threaded Node.js process can handle tens of thousands of concurrent connections.&lt;/li&gt;
&lt;li&gt;Redis processes millions of operations per second on one thread.&lt;/li&gt;
&lt;li&gt;Nginx serves enormous traffic without spawning thousands of OS threads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your event-driven architecture is, at its foundation, a process sleeping in the kernel, waiting to be woken by a network interrupt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 5: The Worker, Acknowledgments, and What "At Least Once" Actually Means
&lt;/h2&gt;

&lt;p&gt;Below is a TypeScript worker consuming from the same queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Worker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bullmq&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&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;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emails&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// Ii we reach here without throwing, BullMQ sends the ACK automatically&lt;/span&gt;
  &lt;span class="c1"&gt;// if we throw, BullMQ sends a NACK and schedules a retry&lt;/span&gt;

&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6379&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// how many jobs this worker handles in parallel&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple from the outside. Under the hood, the worker process opened a TCP connection to Redis, subscribed to the queue, and entered its own event loop. From the OS's perspective, it is a sleeping process blocked on a socket read, parked in the kernel's wait queue, consuming no CPU.&lt;/p&gt;

&lt;p&gt;When Redis has a job ready, it sends data over TCP. The kernel receives the bytes, the socket becomes readable, the wait queue callback fires, the process wakes, BullMQ deserializes the job data, and your function is called.&lt;/p&gt;

&lt;p&gt;When your function returns without throwing, BullMQ sends an ACK to Redis. Redis marks the job complete. If your function throws before that ACK is sent, the job stays in an active state. After a configurable timeout, it gets requeued.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;at-least-once delivery&lt;/strong&gt;. Your function will be called at minimum once. It may be called more than once. Here is why that matters in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Unsafe: if this crashes after the charge but before the receipt,&lt;/span&gt;
&lt;span class="c1"&gt;// the job retries and the customer gets charged twice&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processPaymentJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;chargeCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendReceipt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Safer: the idempotency key ensures a duplicate call&lt;/span&gt;
&lt;span class="c1"&gt;// is treated as the same operation, not a new one&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processPaymentJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;chargeCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Idempotency is not a nice-to-have in a queue-based system. &lt;/span&gt;
    &lt;span class="c1"&gt;// It is a correctness requirement for any handler that makes side effects in the outside world.&lt;/span&gt;
    &lt;span class="na"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendReceipt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Layer 6: The Things That Quietly Go Wrong
&lt;/h2&gt;

&lt;p&gt;Now that we can see the full path, these failure modes are a lot less surprising.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The queue fills up and nobody is watching.&lt;/strong&gt; Workers fall behind. Messages pile up. RabbitMQ reaches its memory threshold and stops accepting new messages. Your producers' TCP writes start hanging. Your API response times climb. Engineers look for a slow database query. Add queue depth and consumer lag to your dashboards and save everyone a confusing hour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data loss because persistence was not configured.&lt;/strong&gt; Redis with default settings holds everything in memory. A clean deployment restart flushes the queue. No error is thrown because nothing went wrong from the broker's perspective. The data simply stops existing. Enable AOF persistence and understand the durability tradeoff you are making before your first production incident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The dead letter queue fills up unnoticed.&lt;/strong&gt; A dead letter queue is where failed jobs end up after repeated retries. It’s easy to ignore because nothing actively lives there by default. But that’s exactly the problem. If you don’t build a consumer for it, and don’t monitor its depth, you can quietly accumulate months of failed work without realizing it. By the time you notice, you’re just gonna be digging through history of dead queues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Duplicate processing because the handler is not idempotent.&lt;/strong&gt; A worker completes its work, then crashes before ACKing. The broker redelivers. The external side effect runs twice. This has caused real double-charges, duplicate emails, and inconsistent records at real companies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A blocking call inside an async worker.&lt;/strong&gt; If your handler makes a synchronous blocking call, it stalls the event loop for the duration. No other messages are processed. Throughput drops to the speed of the slowest synchronous operation. Use async drivers for databases, HTTP clients, and file I/O inside workers.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "Fast" Actually Means
&lt;/h2&gt;

&lt;p&gt;When we say a queue makes an endpoint faster, we mean something specific: &lt;strong&gt;the HTTP response no longer waits for the work to finish&lt;/strong&gt;. The work itself takes the same amount of time. Often more, when you factor in serialization, network round trips to the broker, storage, retrieval, deserialization, and worker execution.&lt;/p&gt;

&lt;p&gt;What changed is when the user gets their response. Not how long the work takes.&lt;/p&gt;

&lt;p&gt;This is genuinely valuable. Decoupling user-facing latency from background work is a real improvement. But it is a different thing than making work faster, and conflating the two leads to queues in places that add complexity without benefit, and missing queues in places where the decoupling would genuinely help.&lt;/p&gt;

&lt;p&gt;A queue earns its place when the work is not needed to form the response, the work is slow enough that waiting feels bad, the work is safe to retry, and the producer and consumer need to scale independently.&lt;/p&gt;

&lt;p&gt;A queue adds complexity without clear payoff when you need the result to respond, your latency budget is too tight for broker round trips, or the operation is fast enough that doing it inline is simpler and easier to observe.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full Journey, Summarized
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your TypeScript code
  ↓  JSON.stringify → bytes allocated in your process memory
  ↓  write() syscall → bytes copied to kernel TCP send buffer
  ↓  kernel TCP stack → NIC DMA transfer → wire

Broker process (Redis / RabbitMQ / Kafka)
  ↓  epoll_wait() wakes on incoming socket data
  ↓  deserialize bytes → route → store (RAM / disk / log file)
  ↓  ACK sent back to producer over TCP

Worker process
  ↓  sleeping in kernel wait queue, zero CPU consumption
  ↓  broker pushes job data over TCP
  ↓  kernel wakes worker via socket callback
  ↓  deserialize → your async handler function runs
  ↓  ACK on success → broker removes job
  ↓  throw on failure → broker requeues after timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every arrow is real work with real cost and real failure modes. None of it is magic. All of it is understandable, and understanding it makes you a meaningfully better engineer of the systems that depend on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Queues and background jobs are genuinely good tools. The resilience they add, the ability to absorb traffic spikes, retry failures gracefully, and let producers and consumers scale independently — all of that is real and worth the added complexity.&lt;/p&gt;

&lt;p&gt;The point is not to be wary of them. It is to understand what you are working with so you can configure them thoughtfully, monitor the right things, and know where to look when something behaves unexpectedly.&lt;/p&gt;

&lt;p&gt;The queue depth is a metric worth watching. The dead letter queue is an inbox worth reading. The ACK timeout is a contract with the broker worth understanding.&lt;/p&gt;

&lt;p&gt;Keep an eye on those three and your queues will mostly be a quiet, reliable part of your system doing exactly what they promised.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>typescript</category>
      <category>backend</category>
    </item>
    <item>
      <title>When Duplicate Code Is the Better Design</title>
      <dc:creator>Adam - The Developer ✨</dc:creator>
      <pubDate>Mon, 01 Jun 2026 06:09:57 +0000</pubDate>
      <link>https://dev.to/adamthedeveloper/when-duplicate-code-is-the-better-design-1idk</link>
      <guid>https://dev.to/adamthedeveloper/when-duplicate-code-is-the-better-design-1idk</guid>
      <description>&lt;p&gt;You've seen the developer. Maybe you &lt;em&gt;are&lt;/em&gt; the developer.&lt;/p&gt;

&lt;p&gt;They discover DRY — ✨ &lt;strong&gt;Don't Repeat Yourself&lt;/strong&gt; ✨ — and something switches in their brain. A primal need awakens. Every duplicated string, every similar-looking function, every pair of lines that rhyme in the wrong light becomes a &lt;strong&gt;personal affront&lt;/strong&gt;. An itch. A moral failing.&lt;/p&gt;

&lt;p&gt;Two weeks later, their codebase looks like a game of Jenga where every piece is also load-bearing, also abstract, also parametrized six ways to Sunday, and also, crucially, &lt;strong&gt;completely impossible to understand&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Congratulations. You've achieved ✨ DRY ✨. You've also achieved a codebase that will ruin your next three Fridays and everyone's.&lt;/p&gt;




&lt;h2&gt;
  
  
  What DRY Actually Says (and Doesn't)
&lt;/h2&gt;

&lt;p&gt;DRY comes from &lt;em&gt;The Pragmatic Programmer&lt;/em&gt; by Andy Hunt and Dave Thomas. The actual rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Every piece of &lt;strong&gt;knowledge&lt;/strong&gt; must have a single, unambiguous, authoritative representation within a system."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notice what it says: &lt;strong&gt;knowledge&lt;/strong&gt;. it's not &lt;em&gt;code&lt;/em&gt;. it's not &lt;em&gt;characters&lt;/em&gt;. it's not &lt;em&gt;"things that look similar when squinted at from across the room."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It says knowledge.&lt;/p&gt;

&lt;p&gt;The principle is about avoiding duplicated &lt;em&gt;intent and logic&lt;/em&gt; — not scrubbing every repeated character like you're laundering evidence. But somewhere between the book and the keyboard, people stopped reading and started pattern-matching like raccoons sorting shiny garbage:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If two lines of code look alike, merge them immediately or you're morally bankrupt, professionally suspect, and probably the reason standups run long."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's not DRY. That's &lt;strong&gt;aesthetic OCD with a philosophy degree and a GitHub contribution graph to protect&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Taxonomy of DRY Crimes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Crime #1: Abstracting Coincidental Similarity
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Two functions that happen to look the same TODAY&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatUserName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatAuthorName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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 classic DRY-brain response: &lt;em&gt;"These are identical! Extract!"&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The abstraction&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entity&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;Fine. Harmless, even. Like a campfire in dry grass. Six months later, user names want a salutation, author names want a pen name fallback, and your cute little &lt;code&gt;formatName&lt;/code&gt; has metastasized into a fifteen-parameter horror show with an enum called &lt;code&gt;NameFormattingStrategy&lt;/code&gt; — the kind of function that needs its own onboarding doc and makes junior devs reconsider their career choices.&lt;/p&gt;

&lt;p&gt;The duplication wasn't a bug. It was &lt;strong&gt;two different things that happened to share a shape for one Tuesday&lt;/strong&gt;. A user is not an author. Their names evolve on different timelines. The duplicate code was whispering that — you heard "refactor opportunity" and built a cage instead.&lt;/p&gt;




&lt;h3&gt;
  
  
  Crime #2: The Abstraction That Needs a Manual
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: readable in 3 seconds&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getActiveAdminUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isActive&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// After: DRY'd into a puzzle box&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;filterEntities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;predicates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sortBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transform&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;predicates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;pred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sortBy&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sortBy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limited&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;transform&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;limited&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;limited&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage (good luck, new hire)&lt;/span&gt;
&lt;span class="nf"&gt;filterEntities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&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 saved four lines. You created a &lt;code&gt;filterEntities&lt;/code&gt; function that now silently accumulates every filtering need in your entire app, grows to 200 lines, and gets passed to new developers with the haunted look of someone handing off a cursed object.&lt;/p&gt;

&lt;p&gt;The original function had a &lt;strong&gt;name&lt;/strong&gt;. It was easy to understand, it told a simple story, &lt;code&gt;getActiveAdminUsers&lt;/code&gt; is self-documenting. Your generalized thing is a puzzle.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Code is read far more than it's written. Abstractions are not free — they are paid for in comprehension, every single time someone opens that file.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Crime #3: DRY Across Wrong Boundaries
&lt;/h3&gt;

&lt;p&gt;The most insidious form. And unlike the toy examples above, this one has a body count.&lt;/p&gt;

&lt;p&gt;Every backend dev has seen this happen. You're building a notification system. You have email, SMS, push, and in-app notifications. They all take a user, a type, and some options. They look identical at the call site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// v1 — reasonable. clean. you feel good about yourself.&lt;/span&gt;
&lt;span class="nf"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order_confirmed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The abstraction makes sense. You write one function. You route internally. Ship it.&lt;/p&gt;

&lt;p&gt;Then marketing wants to send a promotional email blast. Different thing, same function — but now you need an &lt;code&gt;isMarketing&lt;/code&gt; flag because marketing emails have unsubscribe footers and transactional ones don't.&lt;/p&gt;

&lt;p&gt;Then legal needs CAN-SPAM compliance on marketing sends. Different opt-out logic per locale. Now you need &lt;code&gt;locale&lt;/code&gt; and &lt;code&gt;complianceRules&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then mobile push notifications need to respect iOS quiet hours, which email doesn't care about. Add &lt;code&gt;respectQuietHours&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then in-app notifications don't go through any external service at all — they're just a database write — but they're still "notifications" so they stay in the function.&lt;/p&gt;

&lt;p&gt;Then SMS gets a character limit and needs message chunking logic that email will never need.&lt;/p&gt;

&lt;p&gt;Eighteen months after that clean &lt;code&gt;sendNotification(user, type, options)&lt;/code&gt;, you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// v∞ — the function that consumed itself&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NotificationType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NotificationOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;push&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;in_app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isMarketing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isTransactional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;complianceRules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComplianceConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;respectQuietHours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;chunkIfOverLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;bypassOptOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// "just this once, for the launch"&lt;/span&gt;
  &lt;span class="nx"&gt;trackingPixel&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isMarketing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 40 lines of compliance logic&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="c1"&gt;// 30 lines of transactional logic&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 50 lines that have nothing to do with email&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;push&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 45 lines that have nothing to do with SMS&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;in_app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// just writes to a table but must pass through this gauntlet anyway&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 function is now 300 lines. It has no tests that actually cover all the flag combinations — there are 2⁷ of them, good luck. Every engineer who touches it adds one parameter at the top and one &lt;code&gt;if&lt;/code&gt; branch somewhere in the middle, and prays they didn't break the Malaysian SMS path.&lt;/p&gt;

&lt;p&gt;You have not DRY'd your code. You have built a &lt;strong&gt;load-bearing monolith disguised as a helper function&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The actual fix is humbling: email, SMS, push, and in-app were never the same thing. They shared a surface-level interface and nothing else. They have different rate limits, different compliance regimes, different retry logic, different failure modes, and different delivery guarantees. The right design was always:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;sendSms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;sendPushNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;createInAppNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, it's four functions. Yes, some options are repeated across them. That's fine — the repetition is &lt;em&gt;honest&lt;/em&gt;. Each function can evolve independently. The SMS team can add chunking without touching the email function. Legal can update the CAN-SPAM logic without a regression on push. A new engineer can read &lt;code&gt;sendEmail&lt;/code&gt; and understand &lt;code&gt;sendEmail&lt;/code&gt; without holding the entire notification universe in their head.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The shared abstraction didn't eliminate complexity. It hid it, then grew it, then held it hostage.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Crime #4: The Generic Repository That Ate Your Domain
&lt;/h3&gt;

&lt;p&gt;Every backend dev hits this phase. You have &lt;code&gt;UserService&lt;/code&gt;, &lt;code&gt;OrderService&lt;/code&gt;, &lt;code&gt;ProductService&lt;/code&gt;. They all need to fetch things from the database. The queries look suspiciously similar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Three services, three nearly identical queries. Your DRY alarm is screaming.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findAllUsers&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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;deletedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;async&lt;/span&gt; &lt;span class="nf"&gt;findAllOrders&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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;not&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draft&lt;/span&gt;&lt;span class="dl"&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;async&lt;/span&gt; &lt;span class="nf"&gt;findAllProducts&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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isPublished&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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 fix writes itself. You extract a base class. You add generics. You feel like you're finally doing &lt;em&gt;real&lt;/em&gt; architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The abstraction that will haunt three teams&lt;/span&gt;
&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&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;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findPaginated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filters&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products&lt;/span&gt;&lt;span class="dl"&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;Beautiful. Reusable. You deleted forty lines and posted about it in Slack. Life is good.&lt;/p&gt;

&lt;p&gt;Then product management asks for order search with customer name, date range, and payment status — but only for admins, and only orders that haven't been refunded unless the refund was partial. Your &lt;code&gt;findPaginated&lt;/code&gt; now accepts a &lt;code&gt;QueryOptions&lt;/code&gt; object with twelve optional fields and a &lt;code&gt;include&lt;/code&gt; array that half the team misconfigures.&lt;/p&gt;

&lt;p&gt;Then users need soft-delete scoping, role-based visibility, and a "last active" sort that requires a join. You override &lt;code&gt;findAll&lt;/code&gt; in &lt;code&gt;UserRepository&lt;/code&gt;. Then you override &lt;code&gt;findPaginated&lt;/code&gt; too. The base class is now mostly dead code that new hires still have to read.&lt;/p&gt;

&lt;p&gt;Then products need full-text search, category trees, and inventory counts from a warehouse table in another service. Someone adds &lt;code&gt;findPaginatedWithSearch&lt;/code&gt;. Someone else adds &lt;code&gt;findPaginatedWithJoins&lt;/code&gt;. The base class grows a &lt;code&gt;buildQuery&lt;/code&gt; hook, then a &lt;code&gt;QueryBuilder&lt;/code&gt; parameter, then an escape hatch called &lt;code&gt;rawQueryOverride&lt;/code&gt; that three repositories use and nobody documents.&lt;/p&gt;

&lt;p&gt;Eighteen months later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// v∞ — BaseRepository, but make it suffer&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;findPaginated&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;FilterMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;include&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;IncludeMap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;JoinConfig&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;search&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;SearchConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;SortConfig&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;SortConfig&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;internal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;softDelete&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;bypassTenantScope&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "temporary, for the migration"&lt;/span&gt;
    &lt;span class="nl"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;AggregateConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;unknown&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PaginatedResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;TransformedRow&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 180 lines of conditional query assembly&lt;/span&gt;
  &lt;span class="c1"&gt;// every repository passes a different options shape&lt;/span&gt;
  &lt;span class="c1"&gt;// the type signature is longer than most functions it replaces&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have not eliminated duplication. You have built a &lt;strong&gt;generic pagination framework&lt;/strong&gt; that every entity in your system must awkwardly fit into, like forcing every piece of furniture through the same IKEA allen wrench.&lt;/p&gt;

&lt;p&gt;The honest version was always:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// UserRepository — boring, explicit, correct&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findActiveUsersForAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// OrderRepository — different domain, different query&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findOrdersForDashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderDashboardFilters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ProductRepository — nobody else's problem&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findPublishedProductsWithInventory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;categoryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, they all paginate. Yes, they all query a database. That's &lt;strong&gt;infrastructure similarity&lt;/strong&gt;, not &lt;strong&gt;domain knowledge&lt;/strong&gt;. Users, orders, and products don't share a reason to change — they share a SQL dialect. Conflating those two is how you end up with a &lt;code&gt;BaseEntityManagerFactoryHelperUtil&lt;/code&gt; and a Jira ticket titled "refactor findPaginated (blocked: needs architect approval)."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pagination is not a domain concept. It's a transport detail. Your repository layer is not a place to build a query DSL because SELECT statements rhymed once.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Principle You Actually Need: AHA
&lt;/h2&gt;

&lt;p&gt;Sandi Metz — of &lt;em&gt;Practical Object-Oriented Design&lt;/em&gt; fame — offers the antidote:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AHA: Avoid Hasty Abstractions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The rule: &lt;strong&gt;prefer duplication over the wrong abstraction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A wrong abstraction is worse than duplication because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Duplication is visible and easy to fix&lt;/li&gt;
&lt;li&gt;A wrong abstraction is load-bearing, hard to see, and expensive to undo&lt;/li&gt;
&lt;li&gt;People are afraid to delete abstractions, so they pile parameters on top until it collapses&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The heuristic she and others suggest: &lt;strong&gt;wait for the third time&lt;/strong&gt;. Once — write it. Twice — note the repetition. Three times — &lt;em&gt;now&lt;/em&gt; think about what abstraction, if any, makes sense. By then you have enough examples to see the &lt;em&gt;actual&lt;/em&gt; shape of the knowledge, not just the accidental shape of the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  When DRY Is Right
&lt;/h2&gt;

&lt;p&gt;To be fair to the principle: DRY is genuinely critical in the right places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business rules.&lt;/strong&gt; The formula for calculating interest, the rule for what makes a user "active," the threshold for triggering an alert — these must live in exactly one place. When the business logic changes, you want to change exactly one thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration.&lt;/strong&gt; A base URL hardcoded in twelve files is twelve places a typo can happen. That's rightfully DRY.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data schemas.&lt;/strong&gt; Your database schema and your validation schema should not diverge because you copy-pasted them. Derive one from the other.&lt;/p&gt;

&lt;p&gt;The test for whether something &lt;em&gt;should&lt;/em&gt; be DRY: &lt;strong&gt;if this thing needs to change, how many places need to change with it, and do those places share a reason to change?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If yes — DRY it.&lt;/p&gt;

&lt;p&gt;If they just look similar today — leave it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Anti-Pattern DRY Is Trying to Fight
&lt;/h2&gt;

&lt;p&gt;DRY's real enemy was never "duplicate code." It was &lt;strong&gt;duplicate knowledge&lt;/strong&gt; — the same business rule, scattered across the system, slowly drifting out of sync.&lt;/p&gt;

&lt;p&gt;The nightmare scenario: your pricing logic is in the database trigger, the API controller, the frontend calculation, and a comment in a Slack message from 2019. When pricing changes, you find three of them. The fourth one quietly disagrees for six months, occasionally giving users the wrong number, and you never know why.&lt;/p&gt;

&lt;p&gt;That's what DRY is for. Not for making your &lt;code&gt;formatName&lt;/code&gt; function 40% more reusable.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Checklist for Before You Abstract
&lt;/h2&gt;

&lt;p&gt;Before you extract that abstraction, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Is this duplicate &lt;em&gt;knowledge&lt;/em&gt;, or duplicate &lt;em&gt;shape&lt;/em&gt;?&lt;/strong&gt; Two things can look alike for different reasons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can I name this abstraction clearly?&lt;/strong&gt; If you're reaching for &lt;code&gt;processEntityData&lt;/code&gt; or &lt;code&gt;handleThing&lt;/code&gt;, stop. The abstraction isn't real yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What happens when one usage changes?&lt;/strong&gt; If you can't change the abstraction without worrying about the other usages, it's too coupled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Am I doing this because it's right, or because repetition makes me uncomfortable?&lt;/strong&gt; Valid question. The answer matters.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;DRY is a heuristic, not a commandment. It was written to fight a specific enemy: knowledge duplication causing systems to rot. It was not written to make your codebase a shrine to abstraction.&lt;/p&gt;

&lt;p&gt;The best codebases I've read had a little repetition in them. Deliberate repetition. The kind where someone clearly decided "these two things look alike but they're not the same thing, and I'm going to leave them separate so they can evolve separately."&lt;/p&gt;

&lt;p&gt;That's not laziness. That's wisdom.&lt;/p&gt;

&lt;p&gt;Write the thing twice if you have to. Your future self, reading the code at 11pm with no context, will thank you for the clarity.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>programming</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
