<?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: &lt;devtips/&gt;</title>
    <description>The latest articles on DEV Community by &lt;devtips/&gt; (@dev_tips).</description>
    <link>https://dev.to/dev_tips</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%2F2901662%2F1dcce5de-7920-43a0-a337-e1dfb375b204.png</url>
      <title>DEV Community: &lt;devtips/&gt;</title>
      <link>https://dev.to/dev_tips</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dev_tips"/>
    <language>en</language>
    <item>
      <title>AI won’t replace you, but bad AI habits will</title>
      <dc:creator>&lt;devtips/&gt;</dc:creator>
      <pubDate>Mon, 15 Jun 2026 15:49:09 +0000</pubDate>
      <link>https://dev.to/dev_tips/ai-wont-replace-you-but-bad-ai-habits-will-1fnp</link>
      <guid>https://dev.to/dev_tips/ai-wont-replace-you-but-bad-ai-habits-will-1fnp</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="293e"&gt;A blunt playbook for devs who don’t want to turn into autocomplete zombies.&lt;/h2&gt;
&lt;span&gt;&lt;/span&gt;&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p id="01e2"&gt;The first time an AI wrote code for me, I felt like I had unlocked cheat codes for real life. I typed a half-baked function name, hit enter, and suddenly I had a block of code that looked legit. It was magical. The second time, though? It suggested something so catastrophic basically the programming equivalent of pulling the fire alarm that I realized: this thing is less “mentor” and more “overconfident intern who thinks they know pointers but actually just broke prod.”&lt;/p&gt;
&lt;p id="8db1"&gt;That’s where most of us are right now. AI is everywhere: in our IDEs, our docs, even sneaking into PR reviews. Some days it feels like rocket fuel; other days it feels like an autocomplete with a drinking problem.&lt;/p&gt;
&lt;p id="8d3d"&gt;The tricky part isn’t whether AI is “good” or “bad.” The tricky part is how we, as developers, use it without becoming lazy, dependent, or worse complacent. Because here’s the uncomfortable truth: AI won’t replace you, but bad AI habits absolutely will.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="5933"&gt;&lt;strong&gt;TLDR&lt;/strong&gt;: This article is a survival guide for developers in the AI era. We’ll break down why AI feels both magical and mid, the five switches that make AI actually useful, when to trust and when to verify, how to use AI as a research assistant (not a code monkey), the dangers of autocomplete brain, and a playbook for building a healthy workflow.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="17f4"&gt;Why AI feels both magical and mid&lt;/h2&gt;
&lt;p id="d555"&gt;Every dev I know has had &lt;em&gt;that moment&lt;/em&gt; with AI. The first time it autocompleted a function and nailed it, you probably thought: &lt;em&gt;“Wow… this thing just saved me half an hour.”&lt;/em&gt; It’s the same dopamine hit as discovering &lt;code&gt;ctrl+r&lt;/code&gt; in bash or realizing you can pipe &lt;code&gt;grep&lt;/code&gt; into &lt;code&gt;less&lt;/code&gt;. Pure wizardry.&lt;/p&gt;
&lt;p id="3d43"&gt;But the honeymoon ends quickly. The same tool that wrote a clean utility function also happily hallucinates imports that don’t exist, invents APIs, and will confidently explain things that are flat-out wrong. It’s like pair programming with someone who &lt;em&gt;sounds&lt;/em&gt; senior but has never actually shipped code.&lt;/p&gt;
&lt;p id="aaf9"&gt;&lt;strong&gt;The magic-mid paradox comes from two truths living side by side:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li id="b752"&gt;
&lt;strong&gt;AI is fast and confident.&lt;/strong&gt; It fills the silence instantly, which feels great when you’re stuck.&lt;/li&gt;
&lt;li id="06d5"&gt;
&lt;strong&gt;AI is also wrong, a lot.&lt;/strong&gt; Not always in spectacular ways sometimes it just misses edge cases or forgets how a library actually works.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id="d676"&gt;The result? You get addicted to the speed, but burned by the trust. One minute you’re flying, the next you’re undoing a migration because AI forgot about a foreign key constraint.&lt;/p&gt;
&lt;p id="0159"&gt;Developers on Stack Overflow noticed this quickly so much so that AI-generated answers were banned because they were too often wrong but written with scary confidence. &lt;strong&gt;Hacker News threads echo the same:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="a80d"&gt;“Feels powerful, but I can’t trust it.”&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="fe20"&gt;And that’s the real catch. AI isn’t here to replace you. It’s here to test whether you still think like an engineer, or whether you’re willing to trust an autocomplete with swagger.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="b2b0"&gt;The five switches framework&lt;/h2&gt;
&lt;p id="0d46"&gt;Using AI effectively isn’t about “prompt engineering wizardry.” It’s about flipping the right switches at the right time. After months of testing (and plenty of bad code reviews), I’ve boiled it down to five controls that separate “autocomplete brain” from “actually useful teammate.”&lt;/p&gt;
&lt;h3 id="ce76"&gt;1. Reasoning mode&lt;/h3&gt;
&lt;p id="9beb"&gt;AI defaults to spitting out the most common answer. That’s fine for boilerplate, but when you’re debugging or designing, you need it to think step by step.&lt;/p&gt;
&lt;p id="1d86"&gt;&lt;strong&gt;Before (default):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span id="3977"&gt;&lt;span&gt;# Prompt&lt;/span&gt;&lt;br&gt;Write a regex that validates emails.&lt;/span&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;span id="16ed"&gt;# Output&lt;br&gt;^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$&lt;/span&gt;&lt;/pre&gt;
&lt;p id="d20f"&gt;Looks okay… until you realize it fails on &lt;code&gt;example@localhost&lt;/code&gt;.&lt;/p&gt;
&lt;p id="3f42"&gt;&lt;strong&gt;After (reasoning):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span id="3722"&gt;&lt;span&gt;# Prompt&lt;/span&gt;&lt;br&gt;Think step by step and list edge cases before writing a regex &lt;span&gt;for&lt;/span&gt; emails.&lt;/span&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;span id="123d"&gt;# Output&lt;br&gt;Edge cases: localhost, subdomains, quoted strings...&lt;br&gt;Regex: (full RFC5322-compliant pattern)&lt;/span&gt;&lt;/pre&gt;
&lt;p id="165f"&gt;Still gnarly, but now it’s at least considering reality instead of hallucinating confidence.&lt;/p&gt;
&lt;h3 id="d9e9"&gt;2. Verbosity control&lt;/h3&gt;
&lt;p id="3163"&gt;Sometimes you want “explain like I’m five.” Sometimes you need “write the RFC.” Most devs forget you can actually control this.&lt;/p&gt;
&lt;ul&gt;
&lt;li id="ef6f"&gt;
&lt;strong&gt;Low verbosity:&lt;/strong&gt; quick code snippet, zero fluff.&lt;/li&gt;
&lt;li id="4632"&gt;
&lt;strong&gt;High verbosity:&lt;/strong&gt; detailed breakdown with trade-offs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id="34ce"&gt;It’s like switching between &lt;code&gt;ls&lt;/code&gt; and &lt;code&gt;ls -la&lt;/code&gt;. Same tool, different levels of detail.&lt;/p&gt;
&lt;h3 id="19e1"&gt;3. Tooling&lt;/h3&gt;
&lt;p id="9c6e"&gt;Don’t let AI just “guess.” Route it to tools when possible: docs, REPLs, diagrams. If your setup allows retrieval (docs fetching) or code execution, use it. AI without tools is like a dev without &lt;code&gt;man&lt;/code&gt; pages dangerous.&lt;/p&gt;
&lt;h3 id="a3b4"&gt;4. Self-reflection prompts&lt;/h3&gt;
&lt;p id="1016"&gt;The easiest hack: ask AI to critique itself.&lt;/p&gt;
&lt;ul&gt;
&lt;li id="ab25"&gt;“What could be wrong with your answer?”&lt;/li&gt;
&lt;li id="51e8"&gt;“List three failure cases.”&lt;/li&gt;
&lt;/ul&gt;
&lt;p id="cbba"&gt;Nine times out of ten, it catches something you missed. It’s the rubber duck debugging effect, but automated.&lt;/p&gt;
&lt;h3 id="491f"&gt;5. Rubrics / meta-prompts&lt;/h3&gt;
&lt;p id="facf"&gt;Structure beats vibes. Instead of “write me a design doc,” try:&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="537c"&gt;“Follow this rubric: Problem → Constraints → Options → Risks → Recommendation.”&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p id="59fa"&gt;&lt;strong&gt;Before:&lt;/strong&gt; bland wall of text.&lt;br&gt;&lt;strong&gt;After:&lt;/strong&gt; structured doc you could actually drop into a repo.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id="d870"&gt;The point&lt;/h3&gt;
&lt;p id="94af"&gt;These five switches aren’t about tricking the model. They’re about managing it like you would a junior teammate: sometimes you need short answers, sometimes deep reasoning, sometimes structured artifacts. If you don’t flip the right switch, don’t be surprised when it gives you garbage-tier output.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="700" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2AuBAliCRn97sz8Uhhkfsj1Q.jpeg"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="145f"&gt;When to trust, when to verify&lt;/h2&gt;
&lt;p id="ad45"&gt;AI is like that one coworker who’s great at banging out boilerplate but absolutely should not be let near production. The trick is knowing when you can trust it versus when you need to verify every single line.&lt;/p&gt;
&lt;p id="3131"&gt;&lt;strong&gt;Trust-worthy zones:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li id="1406"&gt;Generating CRUD scaffolding.&lt;/li&gt;
&lt;li id="d283"&gt;Writing repetitive test stubs.&lt;/li&gt;
&lt;li id="bcbf"&gt;Summarizing docs you’ll double-check anyway.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id="1725"&gt;&lt;strong&gt;High-risk zones:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li id="0b6a"&gt;Database migrations.&lt;/li&gt;
&lt;li id="2205"&gt;Authentication logic.&lt;/li&gt;
&lt;li id="adf1"&gt;Anything touching production infra.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id="a403"&gt;Think of it like code reviews: you don’t sweat a for-loop refactor, but you triple-check schema changes. AI should be held to the same standard.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="864e"&gt;&lt;strong&gt;Here’s a quick story:&lt;/strong&gt; I once asked an AI to generate unit tests. Looked fine. All tests passed. Victory, right? Wrong. It had silently tested the wrong function. Everything was green because the assertions were nonsense. That’s when I realized: green doesn’t mean correct, it just means consistent.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="ff9e"&gt;Even Stack Overflow caught onto this AI answers looked legit but were often wrong, so they banned them. If one of the largest dev Q&amp;amp;A platforms can’t trust it unsupervised, why should you?&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="e581"&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; use AI where it accelerates, but verify like your job depends on it because it does.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="d64a"&gt;AI as research assistant, not code monkey&lt;/h2&gt;
&lt;p id="bbfc"&gt;The best way I’ve found to use AI isn’t as a code generator at all it’s as a research buddy. Treating it like a junior dev who can draft architecture diagrams, outline RFCs, or brainstorm test cases is way more effective than asking it to brute-force production code.&lt;/p&gt;
&lt;p id="dbb1"&gt;Example: I once asked AI to design an OAuth flow. What I got back was boilerplate diagrams and generic “best practices.” Useless. Then I flipped the script: instead of asking it to design, I gave it &lt;em&gt;my design&lt;/em&gt; and told it to critique. Suddenly I got a list of risks, edge cases, and even alternative libraries to consider. That’s value.&lt;/p&gt;
&lt;p id="d9df"&gt;Another underrated trick: use it to draft headings or structures. For a design doc,&lt;/p&gt;
&lt;p id="7268"&gt;&lt;strong&gt;AI can spit out:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="94c8"&gt;&lt;em&gt;Problem → Constraints → Options → Risks → Recommendation&lt;/em&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="0746"&gt;Then you fill in the engineering meat. It’s like having a personal tech writer who never complains.&lt;/p&gt;
&lt;p id="d1e5"&gt;There’s a reason tools like GitHub RFC templates exist: structure matters. AI is great at scaffolding, but you need to provide the judgment and the trade-offs.&lt;/p&gt;
&lt;p id="6c38"&gt;So stop asking AI to be your code monkey. Start asking it to be your overcaffeinated research assistant. It’ll still hallucinate, but at least the stakes are lower.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="08b6"&gt;The danger of autocomplete brain&lt;/h2&gt;
&lt;p id="315c"&gt;Here’s the uncomfortable truth: the biggest risk with AI isn’t bad code it’s bad habits. If you lean on it too much, you start losing the ability to think through problems yourself. Call it &lt;em&gt;autocomplete brain&lt;/em&gt;.&lt;/p&gt;
&lt;p id="4f41"&gt;It’s the same loop we all fell into with Stack Overflow back in the day. Copy, paste, ship. You get the dopamine hit of “solving” something without actually understanding it. Multiply that by 10 when AI serves you answers instantly and confidently.&lt;/p&gt;
&lt;p id="9cb3"&gt;I’ve caught myself in this trap. I once spent nearly an hour debugging with AI, chasing one nonsense suggestion after another. Only when I finally opened the logs myself did I see the obvious error. I wasn’t solving problems anymore I was outsourcing my thinking to an overconfident autocomplete.&lt;/p&gt;
&lt;p id="a807"&gt;This is where burnout creeps in. You’re coding all day, but you’re not &lt;em&gt;learning&lt;/em&gt;. You’re not building intuition. And when something truly breaks the kind of bug that requires actual systems thinking you’re suddenly lost.&lt;/p&gt;
&lt;p id="09db"&gt;If you’ve ever felt like you’re coding but not &lt;em&gt;growing&lt;/em&gt;, check your habits. Autocomplete brain doesn’t show up overnight, but it’ll hollow out your skills if you let it.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="700" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2A6PvOyQnnCX5PGZUw4qVg8A.png"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="604d"&gt;Healthy AI/dev workflow (the playbook)&lt;/h2&gt;
&lt;p id="1423"&gt;AI isn’t the enemy. Bad workflow is. The devs who thrive with AI aren’t the ones who let it write everything they’re the ones who treat it like a turbocharged assistant, then layer human judgment on top.&lt;/p&gt;
&lt;p id="f247"&gt;&lt;strong&gt;Here’s the playbook I’ve landed on after many facepalms:&lt;/strong&gt;&lt;/p&gt;
&lt;p id="5b95"&gt;&lt;strong&gt;Draft with AI&lt;/strong&gt;&lt;br&gt; Let it generate the boring stuff: scaffolding, test stubs, outlines. Don’t expect perfection just raw clay.&lt;/p&gt;
&lt;p id="a323"&gt;&lt;strong&gt;Verify with docs, logs, tests&lt;/strong&gt;&lt;br&gt; Before touching prod, check its output against the actual docs or run it in a sandbox. Logs don’t lie.&lt;/p&gt;
&lt;p id="f86f"&gt;&lt;strong&gt;Refine with rubrics&lt;/strong&gt;&lt;br&gt; Ask AI to restructure or critique. &lt;strong&gt;Example:&lt;/strong&gt; “Follow Problem → Constraints → Options → Risks → Recommendation.” Now you get something useful instead of a wall of text.&lt;/p&gt;
&lt;p id="9aa3"&gt;&lt;strong&gt;Human final judgment&lt;/strong&gt;&lt;br&gt; If you wouldn’t merge code from a junior without review, don’t merge AI’s output without review. Same rule.&lt;/p&gt;
&lt;h3 id="fd34"&gt;Decision matrix&lt;/h3&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="213" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2AOMa9mDU_mag6EXFBpPRjhA.png"&gt;&lt;p id="5cb9"&gt;This matrix isn’t gospel it’s a sanity check. The point is to stop treating AI like an oracle and start treating it like a tool you configure. If you add even this much structure to your workflow, you’ll avoid 90% of the garbage-tier outputs that lead to wasted hours.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="09a7"&gt;What’s next: router models &amp;amp; smarter tools&lt;/h2&gt;
&lt;p id="1b49"&gt;AI isn’t standing still. The next wave of tools won’t just be “one big model does everything.” They’ll be &lt;strong&gt;router models&lt;/strong&gt; systems that quietly decide which sub-model or tool should handle your request. Think of it like a senior engineer who knows when to grab the database person, the security person, or the intern for boilerplate.&lt;/p&gt;
&lt;p id="22f8"&gt;OpenAI already hinted at this in their system card: when you ask something complex, it can route parts of the query to specialized solvers. That’s why one moment it’s good at summarizing research, and the next it’s drafting halfway-decent code. Behind the curtain, it might not be the same model doing both.&lt;/p&gt;
&lt;p id="d308"&gt;This is exciting, but also risky. Some researchers have praised GPT-5’s problem-solving chops, noting that it produced surprisingly strong results on hard math problems. Others pointed out the obvious: the results were “impressive, but within reach for an expert.” In other words, cool demo but don’t throw out your textbooks yet.&lt;/p&gt;
&lt;p id="eaa7"&gt;The real future probably isn’t one mega-model ruling everything. It’s orchestration: devs deciding when to lean on reasoning, when to force rubrics, when to route queries. Tools will get smarter, but the responsibility to &lt;em&gt;use them wisely&lt;/em&gt; will stay on us.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="98b9"&gt;Conclusion&lt;/h2&gt;
&lt;p id="e2c3"&gt;AI isn’t here to steal your job. But it can absolutely steal your edge if you let it. The devs who survive the AI wave won’t be the ones who let autocomplete write their apps they’ll be the ones who know when to draft, when to verify, and when to flat-out ignore the shiny suggestion.&lt;/p&gt;
&lt;p id="30d1"&gt;Here’s the uncomfortable part: if you stop thinking critically, you’re basically just a human captcha. AI doesn’t need more prompt typers; it needs engineers who can orchestrate workflows, verify outputs, and push it beyond surface-level answers. That’s the skill set that will separate “AI user” from “AI abuser.”&lt;/p&gt;
&lt;p id="43d4"&gt;I’ve seen both sides in my own projects: the moments where AI made me feel unstoppable, and the moments where I realized I’d trusted a hallucination and wasted hours. The difference was never the tool it was how I used it.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="528d"&gt;&lt;strong&gt;So here’s my take: &lt;/strong&gt;AI won’t replace you. But bad AI habits will. What’s your worst AI fail story? Drop it in the comments I guarantee you’re not the only one who trusted autocomplete a little too much.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id="c6dc"&gt;Helpful resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li id="524b"&gt;
&lt;a href="https://github.com/openai/openai-cookbook" rel="noopener ugc nofollow noreferrer"&gt;OpenAI Cookbook&lt;/a&gt; practical guides for prompting, evaluation, and workflows.&lt;/li&gt;
&lt;li id="5350"&gt;
&lt;a href="https://meta.stackoverflow.com/questions/421831/temporary-policy-chatgpt-is-banned" rel="noopener ugc nofollow noreferrer"&gt;Stack Overflow AI ban&lt;/a&gt; why AI answers got blocked.&lt;/li&gt;
&lt;li id="becf"&gt;
&lt;a href="https://github.com/rust-lang/rfcs" rel="noopener ugc nofollow noreferrer"&gt;GitHub RFC templates&lt;/a&gt; structure your design docs like the pros.&lt;/li&gt;
&lt;li id="d5af"&gt;
&lt;a href="https://www.reddit.com/r/programming/" rel="noopener ugc nofollow noreferrer"&gt;Reddit’s r/programming&lt;/a&gt; &amp;amp; Hacker News ongoing dev community debates on AI.&lt;/li&gt;
&lt;/ul&gt;


</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>MCP servers just made your AI agent actually useful in prod</title>
      <dc:creator>&lt;devtips/&gt;</dc:creator>
      <pubDate>Mon, 15 Jun 2026 15:40:06 +0000</pubDate>
      <link>https://dev.to/dev_tips/mcp-servers-just-made-your-ai-agent-actually-useful-in-prod-1glh</link>
      <guid>https://dev.to/dev_tips/mcp-servers-just-made-your-ai-agent-actually-useful-in-prod-1glh</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="22d1"&gt;Your Claude can write Terraform. Can it tell you your cluster is on fire right now? In 2026, the answer is finally yes if you’re plugged in right.&lt;/h2&gt;
&lt;span&gt;&lt;/span&gt;&lt;p id="eeda"&gt;There’s a moment every DevOps engineer has experienced at least once. You’re mid-incident, something is broken in prod, and you decide against your better judgment to ask your AI assistant what’s going on. It confidently tells you to run a kubectl command. You run it. Things get worse. The AI had no idea what your cluster state was. It was just pattern-matching from training data, cosplaying as an SRE.&lt;/p&gt;
&lt;p id="739f"&gt;That’s not a dig at AI. That’s a fundamental architecture problem. The model has no eyes. It’s brilliant in a vacuum it can write your Helm charts, explain your runbooks, draft your postmortems but it cannot see your live Prometheus metrics, your failing pods, or the PagerDuty alert that fired six minutes ago. It’s like hiring the smartest engineer you’ve ever met, then never giving them VPN access.&lt;/p&gt;
&lt;p id="7de5"&gt;That’s the gap Model Context Protocol is closing. MCP Anthropic’s open standard, shipped in late 2024 is basically USB-C for AI agents. It gives models a standardized way to connect to real, live tools. Not a snapshot. Not training data. The actual system, right now. And in 2026, the DevOps MCP ecosystem has quietly gone from “interesting experiment” to “wait, this actually works.”&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="91ca"&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; MCP servers are what turn your AI assistant from a really smart text box into something that can meaningfully participate in your infrastructure. This article breaks down the 10 worth knowing about, how to think about picking them, and what it all means for the SRE role going forward.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="0873"&gt;What MCP actually is (and why it’s not just hype)&lt;/h2&gt;
&lt;p id="56e3"&gt;If you’ve ever set up an LSP in Neovim, you already understand MCP intuitively. You had a language servera separate process that knew everything about your codebase and your editor talked to it over a standard protocol. You didn’t hardcode autocomplete for every language into Neovim itself. You just taught it how to talk to something that already knew.&lt;/p&gt;
&lt;p id="6571"&gt;MCP is the same idea, but for AI agents and external tools. Instead of every company building bespoke integrations custom API glue, one-off tool wrappers, fragile function-calling hacks you have a standard protocol that any AI agent can speak and any tool can expose. The model doesn’t need to know the internals of your Kubernetes cluster. It just needs to know how to talk to the MCP server sitting in front of it.&lt;/p&gt;
&lt;p id="b8c3"&gt;Anthropic dropped the &lt;a href="https://github.com/modelcontextprotocol" rel="noopener ugc nofollow noreferrer"&gt;MCP spec&lt;/a&gt; in late 2024 and it landed with the energy of every good open standard: quiet at first, then suddenly everywhere. By early 2026, GitHub, AWS, Docker, HashiCorp, and Datadog all have official MCP servers. The community registry has hundreds more. It went from “Anthropic internal thing” to “the way you connect AI to tools” faster than most people expected same arc as Docker, same arc as LSP itself.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="295" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1050%2F1%2A_PCy8XjUxqo6DuC3zBgykw.jpeg"&gt;&lt;p id="e918"&gt;The real unlock isn’t speed, though everyone sells it that way. The real unlock is &lt;strong&gt;accuracy&lt;/strong&gt;. Before MCP, your agent was operating on stale training data and whatever you pasted into the context window. With MCP, it’s pulling live state actual pod status, real alert history, current IAM policies. The difference between those two things is the difference between a weather app and a weather station. One is telling you what usually happens. The other is telling you what’s happening right now.&lt;/p&gt;
&lt;p id="c443"&gt;That distinction matters a lot when prod is down.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="8705"&gt;The DevOps gap AI couldn’t fill until now&lt;/h2&gt;
&lt;p id="8448"&gt;Here’s an honest recap of AI in DevOps before MCP: great at writing things, useless at knowing things. You could ask it to scaffold a Terraform module and it’d do a decent job. You could ask it to explain a Kubernetes concept and it’d nail it. But the moment you needed it to participate in something live an incident, a deploy, a capacity decision it fell apart. Not because the model was dumb. Because it was blind.&lt;/p&gt;
&lt;p id="d944"&gt;The failure mode was always the same. You’d describe your situation in natural language, the model would generate a plausible-sounding response, and then you’d discover it was using a Terraform provider version from two years ago, or a kubectl flag that got deprecated, or an AWS API that no longer existed. It wasn’t lying to you. It genuinely didn’t know any better. It had no access to your actual environment, your actual versions, your actual state. It was doing its best with a blindfold on.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="6d13"&gt;What’s the point of a copilot that can’t see the cockpit?&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="bf0c"&gt;The incident war stories are universal at this point. Someone asks an AI for a rollback command during a bad deploy. The command looks right. It runs. It makes things worse because the model didn’t know the current replica count, the current image tag, or that the PVC had already been updated. The AI wasn’t wrong in theory. It was wrong about your specific cluster, at that specific moment, in that specific state. And in infrastructure, that gap between theory and reality is exactly where incidents live.&lt;/p&gt;
&lt;p id="ff65"&gt;MCP closes that gap by giving the agent actual context not described context, not pasted context, live context pulled directly from the tools your team already uses. Your agent stops guessing what your cluster looks like and starts reading it. That’s not a small upgrade. That’s the difference between a brilliant intern who’s never been given VPN access and one who’s actually in the system, looking at the same dashboards you are.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="799" height="446" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1050%2F1%2A2LdyNt0SL3MygroI5kCzUw.jpeg"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="7edd"&gt;The 10 MCP servers worth your attention in 2026&lt;/h2&gt;
&lt;p id="2029"&gt;This is the part where most articles throw a numbered list at you and call it a day. We’re not doing that. Each of these is worth understanding what it actually does, why it matters for real DevOps work, and where it fits in your stack.&lt;/p&gt;
&lt;h3 id="94e1"&gt;GitHub MCP server&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p id="7a42"&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Platform engineers, backend developers, DevOps leads&lt;/p&gt;
&lt;p id="9b45"&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; PR creation and review, issue triage and labeling, repo search, CI/CD status queries, code search, branch management, release tracking&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p id="3055"&gt;This one’s official, Anthropic-maintained, and the first one most teams reach for because everyone’s already on GitHub. The practical use case that clicked for me: asking Claude to triage open issues by priority, label them, and draft responses without touching the GitHub UI once. That’s not a demo. That’s Tuesday morning. It also handles CI status queries natively, so your agent can check whether the pipeline passed before suggesting a merge. Sounds small. Saves a surprising amount of tab-switching. &lt;a href="https://github.com/github/github-mcp-server" rel="noopener ugc nofollow noreferrer"&gt;github-mcp-server&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="a987"&gt;AWS MCP server&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p id="a143"&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Cloud engineers, infrastructure teams, FinOps-curious SREs&lt;/p&gt;
&lt;p id="7b55"&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; EC2, S3, IAM, CloudWatch access, cost and usage queries, resource inventory, misconfiguration detection, multi-service coverage across the AWS ecosystem&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p id="ed7f"&gt;The official AWS Labs entry, built in collaboration with Anthropic. The “just describe my infra” use case is where this shines ask it what’s running, what’s expensive, what’s misconfigured. It’s not magic, but it’s a lot better than grepping through the console at midnight. The IAM query support is where it quietly earns its keep least-privilege audits that used to take an afternoon now take a prompt. &lt;a href="https://github.com/awslabs/mcp" rel="noopener ugc nofollow noreferrer"&gt;aws-mcp&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="713f"&gt;Kubernetes MCP server&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p id="6a8a"&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Platform engineers, SREs&lt;/p&gt;
&lt;p id="741b"&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; Full cluster visibility across multiple clusters, natural language pod/deployment/service diagnostics, read-only mode for safe inspection workflows, OpenShift support, direct Kubernetes API integration (not kubectl CLI wrapping)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p id="8459"&gt;Real-time pod status, deployment state, node descriptions, events all queryable in plain language. The community-built &lt;a href="https://github.com/strowk/mcp-k8s-go" rel="noopener ugc nofollow noreferrer"&gt;mcp-k8s-go&lt;/a&gt; is the one most teams are running, and the direct Kubernetes API integration matters more than it sounds. It’s not wrapping kubectl and parsing stdout it’s talking to the API server directly, which means cleaner data and no dependency on your local kubeconfig gymnastics. The read-only mode is what makes this safe to hand to junior engineers and on-call rotations without a lengthy approval process. Ask it why a pod is crash-looping and it’ll actually look at the events, not just guess.&lt;/p&gt;
&lt;h3 id="3fd5"&gt;Datadog MCP server&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p id="45a3"&gt;&lt;strong&gt;Best for:&lt;/strong&gt; SREs, on-call engineers, platform teams running cloud-native observability&lt;/p&gt;
&lt;p id="a156"&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; Live metrics and dashboard queries, monitor and alert status, incident history, log search, APM trace access, SLO tracking&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p id="07e6"&gt;The “&lt;em&gt;is prod on fire?&lt;/em&gt;” server. Live metrics, dashboard data, monitor status, alert history all accessible without leaving your agent workflow. Datadog’s official MCP integration is polished and the use case is obvious: incident triage without tab-switching. When your agent can pull the exact metric spike that triggered the alert, correlate it with a recent deploy, and surface the relevant APM traces the postmortem practically writes itself. The SLO tracking access is underrated too. Instead of manually checking error budget burn rate, you ask. That’s the kind of friction removal that compounds over a quarter.&lt;/p&gt;
&lt;h3 id="ac1d"&gt;Terraform MCP server&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p id="c4ea"&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Infrastructure engineers, DevOps teams managing multi-environment IaC&lt;/p&gt;
&lt;p id="e165"&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; Plan inspection, state queries, drift detection, resource validation, workspace management, module dependency resolution&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p id="2b22"&gt;HashiCorp-backed and pairs absurdly well with Claude Code. The workflow that’s become common: describe a change in natural language, have the agent generate the Terraform, validate it against the real state, and flag drift before you apply. It’s not replacing your &lt;code&gt;terraform plan&lt;/code&gt; review. It's making it faster and harder to skip. The drift detection use case is where this earns its place in production workflows catching state divergence before it becomes an incident instead of after.&lt;/p&gt;
&lt;h3 id="b132"&gt;Prometheus MCP server&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p id="c514"&gt;&lt;strong&gt;Best for:&lt;/strong&gt; SREs and platform teams running open-source observability stacks&lt;/p&gt;
&lt;p id="e2b5"&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; Live PromQL query execution, alert rule inspection, target health and scrape status, recording rule validation, metric label exploration&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p id="c510"&gt;For teams running open-source observability stacks, this fills the Datadog-shaped gap without the Datadog-shaped bill. Live PromQL queries against your real data, alert rule inspection, target health checks. The agents that can write and validate PromQL against your actual metrics are genuinely useful during capacity planning not just incidents. Metric label exploration alone saves the 10 minutes of &lt;code&gt;curl | jq&lt;/code&gt; spelunking you do every time you forget what labels a service is exporting.&lt;/p&gt;
&lt;h3 id="4e2d"&gt;Docker MCP server&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p id="25a1"&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Developers, DevOps engineers, platform teams managing containerized workloads&lt;/p&gt;
&lt;p id="558f"&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; Container lifecycle management, image inspection and vulnerability queries, compose stack operations, volume and network status, registry integration&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p id="c0fc"&gt;Docker’s official mcp-servers repo is actively maintained and covers the workflows that eat developer time during local and staging environment debugging. Container lifecycle management, image inspection, compose operations, volume status. Less critical for pure cloud-native teams running everything in managed Kubernetes essential for everyone else. The image vulnerability query support is a quiet win: ask your agent whether a base image has known CVEs before you promote it to prod.&lt;/p&gt;
&lt;h3 id="624b"&gt;ArgoCD MCP server&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p id="987c"&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Platform engineers and DevOps teams running GitOps workflows&lt;/p&gt;
&lt;p id="1181"&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; Application sync status and health checks, rollback triggers, Git diff views, multi-cluster app visibility, sync policy inspection&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p id="03bf"&gt;For the GitOps-pilled crowd. Sync status, application health, rollback triggers, diff views, multi-cluster visibility all queryable without opening the ArgoCD UI. If your team is already living in ArgoCD, having your agent able to query app state and initiate syncs is the kind of quality-of-life improvement that’s hard to go back from. The multi-cluster visibility is what bumps this from useful to genuinely powerful one prompt to check sync health across all your environments instead of clicking through cluster after cluster.&lt;/p&gt;
&lt;h3 id="8a3b"&gt;PagerDuty MCP server&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p id="987a"&gt;&lt;strong&gt;Best for:&lt;/strong&gt; SREs, on-call engineers, engineering managers tracking reliability trends&lt;/p&gt;
&lt;p id="8612"&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; Incident lookup and acknowledgment, escalation policy queries, on-call schedule inspection, MTTR and incident pattern analysis, service dependency mapping&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p id="2cf4"&gt;The middle-of-the-night use case is obvious. Less obvious: using it proactively during business hours to understand incident patterns, MTTR trends, and which services are generating the most noise. The data has always been there. Now your agent can actually read it, surface the signal, and help you make the case for reliability investment before the next big outage makes that case for you instead.&lt;/p&gt;
&lt;h3 id="e8ef"&gt;HashiCorp Vault MCP server&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p id="43df"&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Security engineers, platform teams, anyone running secrets management at scale&lt;/p&gt;
&lt;p id="0b84"&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; Secrets engine status, policy inspection, lease and token management, audit log queries, PKI certificate status without secrets ever entering LLM context&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p id="d05e"&gt;The most security-sensitive entry on the list and the one that requires the most care. The critical design detail: the Vault MCP server is built so your agent can reason about secrets infrastructure policy coverage, lease expiry, audit anomalies without the secrets themselves ever hitting the LLM context. That’s not a minor implementation detail. That’s the whole security model. If you’re running Vault, understand this boundary before you deploy. The capability is genuinely useful. The risk surface, if misconfigured, is genuinely serious.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="2066"&gt;How to pick the right ones without breaking your agent&lt;/h2&gt;
&lt;p id="66aa"&gt;Here’s the temptation: you read a list like that, get excited, and install all ten. I get it. It feels like power-ups. More servers, smarter agent, better DevOps. That’s not how this works.&lt;/p&gt;
&lt;p id="69f4"&gt;Context window bloat is real. Every MCP server you connect adds tool definitions your agent has to reason about on every request. Past a certain point, you’re not giving your agent more capability you’re giving it more noise to filter through before it can do anything useful. The analogy that fits: this is exactly what happens when you install every VS Code extension you’ve ever found interesting. Individually they all made sense. Collectively your editor takes 40 seconds to open and the autocomplete is fighting itself.&lt;/p&gt;
&lt;p id="7548"&gt;Start with three. Pick based on where your team actually loses time, not based on what sounds impressive.&lt;/p&gt;
&lt;p id="3c71"&gt;The framework that works: &lt;strong&gt;observability first, then infra layer, then your SCM&lt;/strong&gt;. If you’re on Datadog, start there incident triage is where the ROI is most immediate and most obvious. Pair it with either the AWS or Kubernetes server depending on where your infra actually lives. Then add GitHub. That trio covers the majority of real DevOps workflows: something breaks, you find it, you trace it back to a change, you fix it. Three servers, full loop.&lt;/p&gt;
&lt;p id="222c"&gt;What does your team spend the most time doing manually during an incident? Answer that honestly and the right servers become obvious. If you’re spending 20 minutes per incident correlating metrics to deploys, Datadog plus GitHub is your first move. If you’re constantly SSHing into nodes to describe pods, Kubernetes MCP clears that up fast. If your Terraform state drift is a recurring problem, that one pays for itself in the first week.&lt;/p&gt;
&lt;p id="fc4d"&gt;Security and read-only modes matter more than people realize upfront. The Vault server especially deploy it wrong and you’ve created a problem that’s worse than the one you solved. Start every new MCP server integration in read-only or inspection mode where the option exists. Let your team build trust with it before you enable write operations. The Kubernetes server’s read-only mode exists for exactly this reason, and it’s worth using it for longer than feels necessary.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="437" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1050%2F1%2ASChSKlyawCphs5CqPff0DQ.jpeg"&gt;&lt;p id="2fab"&gt;The &lt;a href="https://github.com/modelcontextprotocol/servers" rel="noopener ugc nofollow noreferrer"&gt;MCP community server registry&lt;/a&gt; is worth bookmarking for what comes next it’s growing fast and the quality has gone up considerably since the early days of everyone publishing half-finished experiments. But don’t chase the registry. Chase your actual bottlenecks.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="9760"&gt;What this means for the SRE role (the honest version)&lt;/h2&gt;
&lt;p id="1b4c"&gt;Let’s not do the thing where we pretend this is all upside with no complexity. Every time tooling gets significantly more powerful, the role around it shifts and MCP-connected agents are a meaningful shift, not a incremental one.&lt;/p&gt;
&lt;p id="6434"&gt;The boring parts of SRE work are going first. Alert triage, metric correlation, runbook execution, postmortem drafting, on-call handoff summaries these are the tasks that eat hours without requiring the judgment that actually makes a senior SRE valuable. An agent with the right MCP servers connected can handle a meaningful chunk of that loop already. Not perfectly. Not without oversight. But well enough that the humans in the rotation are spending less time on the mechanical parts and more time on the parts that actually require thinking.&lt;/p&gt;
&lt;p id="abb9"&gt;That sounds like a win. It mostly is. But it also means the SRE who isn’t learning to work with these tools is slowly becoming the SRE who’s doing the parts the agent can’t do yet which, right now, is still a lot, but the list is shrinking faster than most people are comfortable admitting.&lt;/p&gt;
&lt;p id="68b1"&gt;The SRE who learns to orchestrate agents with MCP is worth significantly more than the one who doesn’t. That’s not a hot take, it’s just the same pattern we’ve seen every time a layer of abstraction gets good enough to trust. The engineers who understood Docker when it was new didn’t get replaced they became the people who designed the container strategy everyone else ran. Same thing happened with Kubernetes. Same thing is happening here.&lt;/p&gt;
&lt;p id="8b5e"&gt;What’s actually changing underneath all of this is the model of operations itself. We’re moving toward what some teams are already calling intent-based ops you describe the outcome you want, the agent figures out the sequence of tool calls to get there, and you review and approve rather than execute manually. The tooling is already capable of this in constrained, well-defined workflows. The cultural shift trusting an agent to page you back instead of being the one holding the pager is taking longer, which is probably the right pace.&lt;/p&gt;
&lt;p id="e567"&gt;The SRE subreddit and Hacker News threads on this are genuinely split. Half the comments are engineers who’ve connected a Kubernetes and Datadog MCP server and won’t stop talking about the time it saved during a recent incident. The other half are people pointing out, correctly, that agents with write access to production infrastructure are a new and interesting category of incident cause. Both camps are right. The answer isn’t to avoid the tools it’s to deploy them with the same discipline you’d apply to any system that can affect prod.&lt;/p&gt;
&lt;p id="77c0"&gt;The teams that figure that balance out early are going to have a real advantage. Not because the tools are magic, but because operating leverage compounds. Every hour an agent saves on mechanical SRE work is an hour a human engineer spends on reliability improvements, architecture, and the judgment calls that still require a person. That gap widens over time. And the teams on the wrong side of it will feel it before they understand why.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="b286"&gt;Where this is all going (and what you should do Monday morning)&lt;/h2&gt;
&lt;p id="d424"&gt;I’ll be honest a year ago I would have filed “AI agents in DevOps” under vaporware and moved on. The demos were always impressive and the production reality was always messier. The model would hallucinate a flag, or confidently suggest a command that hadn’t existed since Kubernetes 1.18, and you’d remember why you still had a human on-call.&lt;/p&gt;
&lt;p id="ac84"&gt;MCP changed the calculus. Not because the models got dramatically smarter though they did but because they finally got eyes. The fundamental problem was never intelligence. It was context. And when you give an agent live access to your actual infrastructure through a standardized protocol, the gap between “impressive demo” and “this is genuinely running in our incident workflow” closes faster than expected.&lt;/p&gt;
&lt;p id="3a7c"&gt;We’re early, but not as early as it feels. GitHub, AWS, Docker, HashiCorp, Datadog these aren’t startups experimenting with MCP. These are the companies whose tools your team already depends on, shipping official integrations because they’ve decided this is the direction. That’s a different signal than hype. That’s ecosystem commitment.&lt;/p&gt;
&lt;p id="6665"&gt;The uncomfortable truth is that the tooling is ready before most teams are. The cultural and operational trust required to let an agent acknowledge a PagerDuty alert, correlate it to a Datadog spike, trace it to a GitHub commit, and propose a rollback that workflow is technically possible today. Whether your team is ready to trust it is a different question, and it’s a legitimate one. Rushing that trust is how you create a new category of production incident.&lt;/p&gt;
&lt;p id="028c"&gt;So here’s the honest version of what to do with all of this: pick one server, pick one workflow, and run it in read-only mode for two weeks. Not ten servers, not a full agentic pipeline, not a rewrite of your incident process. One server, one workflow, real data. Let your team build intuition for what the agent gets right and where it still needs a human in the loop. That intuition is what makes everything else safe to scale.&lt;/p&gt;
&lt;p id="8a34"&gt;The teams who figure this out aren’t going to replace their SREs. They’re going to make their SREs unreasonably effective. And in an industry where reliability engineering talent is expensive and incidents are expensive and toil is quietly demoralizing unreasonably effective is a meaningful competitive advantage.&lt;/p&gt;
&lt;p id="09f0"&gt;MCP servers aren’t the final form of AI in DevOps. They’re the connective tissue that makes the whole vision coherent. What comes after fully autonomous remediation, self-healing infrastructure, agents that close the loop without human approval on routine fixes that’s still being figured out. But it starts here. With a protocol, a server, and an agent that can finally see what’s actually happening in your cluster.&lt;/p&gt;
&lt;p id="64fc"&gt;What’s the first MCP server your team would reach for? Drop it in the comments genuinely curious whether the split is Kubernetes vs Datadog or if GitHub is the obvious first move for most teams.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="dc29"&gt;Helpful resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li id="9102"&gt;&lt;a href="https://docs.anthropic.com/en/docs/agents-and-tools/mcp" rel="noopener ugc nofollow noreferrer"&gt;Anthropic MCP documentation&lt;/a&gt;&lt;/li&gt;
&lt;li id="6adb"&gt;&lt;a href="https://github.com/github/github-mcp-server" rel="noopener ugc nofollow noreferrer"&gt;GitHub MCP server&lt;/a&gt;&lt;/li&gt;
&lt;li id="42f2"&gt;&lt;a href="https://github.com/awslabs/mcp" rel="noopener ugc nofollow noreferrer"&gt;AWS Labs MCP&lt;/a&gt;&lt;/li&gt;
&lt;li id="3433"&gt;&lt;a href="https://github.com/strowk/mcp-k8s-go" rel="noopener ugc nofollow noreferrer"&gt;Kubernetes MCP server (mcp-k8s-go)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Anthropic’s engineer just told you to stop using markdown. Here’s what’s actually going on.</title>
      <dc:creator>&lt;devtips/&gt;</dc:creator>
      <pubDate>Wed, 10 Jun 2026 18:04:57 +0000</pubDate>
      <link>https://dev.to/dev_tips/anthropics-engineer-just-told-you-to-stop-using-markdown-heres-whats-actually-going-on-2ep4</link>
      <guid>https://dev.to/dev_tips/anthropics-engineer-just-told-you-to-stop-using-markdown-heres-whats-actually-going-on-2ep4</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="475e"&gt;&lt;strong&gt;The “HTML vs Markdown” war broke the internet last week. Both sides got it wrong and the real answer was buried in the footnotes the whole time.&lt;/strong&gt;&lt;/h2&gt;
&lt;span&gt;&lt;/span&gt;&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p id="b198"&gt;Last week, the engineering lead for Claude Code dropped a post called “The Unreasonable Effectiveness of HTML” and the dev internet split in half before the coffee finished brewing.&lt;/p&gt;
&lt;p id="47dc"&gt;Thariq Shihipar who runs engineering on Anthropic’s Claude Code published 20 working examples showing why AI agents should output HTML instead of Markdown. Interactive navigation. Collapsible sections. Color-coded code reviews. Embedded visualizations. Shareable links. The post hit 4.4 million views in 16 hours.&lt;/p&gt;
&lt;p id="fd55"&gt;The response was exactly what you’d expect from a community that treats tooling preferences like religion.&lt;/p&gt;
&lt;p id="7cdc"&gt;Team HTML declared Markdown dead. Team Markdown called it a security risk wrapped in a token tax. Threads filled up. Quote-tweets went sideways. Someone definitely posted a “this is why we can’t have nice things” reply. The usual.&lt;/p&gt;
&lt;p id="bb62"&gt;&lt;strong&gt;Here’s the problem:&lt;/strong&gt; both sides were arguing about the wrong question entirely.&lt;/p&gt;
&lt;p id="2ba7"&gt;The HTML camp got the direction right but hand-waved the costs the 3–5x token overhead, the AI-generated JavaScript risks, and the slightly awkward fact that Anthropic profits directly from you using more tokens. The Markdown camp identified real risks but is defending a set of constraints that haven’t been real since context windows hit a million tokens. They’re optimizing for a 2022 problem in a 2026 world.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="c23d"&gt;The actual question the one neither camp bothered asking is simpler than any of that: &lt;em&gt;who reads this output, and what do they do with it?&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="7a48"&gt;That’s it. That’s the whole framework. Everything else is noise.&lt;/p&gt;
&lt;p id="ea9b"&gt;So let’s talk about how Markdown became the default, why three of its core assumptions are quietly rotting, what the token math actually looks like when you run it, and why the format war was always a distraction from the decision tree that was sitting there the whole time.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="ac16"&gt;How markdown became the default AI output (nobody chose it it was inherited)&lt;/h2&gt;
&lt;p id="8ba5"&gt;Markdown didn’t win a format war. It just kept showing up at the right time, three times in a row, until nobody questioned it anymore.&lt;/p&gt;
&lt;p id="d1b6"&gt;The first wave was developers. John Gruber built &lt;a href="https://daringfireball.net/projects/markdown/" rel="noopener ugc nofollow noreferrer"&gt;Markdown in 2004&lt;/a&gt; as a way to write readable plain text that converted cleanly to HTML. Convenient tool for bloggers. Then GitHub adopted it for READMEs, issues, and pull request descriptions and overnight, every open-source project on earth was writing Markdown. Not because it was evaluated and selected. Because it was already there.&lt;/p&gt;
&lt;p id="5795"&gt;The second wave was knowledge workers. Through the 2010s, Notion, Obsidian, and Jekyll built their entire editing experience around it. It became the default for wikis, note-taking, and static sites. The pitch was the same every time: human-readable &lt;em&gt;and&lt;/em&gt; machine-parseable. Write it in any text editor, render it anywhere. Simple enough that anyone could pick it up in an afternoon, powerful enough that you never really needed anything else.&lt;/p&gt;
&lt;p id="d09f"&gt;The third wave was AI. When ChatGPT launched in late 2022, it rendered responses in Markdown. Not because OpenAI ran a format evaluation. Because the training data was saturated with it GitHub repos, technical docs, wikis, blog posts, READMEs as far as the eye could see. Markdown was what the model had seen most, so Markdown was what the model produced. Every chatbot and coding assistant since has followed the same default.&lt;/p&gt;
&lt;p id="db10"&gt;I still have READMEs in repos from 2016 that look structurally identical to my Claude outputs today. Same heading hierarchy. Same bullet pattern. Same code block style. That should’ve been the first clue that something was on autopilot.&lt;/p&gt;
&lt;p id="1b12"&gt;Three waves. Each one reinforcing the last. Nobody evaluated Markdown for AI output and decided it was the best fit. It inherited the job because it was already wearing the right shirt from the previous three interviews.&lt;/p&gt;
&lt;p id="3ce0"&gt;That inheritance is the problem. Because the world Markdown was designed for and the world we’re actually building in now are not the same world. And three assumptions that were completely reasonable when Markdown took over are breaking at the same time.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="382" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2Au0mmzwVxQi2NvHFJy6Dr5w.jpeg"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="ed8d"&gt;Three assumptions baked into markdown that are quietly rotting&lt;/h2&gt;
&lt;p id="efc2"&gt;Markdown became the default AI output under three premises. All three made sense in 2022. None of them really hold in 2026.&lt;/p&gt;
&lt;p id="a904"&gt;&lt;strong&gt;Premise 1: Humans edit the output.&lt;/strong&gt;&lt;/p&gt;
&lt;p id="2397"&gt;Markdown was designed for people who write and revise their own text. That’s still how READMEs, docs, and blog posts work someone opens the file, rewrites a paragraph, pushes a commit. But agent output is different. You send a prompt. The agent generates a 2,000-word implementation plan, a code review, a competitive analysis. You read it. Maybe you share it. You almost never open it in an editor and start rewriting paragraphs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p id="b1d7"&gt;When was the last time you actually did that?&lt;/p&gt;
&lt;p id="8da7"&gt;Took a Claude output, opened it in VS Code, and edited the prose?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p id="79ba"&gt;The format’s core value proposition easy to write and revise by hand no longer matches the actual use case. The agent wrote it. You’re just the reader now.&lt;/p&gt;
&lt;p id="ba15"&gt;&lt;strong&gt;Premise 2: Content is small.&lt;/strong&gt;&lt;/p&gt;
&lt;p id="b66a"&gt;A 500-word doc renders fine in Markdown. A 3,000-word agent-generated architecture decision with trade-off tables, code samples, and implementation notes does not. Past roughly 100 lines, Markdown becomes a wall. No navigation, no collapsible sections, no way to jump to the part you actually care about without scrolling through everything you don’t.&lt;/p&gt;
&lt;p id="fa9a"&gt;Thariq’s observation on this is blunt: nobody really reads a Markdown file longer than 100 lines. They skim, miss things, and close it. The format that was perfect for a README is actively fighting you when the output is a full technical report.&lt;/p&gt;
&lt;p id="849a"&gt;&lt;strong&gt;Premise 3: Output is read-only.&lt;/strong&gt;&lt;/p&gt;
&lt;p id="7edb"&gt;The old workflow was linear.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="c473"&gt;Prompt → generate → read → close.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="3503"&gt;Done. But the agent era is pushing toward something different. Filter a table. Adjust a parameter. Compare two options side by side. Export a subset. Feed the result back into the next prompt as structured input. Markdown can’t carry any of that. It’s a one-way street with no exits.&lt;/p&gt;
&lt;p id="62de"&gt;Here’s the reframe that cuts through all three premises at once: Markdown is a report. HTML is an interface. You read a report and close it. You operate on an interface and feed the result forward.&lt;/p&gt;
&lt;p id="86c3"&gt;That distinction matters more than any token cost calculation. But since token cost is the number everyone keeps citing, let’s actually run it.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="382" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2A4R5l7wt-WbhjBnhRRM4j8A.jpeg"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="c1c5"&gt;The token math nobody actually ran&lt;/h2&gt;
&lt;p id="2a24"&gt;The main ammunition Team Markdown keeps loading is the token overhead. HTML costs 3–5x more tokens. They say it like it ends the conversation.&lt;/p&gt;
&lt;p id="f96e"&gt;Almost nobody checks what that actually means in dollars.&lt;/p&gt;
&lt;p id="717a"&gt;Same 2,000-word report, three formats. Plain Markdown comes in around 3,000 output tokens. Lean semantic HTML proper structure, no heavy styling runs about 7,200. Full HTML with CSS, embedded charts, and interactive sections hits roughly 14,400. The “3–5x” range you’ve seen quoted is real. For rich HTML, you’re burning close to 5x the tokens.&lt;/p&gt;
&lt;p id="da33"&gt;&lt;strong&gt;Here’s what that costs per report at current &lt;/strong&gt;&lt;a href="https://www.anthropic.com/pricing" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Anthropic API pricing&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li id="6f9b"&gt;Markdown report: ~$0.072 on Claude Sonnet&lt;/li&gt;
&lt;li id="3d17"&gt;Lean HTML: ~$0.17&lt;/li&gt;
&lt;li id="58d6"&gt;Full HTML with styling: ~$0.34&lt;/li&gt;
&lt;/ul&gt;
&lt;p id="402b"&gt;The overhead on a single HTML report is less than the electricity cost to charge the phone you’re reading this on.&lt;/p&gt;
&lt;p id="e241"&gt;You need to generate 171 HTML reports on Claude Sonnet to spend one extra dollar compared to Markdown. One dollar. That’s the number people are building their entire format philosophy around.&lt;/p&gt;
&lt;p id="a41b"&gt;This is what I’d call the Token Trap. Optimizing for a cost that’s a rounding error in your actual engineering budget while ignoring the cost that actually matters.&lt;/p&gt;
&lt;p id="759a"&gt;But the math has a second act, and Team Markdown deserves credit for it.&lt;/p&gt;
&lt;p id="eea9"&gt;Scale it up and the numbers shift. At 100 reports per day, the HTML overhead on Claude Sonnet runs roughly $500 a month extra. At enterprise volume — thousands of agent calls daily across a whole platform you’re looking at real line items, not pocket change. Team Markdown isn’t wrong about this. They’re just applying it everywhere instead of where it actually applies.&lt;/p&gt;
&lt;p id="8054"&gt;Here’s what both camps keep skipping: human attention has a price too.&lt;/p&gt;
&lt;p id="a77a"&gt;A senior engineer earns somewhere between $75 and $150 an hour. Fifteen minutes spent scrolling a Markdown wall hunting for the architecture decision buried in paragraph nine, re-reading a table that should have been filterable, copy-pasting a section into Slack because there’s no shareable link costs between $19 and $38 in engineer time.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="0d7c"&gt;The token overhead for that same report in HTML? Seventeen cents on Sonnet.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="79bd"&gt;The Token Trap runs in both directions. Individual developers waste time debating $0.17. Enterprise teams burn thousands in engineer attention to save hundreds in token costs. In both cases, the format decision is being made on the wrong variable entirely.&lt;/p&gt;
&lt;p id="a7d7"&gt;The right variable is simpler. It’s not what the tokens cost. It’s who reads the output and what they do with it.&lt;/p&gt;
&lt;p id="e3dd"&gt;Which is exactly where we’re going next.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="382" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2A1TM-TOKLstPBNt8RUxIXtA.jpeg"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="f3d5"&gt;The decision tree that ends the format war&lt;/h2&gt;
&lt;p id="c462"&gt;Every agent output has one of three audiences. The format choice follows directly from which one you’re dealing with. That’s the whole framework.&lt;/p&gt;
&lt;p id="043d"&gt;&lt;strong&gt;Reader 1: A human.&lt;/strong&gt;&lt;/p&gt;
&lt;p id="0b9b"&gt;Your stakeholder opens a browser tab. They scan for the section they care about, screenshot a chart for Slack, share a link with the team, click through a collapsible architecture section without reading the parts that don’t apply to them. This is the use case Thariq built 20 examples around code reviews with inline severity colors, implementation plans with jump navigation, design system comparisons with live swatches you can actually interact with.&lt;/p&gt;
&lt;p id="61c9"&gt;HTML wins here because the output is a destination. The reader navigates it, operates on it, shares it forward. Markdown flattens all of that into a scroll and hopes for the best.&lt;/p&gt;
&lt;p id="2f64"&gt;&lt;strong&gt;Reader 2: Another agent.&lt;/strong&gt;&lt;/p&gt;
&lt;p id="aa34"&gt;Your output feeds a downstream pipeline. An agent reads the analysis, extracts structured data, makes a decision, triggers the next step. No human ever sees it. This is where Markdown still wins cleanly lightweight, parseable, diffable, and processable without any rendering overhead. Other models consume it without friction. Git tracks it. CI pipelines process it without choking.&lt;/p&gt;
&lt;p id="df24"&gt;Using HTML for agent-to-agent communication is like printing a spreadsheet, laminating it, and handing it to someone who’s going to retype all the numbers anyway.&lt;/p&gt;
&lt;p id="5104"&gt;&lt;strong&gt;Reader 3: Both.&lt;/strong&gt;&lt;/p&gt;
&lt;p id="726c"&gt;This is the most common case in real engineering workflows, and it’s the one neither camp bothers addressing. A developer generates a PR review they read it themselves, and they also want it tracked in the repo. A team lead generates a weekly status report stakeholders view it in the browser, and the data feeds into next week’s planning prompt. Human and machine, same output, different needs.&lt;/p&gt;
&lt;p id="8002"&gt;The answer here isn’t picking a side. It’s: Markdown source, HTML artifact.&lt;/p&gt;
&lt;p id="3502"&gt;Keep Markdown as the editable, diffable, git-tracked source of truth. Generate an HTML companion for the humans who need to navigate and share it. This is actually what Thariq recommends in his own post it got buried under the tribal response, but it’s in there: keep Markdown in repositories, generate HTML as the companion artifact for review.&lt;/p&gt;
&lt;p id="d01c"&gt;Both camps were arguing against a recommendation that never said what they thought it said.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="8c63"&gt;The decision tree is three questions. Does only a human read this?&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="9fcb"&gt;Use HTML. Does only an agent read this? Use Markdown. Do both read it?&lt;/p&gt;
&lt;p id="9953"&gt;Markdown source, HTML artifact. Screenshot that and you never have to read another format war thread again.&lt;/p&gt;
&lt;p id="6cfd"&gt;The framework is clean. The real world, predictably, is not.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="6775"&gt;Where it actually breaks and who profits from this shift&lt;/h2&gt;
&lt;p id="321a"&gt;The decision tree is clean. Before you rewrite your &lt;code&gt;CLAUDE.md&lt;/code&gt; to default HTML output, here are the risks Team Markdown got right and one they didn't mention at all.&lt;/p&gt;
&lt;p id="fda2"&gt;&lt;strong&gt;Security is the real concern, not the token bill.&lt;/strong&gt;&lt;/p&gt;
&lt;p id="a0bf"&gt;AI-generated HTML can include JavaScript. JavaScript means potential XSS vulnerabilities, local data leaks, and code execution you never asked for and definitely didn’t audit. This isn’t a theoretical edge case. If you’re generating HTML for internal tools, dashboards, or anything that touches real user data, you need either a strict no-JS constraint baked into your prompt or a review step before anything hits production.&lt;/p&gt;
&lt;p id="6d74"&gt;Thariq’s own guidelines for generating HTML are pretty direct about this: no external CDN links, no unpkg imports, system fonts only, zero network calls at runtime. The vision is clean. The default behavior of most AI-generated HTML is not. You have to prompt for the guardrails explicitly, and most people don’t.&lt;/p&gt;
&lt;p id="53b1"&gt;&lt;strong&gt;Accessibility doesn’t come for free.&lt;/strong&gt;&lt;/p&gt;
&lt;p id="737a"&gt;AI-generated HTML misses WCAG compliance by default. No alt text on images, inconsistent focus order, contrast ratios that would fail a basic audit. If your outputs go anywhere near a public-facing interface or a team with accessibility requirements, you have to ask for it explicitly WCAG 2.2 AA, descriptive alt text, logical focus order, minimum 4.5:1 contrast. It’s solvable. It’s just not automatic, and the HTML enthusiasm tends to skip this part entirely.&lt;/p&gt;
&lt;p id="18ee"&gt;&lt;strong&gt;Reviewability needs a pattern, not a format change.&lt;/strong&gt;&lt;/p&gt;
&lt;p id="7139"&gt;HTML diffs are noisy. A one-line content change can generate 50 lines of diff because surrounding markup shifts. For teams that live in pull requests, this is real friction. The fix is the template-plus-data pattern keep the HTML structure static, store variable content in a JSON payload, diff only the JSON. Clean version control, rich visual output. Slightly more setup. Worth it if your team reviews agent output in git.&lt;/p&gt;
&lt;p id="add9"&gt;&lt;strong&gt;Now the part most coverage skipped.&lt;/strong&gt;&lt;/p&gt;
&lt;p id="eeb3"&gt;Anthropic profits directly from this shift. HTML output burns 3–5x more tokens than Markdown. More tokens means more API revenue. And beyond the immediate billing, HTML output creates ecosystem stickiness once your team builds workflows around Claude-generated interactive reports and dashboards, switching to another model means rebuilding all of those workflows from scratch.&lt;/p&gt;
&lt;p id="f675"&gt;This isn’t a conspiracy theory. It’s just an incentive structure worth understanding before you adopt a recommendation wholesale. The engineer making the case works at the company that gets paid per token. That doesn’t make him wrong. Thariq’s examples are genuinely compelling and the use cases are real.&lt;/p&gt;
&lt;p id="af90"&gt;It just means you should read the footnotes.&lt;/p&gt;
&lt;p id="9247"&gt;The argument for HTML is strong for the right contexts. The tooling, the guardrails, and the security patterns are still catching up to the vision. Both things are true at the same time.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="9ac3"&gt;Markdown isn’t dying. It’s being promoted.&lt;/h2&gt;
&lt;p id="8830"&gt;Here’s the reframe that actually resolves this: Markdown was always better as a machine-readable format than a human-readable one. The agent era just made that obvious.&lt;/p&gt;
&lt;p id="c242"&gt;Think about what Markdown actually is at its core. Structured plain text with lightweight syntax. Easy to parse, easy to diff, easy to version control, easy to feed into the next system in the pipeline. That’s not a display format. That was always a protocol. We just kept using it as a display format because nothing better had shown up yet for the AI output layer and because the training data made it the path of least resistance.&lt;/p&gt;
&lt;p id="bc44"&gt;HTML isn’t the future of everything. It’s the future of output that a human actually needs to read, navigate, and act on. For everything else agent pipelines, git-tracked docs, machine-consumed reports, anything that feeds forward into another model Markdown stays. It just stops pretending to be something it isn’t.&lt;/p&gt;
&lt;p id="d153"&gt;The skill that actually matters now isn’t picking the right format. It’s knowing your reader before you write the prompt. Everything else follows from that one question. Who opens this output? What do they do with it? Do they scroll it, share it, diff it, or pipe it into something else? Answer that and the format choice becomes obvious every time.&lt;/p&gt;
&lt;p id="980e"&gt;The format war was always a distraction. Two camps arguing about tooling aesthetics while the actual decision was sitting there quietly the whole time, waiting for someone to ask the right question.&lt;/p&gt;
&lt;p id="f319"&gt;Thariq’s post wasn’t a declaration of war on Markdown. It was a reminder that the default was never chosen it was inherited. And inherited defaults are worth questioning, especially when the use cases have moved on.&lt;/p&gt;
&lt;p id="c842"&gt;So question it. Not because an Anthropic engineer said to. Because your outputs are being read by humans who deserve better than a 3,000-word scroll with no navigation, and by agents who deserve clean structured text without presentation overhead baked in.&lt;/p&gt;
&lt;p id="e26b"&gt;Give each reader what they actually need. That’s the whole job.&lt;/p&gt;
&lt;p id="49fd"&gt;Drop a comment if your team has already made the switch or if you’ve hit the security issues nobody’s talking about yet. Would genuinely like to know what patterns people are landing on in production.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="de0b"&gt;&lt;strong&gt;Resources&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li id="cf97"&gt;&lt;a href="https://daringfireball.net/projects/markdown/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;John Gruber&lt;/strong&gt; Markdown spec&lt;/a&gt;&lt;/li&gt;
&lt;li id="cd92"&gt;&lt;a href="https://www.anthropic.com/pricing" rel="noopener ugc nofollow noreferrer"&gt;Anthropic API pricing&lt;/a&gt;&lt;/li&gt;
&lt;li id="1a06"&gt;&lt;a href="https://www.w3.org/TR/WCAG22/" rel="noopener ugc nofollow noreferrer"&gt;WCAG 2.2 guidelines&lt;/a&gt;&lt;/li&gt;
&lt;li id="e8ca"&gt;&lt;a href="https://news.ycombinator.com" rel="noopener ugc nofollow noreferrer"&gt;Hacker News thread on the HTML post&lt;/a&gt;&lt;/li&gt;
&lt;li id="e8e8"&gt;&lt;a href="https://dev.to" rel="noopener ugc nofollow"&gt;Dev.to community response&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>claude</category>
    </item>
    <item>
      <title>Go didn’t ask for permission. It just took over.</title>
      <dc:creator>&lt;devtips/&gt;</dc:creator>
      <pubDate>Tue, 09 Jun 2026 04:11:05 +0000</pubDate>
      <link>https://dev.to/dev_tips/go-didnt-ask-for-permission-it-just-took-over-153o</link>
      <guid>https://dev.to/dev_tips/go-didnt-ask-for-permission-it-just-took-over-153o</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="632d"&gt;&lt;strong&gt;The backend language nobody hyped is now running the infrastructure everyone depends on. Here’s why that happened and what it means for your stack.&lt;/strong&gt;&lt;/h2&gt;
&lt;span&gt;&lt;/span&gt;&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p id="cb2e"&gt;There’s a specific kind of dread that hits when you’re onboarding onto a new team and you open the services directory for the first time. You expect Java. You expect some Node mess with 400 packages and a &lt;code&gt;package-lock.json&lt;/code&gt; that hasn't been touched since 2021. Maybe Python somewhere doing something it shouldn't be doing.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="4176"&gt;Instead you find &lt;code&gt;.go&lt;/code&gt; files. Everywhere.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="f03a"&gt;No Spring Boot XML. No &lt;code&gt;node_modules&lt;/code&gt; folder the size of a small country. Just clean, flat directories and a binary that builds in four seconds. You ask your teammate when they migrated. He shrugs. "We just started writing new services in Go. Nobody made a decision. It kind of happened."&lt;/p&gt;
&lt;p id="fd2e"&gt;That’s the Go story. No conference keynote. No influencer push. No “Go is the future of everything” Medium posts going viral in 2019. Just engineers, quietly choosing it when the old stuff started creaking and never looking back.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="491" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2AXXinfq_mnLvpyydzKWm5jA.jpeg"&gt;&lt;blockquote&gt;&lt;p id="344d"&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Go didn’t win on syntax. It didn’t win a design award or top a “most loved language” poll. It won because it’s cheap to run, fast to deploy, and terrifyingly good at concurrency. When your cloud bill is real and your Kubernetes cluster isn’t infinite, those things start to matter more than elegant abstractions.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="1b0c"&gt;This article is about why the traditional backend stack Java, Node, Python is losing ground, why Go is filling the gap, and whether any of this is actually a problem.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="ceff"&gt;The stack that made sense until it didn’t&lt;/h2&gt;
&lt;p id="64fa"&gt;For a long time, the backend language decision was basically a personality quiz.&lt;/p&gt;
&lt;p id="d819"&gt;Are you an enterprise company with a procurement team and a strong opinion about XML? Java. Are you a startup that needs to ship in three weeks and your whole team came from frontend? Node.js. Are you doing data work, scripts, or anything that touches a model? Python. This wasn’t laziness it was legitimate. These stacks had ecosystems, hiring pools, Stack Overflow answers, and years of production battle-testing behind them.&lt;/p&gt;
&lt;p id="41e6"&gt;The problem wasn’t the languages. The problem was the world they were designed for quietly stopped existing.&lt;/p&gt;
&lt;p id="af81"&gt;The old assumptions were reasonable. Applications were mostly monoliths. Deployments happened weekly, maybe monthly. Memory was expensive so you thought about it, but you weren’t running 40 microservices on a shared Kubernetes cluster where every megabyte shows up on a bill. Concurrency was an advanced topic, not a default requirement. You wrote your servlet, deployed your WAR file, went home.&lt;/p&gt;
&lt;p id="5095"&gt;Modern systems blew all of that up.&lt;/p&gt;
&lt;p id="9643"&gt;Now your backend is expected to spin up in milliseconds because Kubernetes will kill and reschedule your pod without warning. It needs to handle thousands of concurrent connections because that’s just Tuesday traffic. It has to run in a container small enough that you can actually afford the node pool. And it needs to deploy 15 times a day because the team runs CI/CD and nobody’s waiting for a release window.&lt;/p&gt;
&lt;p id="2b89"&gt;Java started showing its age first. Not because Java is bad it’s still an engineering powerhouse but because the JVM was built for a world where you had one big long-running process, not a fleet of tiny short-lived containers. A simple microservice doing nothing interesting could eat 800MB to 1.4GB of RAM just warming up. Cold starts on a fresh container pod weren’t milliseconds, they were seconds. At scale, that’s not a performance inconvenience. That’s an infrastructure cost problem with a line item.&lt;/p&gt;
&lt;p id="43b3"&gt;Node had a different flavor of the same issue. The event loop is genuinely clever for I/O-heavy work. But the moment you put CPU pressure on it image processing, real-time scoring, anything computationally non-trivial it starts struggling in ways that feel personal. Single-threaded by default. Async complexity that compounds over time into callback archaeology. A &lt;code&gt;node_modules&lt;/code&gt; folder that is functionally its own biome. Node is great at what it's great at. The issue is teams kept using it for things it was never meant to do.&lt;/p&gt;
&lt;p id="e5b9"&gt;Python’s situation is more nuanced because Python earns its keep in data and ML. But for backend services under load, the GIL is a real ceiling, and horizontal scaling through brute-force container replication gets expensive fast. More containers, more workers, more infrastructure all to compensate for what the runtime isn’t doing for you.&lt;/p&gt;
&lt;p id="7d9f"&gt;The stacks weren’t wrong. They just accumulated assumptions like technical debt invisibly, until the bill came due.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="02ed"&gt;What Go actually is (not what you think)&lt;/h2&gt;
&lt;p id="b25e"&gt;Most developers, when they first encounter Go, file it under “simple language for simple things.” The syntax is minimal. There’s no inheritance. The standard library does a lot without asking permission. It feels almost boring compared to the type gymnastics you can do in Kotlin or the decorator magic in Python.&lt;/p&gt;
&lt;p id="be9f"&gt;That instinct is wrong. And it’s why Go keeps surprising teams who adopt it.&lt;/p&gt;
&lt;p id="57b0"&gt;Go wasn’t built to be a better scripting language or a cleaner Java. It was built at Google specifically to deal with the kind of infrastructure problems that would make most engineers quietly update their LinkedIn. Massive distributed systems. Internal platform tooling. Networking infrastructure running at a scale where a 50ms GC pause isn’t a benchmark footnote it’s an incident. The language design decisions that feel like limitations are mostly intentional constraints born from that context.&lt;/p&gt;
&lt;p id="648a"&gt;Simple isn’t a weakness. In production at scale, simple is the whole point.&lt;/p&gt;
&lt;p id="76b1"&gt;The feature that actually matters the one that gets undersold in every Go explainer is goroutines. Not because they sound cool, but because of what they cost. Traditional threads are expensive. The OS manages them, they each need their own stack, and spinning up thousands of them is a legitimate systems engineering problem. Goroutines are managed by the Go runtime, start with tiny stacks, and can be created by the hundreds of thousands on a single machine without drama.&lt;/p&gt;
&lt;pre&gt;&lt;span id="3e99"&gt;&lt;span&gt;&lt;span&gt;func&lt;/span&gt; &lt;span&gt;fetchHotelPrice&lt;/span&gt;&lt;span&gt;(hotelID &lt;span&gt;string&lt;/span&gt;, ch &lt;span&gt;chan&lt;/span&gt; Price)&lt;/span&gt;&lt;/span&gt; {&lt;br&gt;    price := supplierAPI(hotelID)&lt;br&gt;    ch &amp;lt;- price&lt;br&gt;}&lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;func&lt;/span&gt; &lt;span&gt;main&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt; {&lt;br&gt;    hotels := []&lt;span&gt;string&lt;/span&gt;{&lt;span&gt;"H1"&lt;/span&gt;, &lt;span&gt;"H2"&lt;/span&gt;, &lt;span&gt;"H3"&lt;/span&gt;, &lt;span&gt;"H4"&lt;/span&gt;, &lt;span&gt;"H5"&lt;/span&gt;}&lt;br&gt;    ch := &lt;span&gt;make&lt;/span&gt;(&lt;span&gt;chan&lt;/span&gt; Price)&lt;br&gt;    &lt;span&gt;for&lt;/span&gt; _, hotel := &lt;span&gt;range&lt;/span&gt; hotels {&lt;br&gt;        &lt;span&gt;go&lt;/span&gt; fetchHotelPrice(hotel, ch)&lt;br&gt;    }&lt;br&gt;    &lt;span&gt;for&lt;/span&gt; &lt;span&gt;range&lt;/span&gt; hotels {&lt;br&gt;        result := &amp;lt;-ch&lt;br&gt;        fmt.Println(result)&lt;br&gt;    }&lt;br&gt;}&lt;/span&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p id="98a6"&gt;&lt;em&gt;This is roughly what a real-time pricing aggregator looks like. Except in production there are 800 suppliers and the deadline is 200ms.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="ea82"&gt;That’s not clever code. That’s the point. The concurrency model is so lightweight that you stop thinking about threads as a resource to manage and start thinking about them as a default tool. For API gateways, payment retries, supplier aggregations, event fans the pattern just works, and it works cheaply.&lt;/p&gt;
&lt;p id="3e22"&gt;The other thing that quietly changes everything is the binary. Go compiles to a single static binary with no runtime dependencies. No JVM. No Python interpreter. No &lt;code&gt;node_modules&lt;/code&gt; folder that needs to follow the service around. You build it, you ship it, it runs. Containerizing a Go service produces an image that can be under 20MB if you're not doing anything unusual. That sounds like a nerdy trivia point until you're managing a 40-service cluster and your node costs drop noticeably.&lt;/p&gt;
&lt;p id="83f1"&gt;Go binaries are basically the static sites of compiled programs. Annoying to debug sometimes, but they just go.&lt;/p&gt;
&lt;p id="dba2"&gt;The minimal standard library deserves a mention too. A production-ready HTTP server in Go is genuinely small a few imports, a handler function, &lt;code&gt;ListenAndServe&lt;/code&gt;. No framework required. No magic middleware stack you have to understand before you can read the code. That simplicity compounds over years. Go code written in 2019 is usually still readable in 2026 without a guided tour through six layers of abstraction.&lt;/p&gt;
&lt;p id="5e15"&gt;That’s not an accident. That’s what happens when the language is designed by people who spent years cleaning up the messes that cleverness creates.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="bf62"&gt;The Kubernetes gravitational pull nobody talks about&lt;/h2&gt;
&lt;p id="a180"&gt;Here’s the thing that gets left out of every “why Go is winning” conversation.&lt;/p&gt;
&lt;p id="55c2"&gt;It wasn’t just engineers choosing Go because they read a benchmark. A huge part of Go’s adoption happened passively, through tooling gravity. The infrastructure ecosystem that every backend team now runs on Kubernetes, Docker, Terraform, Prometheus, etcd, Grafana Loki is almost entirely written in Go. Not partially. Not mostly. The core of modern cloud-native infrastructure is a Go codebase.&lt;/p&gt;
&lt;p id="9cb5"&gt;That’s not a coincidence. It’s a gravitational field.&lt;/p&gt;
&lt;p id="bf59"&gt;When Kubernetes became the default deployment platform, teams started reading Kubernetes source code to understand why their pods were dying at 3am. Then they started writing operators and controllers to automate their own infra. Then someone needed to extend Prometheus. Then someone wrote a custom admission webhook. At every step, the path of least resistance was Go because the documentation assumed it, the examples used it, and the libraries were already there.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="382" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2AM_P0L4WQvy7vdZCl7Dm1Vg.jpeg"&gt;&lt;p id="787b"&gt;Go to the &lt;a href="https://landscape.cncf.io/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;CNCF landscape&lt;/strong&gt;&lt;/a&gt; and count how many of the graduated and incubating projects are written in Go. It’s not a subtle pattern. The cloud-native ecosystem essentially standardized on Go as its implementation language before most teams consciously decided to adopt it. By the time developers noticed, they were already writing it.&lt;/p&gt;
&lt;p id="22e8"&gt;I’ve had this exact experience. Opened a Kubernetes controller to understand how a custom resource was being reconciled. Needed to patch something. Ended up writing a small operator. Three months later the team had four Go services in production and nobody had sat down to make a “Go strategy” decision. The tooling pulled us in.&lt;/p&gt;
&lt;p id="b196"&gt;That’s how gravity works. You don’t choose it. You notice it after you’ve already moved.&lt;/p&gt;
&lt;p id="ee47"&gt;The Kubernetes effect also had a hiring dimension that companies underestimated. DevOps and platform engineers who lived in the Kubernetes ecosystem started becoming fluent in Go naturally. When those same engineers moved into backend roles or started influencing architecture decisions, Go came with them. It wasn’t a top-down mandate from a CTO who read a whitepaper. It was bottom-up adoption from the people who actually ran the systems.&lt;/p&gt;
&lt;p id="285e"&gt;HashiCorp built Terraform, Vault, and Consul in Go. Cloudflare uses Go extensively across their edge infrastructure and writes about it regularly. Uber migrated significant backend services to Go and published the style guide their engineers now follow. These aren’t startups chasing trends. These are companies that operate at a scale where the language choice shows up in the quarterly infrastructure bill.&lt;/p&gt;
&lt;p id="4337"&gt;The ecosystem compounded. More Go in production meant more Go libraries. More Go libraries meant less friction adopting it. Less friction meant more teams defaulting to it for new services. And once your platform team, your DevOps tooling, and your three newest backend services are all Go, the question stops being “why Go?” and starts being “why not Go?”&lt;/p&gt;
&lt;p id="c311"&gt;That shift is quiet. But it’s also basically irreversible.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="107b"&gt;The honest tradeoffs Go is not perfect&lt;/h2&gt;
&lt;p id="dc85"&gt;Let’s not do the thing where we spend 1500 words hyping a language and then add one diplomatic paragraph at the end saying “but every tool has its place.” Go has real friction. Some of it is annoying. Some of it is a legitimate reason to pick something else.&lt;/p&gt;
&lt;p id="b5ce"&gt;The most famous one is error handling. In Go, errors are just values. You check them manually, every time, at every call site. There’s no try-catch block to lean on, no exception propagation to let you ignore the problem until it blows up two layers up the stack. What you get instead is this:&lt;/p&gt;
&lt;pre&gt;&lt;span id="877c"&gt;result, err := doSomething()&lt;br&gt;&lt;span&gt;if&lt;/span&gt; err != &lt;span&gt;nil&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;return&lt;/span&gt; &lt;span&gt;nil&lt;/span&gt;, err&lt;br&gt;}&lt;br&gt;&lt;br&gt;data, err := processResult(result)&lt;br&gt;&lt;span&gt;if&lt;/span&gt; err != &lt;span&gt;nil&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;return&lt;/span&gt; &lt;span&gt;nil&lt;/span&gt;, err&lt;br&gt;}&lt;br&gt;output, err := saveData(data)&lt;br&gt;&lt;span&gt;if&lt;/span&gt; err != &lt;span&gt;nil&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;return&lt;/span&gt; &lt;span&gt;nil&lt;/span&gt;, err&lt;br&gt;}&lt;/span&gt;&lt;/pre&gt;
&lt;p id="2c5a"&gt;&lt;em&gt;Not a joke. This is a normal Tuesday in a Go codebase. You get used to it. You don’t have to like it.&lt;/em&gt;&lt;/p&gt;
&lt;p id="2669"&gt;It’s verbose. There’s no getting around that. A function that does four things will have four &lt;code&gt;if err != nil&lt;/code&gt; blocks and it will feel repetitive in a way that makes developers coming from Python or Kotlin visibly uncomfortable. The Go community has made peace with it by arguing that explicit error handling forces you to think about failure paths. That's true. It's also a lot of typing.&lt;/p&gt;
&lt;p id="fa77"&gt;Generics arrived in Go 1.18 and they’re still polarizing. Before generics, writing a reusable data structure meant either duplicating code for every type or using &lt;code&gt;interface{}&lt;/code&gt; and losing type safety. Generics fixed the worst of that but the implementation has rough edges and the community is still figuring out the idioms. If you're coming from a language with a rich type system, Go's type story feels like it's still catching up.&lt;/p&gt;
&lt;p id="e837"&gt;The minimal philosophy that makes Go readable at scale also makes it genuinely limited for certain problem domains. There’s no magic. Which means no shortcuts. Which means if you want something that doesn’t exist in the standard library, you build it or find a library and hope it’s maintained. For teams that rely on rich framework ecosystems the Spring Boots and Djangos of the world Go’s “just use the standard library” energy can feel like being handed a hammer and told to build a house.&lt;/p&gt;
&lt;p id="751b"&gt;Go is also not Python. That sounds obvious but it matters. If your backend is mostly data science glue code, model serving wrappers, or anything that touches pandas and numpy, Go will make you miserable. Python’s ML ecosystem is 15 years deep and nothing is close. Switching to Go for an ML-adjacent service isn’t engineering discipline it’s self-sabotage.&lt;/p&gt;
&lt;p id="03e9"&gt;Same story for fullstack or TypeScript-heavy teams. The Node and TypeScript ecosystem for API development has matured significantly. End-to-end type safety from database to frontend, shared types between client and server, a massive library ecosystem if that’s your world, Go doesn’t improve it. It just makes it different and harder to hire for.&lt;/p&gt;
&lt;p id="bf27"&gt;The hiring question is real at smaller companies. Go engineers exist, but the talent pool is smaller than Java or JavaScript. If you’re a 12-person startup and your entire team knows Node, rewriting services in Go because you read a blog post is how you create a bus factor problem. The language needs to match the team, not just the benchmark.&lt;/p&gt;
&lt;p id="b436"&gt;Go is a tool with a specific shape. It fits the infrastructure-heavy, concurrency-intensive, cloud-native backend world almost perfectly. It fits a lot of other worlds poorly, and it doesn’t apologize for that.&lt;/p&gt;
&lt;p id="bfb6"&gt;Knowing the difference is the actual skill.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="7f18"&gt;Where Go is quietly winning right now&lt;/h2&gt;
&lt;p id="871b"&gt;The benchmark debates are fine. The goroutine explainers are useful. But the most convincing argument for Go isn’t theoretical it’s the list of companies that bet their infrastructure on it and didn’t regret it.&lt;/p&gt;
&lt;p id="88b2"&gt;Cloudflare runs a significant portion of their edge network in Go. Their engineering blog has post after post about replacing C++ and Lua components with Go services that are easier to maintain, cheaper to operate, and fast enough that the performance tradeoff never materialized. For a company whose entire business model is “be faster than the internet,” that’s not a casual endorsement.&lt;/p&gt;
&lt;p id="25fe"&gt;Uber migrated large parts of their backend to Go and published the &lt;a href="https://github.com/uber-go/guide/blob/master/style.md" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Uber Go Style Guide&lt;/strong&gt;&lt;/a&gt; a living document their engineers actually follow in production. Not a thought leadership piece. A real internal standard that leaked into the open. Stripe’s infrastructure layer, Dropbox’s backend systems, HashiCorp’s entire product suite Terraform, Vault, Consul, Nomad all Go. These aren’t companies that adopted a trend. These are companies that operate at a scale where the wrong language choice shows up as a real number on a real bill.&lt;/p&gt;
&lt;p id="0da2"&gt;The pattern across all of them is consistent. High concurrency requirements. Cost pressure on infrastructure. Small teams relative to system complexity. Operational reliability as a non-negotiable. Go fits that profile so well it almost feels designed for it because it was.&lt;/p&gt;
&lt;p id="f623"&gt;Fintech is where Go’s adoption is probably most aggressive right now. Payment orchestration systems, fraud detection engines, real-time risk scoring these are workloads that need low latency, high throughput, and the kind of predictable performance that doesn’t degrade under load. A GC pause at the wrong moment in a payment flow isn’t a benchmark blip. It’s a failed transaction and a support ticket. Go’s runtime behavior is predictable enough that fintech teams trust it with the code path that touches money.&lt;/p&gt;
&lt;p id="0889"&gt;DevOps tooling is the other obvious stronghold. If you’re writing a CLI tool, a Kubernetes operator, a custom controller, or anything that needs to ship as a single binary across multiple platforms, Go is the default answer for most platform teams. The cross-compilation story is genuinely good one machine, one &lt;code&gt;go build&lt;/code&gt; command, binaries for Linux, Mac, and Windows. That matters when your tool needs to run everywhere without a runtime installation requirement.&lt;/p&gt;
&lt;p id="fa5a"&gt;The economic argument is the one that closes the conversation at the director level. Fewer machines for the same throughput. Smaller container images. Faster cold starts. Lower memory footprint per service. None of these are dramatic individually. Together, across a microservices architecture running 24/7 on cloud infrastructure with per-second billing, they compound into a meaningful cost difference. You don’t switch to Go because you love the syntax. You switch because the cloud bill arrives and someone opens a spreadsheet.&lt;/p&gt;
&lt;p id="acb2"&gt;The quiet part is that this adoption is still accelerating. As AI workloads get bolted onto backend infrastructure inference endpoints, embedding pipelines, real-time feature serving the runtime efficiency story gets stronger, not weaker. The services wrapping those models need to be fast, cheap, and reliable. Go keeps showing up as the right answer for that layer.&lt;/p&gt;
&lt;p id="057a"&gt;It didn’t announce itself. It just kept being useful in the places that matter.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="4336"&gt;Go didn’t win a beauty contest. It won a war of attrition.&lt;/h2&gt;
&lt;p id="9d07"&gt;There was no moment. No keynote where a charismatic founder declared Go the future and the crowd went wild. No viral framework that made every JavaScript developer suddenly want to learn a compiled language. Just engineers, one team at a time, running into the same walls with the same stacks and finding that Go had quietly already solved the problem.&lt;/p&gt;
&lt;p id="526e"&gt;That’s a different kind of winning. It’s slower. It’s less exciting to write about. But it’s also much harder to reverse.&lt;/p&gt;
&lt;p id="3a12"&gt;The traditional stacks aren’t dead. Java still runs a significant portion of the world’s financial infrastructure and it’s not going anywhere. Python owns ML and data and that grip is tightening, not loosening. Node still makes sense for teams where TypeScript is already the lingua franca and the workloads fit. None of these languages failed. They just have a new neighbor that’s better at a specific set of problems, and that neighbor keeps getting more relevant as those problems become more common.&lt;/p&gt;
&lt;p id="d48b"&gt;The industry shift that’s actually happening quietly, without a conference talk is from “which language is most expressive” to “which language keeps production stable at scale.” That question has a different answer than it did ten years ago. Go benefits from that change more than almost any other language right now.&lt;/p&gt;
&lt;p id="23dc"&gt;Here’s the slightly spicy take to end on: in five years, engineers who know Go well and understand distributed systems are going to have the same energy that senior Rails developers had in 2012. Right place, right time, right tool. The timing feels about right.&lt;/p&gt;
&lt;p id="bac7"&gt;Or I’m completely wrong and Rust takes everything. Tell me in the comments.&lt;/p&gt;
&lt;h2 id="2301"&gt;Helpful resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li id="9ec6"&gt;&lt;a href="https://go.dev/doc/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Go official documentation&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li id="a337"&gt;
&lt;a href="https://go.dev/doc/effective_go" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Effective Go&lt;/strong&gt;&lt;/a&gt; the canonical style reference&lt;/li&gt;
&lt;li id="1656"&gt;
&lt;a href="https://gobyexample.com/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Go by Example&lt;/strong&gt;&lt;/a&gt; best hands-on intro&lt;/li&gt;
&lt;li id="b949"&gt;
&lt;a href="https://github.com/uber-go/guide/blob/master/style.md" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Uber Go Style Guide&lt;/strong&gt;&lt;/a&gt; real production standards&lt;/li&gt;
&lt;li id="a0d2"&gt;&lt;a href="https://blog.cloudflare.com/tag/go/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Cloudflare engineering blog&lt;/strong&gt; Go posts&lt;/a&gt;&lt;/li&gt;
&lt;li id="1427"&gt;
&lt;a href="https://landscape.cncf.io/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;CNCF landscape&lt;/strong&gt;&lt;/a&gt; count the Go projects&lt;/li&gt;
&lt;li id="5206"&gt;
&lt;a href="https://www.reddit.com/r/golang/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;r/golang&lt;/strong&gt;&lt;/a&gt; community takes, good and bad&lt;/li&gt;
&lt;/ul&gt;


</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>go</category>
    </item>
    <item>
      <title>They fired the devs. The AI broke production. Now they’re begging them back.</title>
      <dc:creator>&lt;devtips/&gt;</dc:creator>
      <pubDate>Sat, 06 Jun 2026 15:04:50 +0000</pubDate>
      <link>https://dev.to/dev_tips/they-fired-the-devs-the-ai-broke-production-now-theyre-begging-them-back-4k7j</link>
      <guid>https://dev.to/dev_tips/they-fired-the-devs-the-ai-broke-production-now-theyre-begging-them-back-4k7j</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h1 id="e85c"&gt;&lt;/h1&gt;
&lt;h2 id="852d"&gt;&lt;em&gt;The AI replacement experiment ran at scale. The results are in. Nobody’s writing a press release about what they found.&lt;/em&gt;&lt;/h2&gt;
&lt;span&gt;&lt;/span&gt;&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p id="d407"&gt;Nobody announced the plan out loud. But somewhere in 2024, the same decision got made at a hundred companies simultaneously. Developers were expensive. AI coding tools were getting genuinely impressive. The math looked obvious on a slide deck.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="d875"&gt;Cut headcount. Ship with AI. Protect margins. Simple.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="3aef"&gt;Around 124,000 software developers got laid off across the industry that year. Amazon, Microsoft, Meta, Salesforce the list was long and the announcements came fast. The narrative was confident. AI would write the code. Humans were the bottleneck.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="382" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2A3JAx-Rw2k_HOUYxmKXZ9Ag.jpeg"&gt;&lt;p id="8e0e"&gt;That narrative is now quietly, awkwardly falling apart.&lt;/p&gt;
&lt;p id="f9a9"&gt;Gartner recently estimated that 50% of companies that laid off workers because of AI will rehire for the exact same roles by 2027. Around 40% of new hires at some companies are already former employees who were shown the door after the AI pivot. Google rehired roughly 20% of its 2025 engineering intake from people it had previously cut.&lt;/p&gt;
&lt;p id="3fcc"&gt;Nobody’s throwing a party about this. There are no press releases celebrating the return of the developers. No LinkedIn post from a CTO saying “we were wrong.” But the hiring data tells the story that the PR team won’t.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="13c9"&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; AI replaced the code-writers. It couldn’t replace the context-holders the people who know &lt;em&gt;why&lt;/em&gt; the system works the way it does, where the bodies are buried, and which part of the codebase you absolutely do not touch on a Friday. Now those people are getting their calls returned.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="01a0"&gt;The theory was clean. The codebase was not.&lt;/h2&gt;
&lt;p id="5b49"&gt;The logic behind the layoffs was reasonable on the surface. AI tools generate code fast. If you reduce the humans writing code and increase the AI writing it, output stays roughly the same and the salary line goes down. Clean arbitrage.&lt;/p&gt;
&lt;p id="22b5"&gt;The theory had one flaw that took about eighteen months to become undeniable.&lt;/p&gt;
&lt;p id="ad61"&gt;Writing code and developing software are not the same thing.&lt;/p&gt;
&lt;p id="285b"&gt;AI can produce code. It produces it fast and it produces a lot of it. What it produced in practice was code that worked in isolation and quietly detonated when it touched a real system. IBM research found that four out of ten development teams reported compatibility issues when integrating AI-generated code into existing infrastructure. Not “it threw a warning.” Integration failures. The kind that take down services.&lt;/p&gt;
&lt;ul&gt;&lt;li id="0b4b"&gt;&lt;em&gt;The syntax was fine. Gartner found that more than 50% of errors in AI-generated code were related to missing business context not algorithm bugs, not type errors, not off-by-one mistakes. The code was technically correct in a vacuum and wrong in the actual system it was dropped into.&lt;/em&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p id="2b49"&gt;The AI didn’t know why the payment service uses polling instead of webhooks. It didn’t know that decision came from a vendor bug years ago that cost the company a full weekend of downtime. It didn’t know that two services share a cache, that a certain table gets locked during batch jobs, or that the “legacy” module nobody touches is legacy for a reason.&lt;/p&gt;
&lt;p id="76e4"&gt;It read the codebase. It did not understand the history of the codebase. Those are completely different things.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="382" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2AfbsacPA_uetAcWU9HwWH1Q.jpeg"&gt;&lt;p id="1933"&gt;Think of it like hiring a contractor who builds exactly to spec, fast and cheap, but has no idea the building has a load-bearing wall that’s not on any blueprint. The wall exists because a structural engineer made a call in 2019 and never wrote it down. The contractor removes it. The building does not fall immediately. It falls six months later when someone adds weight to the second floor.&lt;/p&gt;
&lt;p id="4a06"&gt;The developers who got laid off were the ones who knew about the wall. The AI read the blueprints and had no idea the wall existed.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="e69a"&gt;&lt;strong&gt;The productivity math that nobody checked twice&lt;/strong&gt;&lt;/h2&gt;
&lt;p id="cfcb"&gt;The ROI case for replacing developers with AI was built on a specific assumption: that AI tools would make the remaining engineers dramatically faster, or eliminate the need for as many of them entirely. Either way, output goes up and costs go down.&lt;/p&gt;
&lt;p id="6630"&gt;Neither half held up.&lt;/p&gt;
&lt;p id="d76e"&gt;A study found that seasoned engineers were actually 19% &lt;em&gt;slower&lt;/em&gt; when using AI tools compared to working without them. Not junior devs getting overwhelmed. Senior engineers the exact people you’d expect to extract the most value from AI tooling moving slower because of it. The tools generated suggestions that looked plausible on the surface and required time-consuming verification underneath. Reading AI output carefully enough to trust it turns out to take longer than just writing the thing yourself.&lt;/p&gt;
&lt;p id="469c"&gt;&lt;strong&gt;The error rate compounded the problem.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="c575"&gt;

&lt;strong&gt;&lt;em&gt;AI-generated code contained up to 1.7x more errors than human-written code.&lt;/em&gt;&lt;/strong&gt;&lt;em&gt; Not slightly more. Nearly double. And those errors weren’t always obvious. They were the kind that pass a code review, survive testing, and surface in production three weeks later when a specific edge case finally triggers them.&lt;/em&gt;
&lt;/li&gt;

&lt;li id="5c8a"&gt;

&lt;strong&gt;&lt;em&gt;Teams that adopted AI tools heavily ended up maintaining 38% more code than before.&lt;/em&gt;&lt;/strong&gt;&lt;em&gt; The AI shipped fast. The humans then spent months cleaning up what shipped.&lt;/em&gt;
&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="61b0"&gt;The intern analogy is almost too accurate here. You hired someone who types extremely fast, never gets tired, and produces a lot of output. The problem is that a senior engineer is now spending the majority of their day reviewing that output instead of building anything. You didn’t reduce the senior engineering workload. You redirected it toward something less valuable and more exhausting.&lt;/p&gt;
&lt;p id="b4f8"&gt;Congrats. You now have 40% more code, zero new features, and one very tired staff engineer.&lt;/p&gt;
&lt;p id="e6f2"&gt;A GitHub study found that 49% of teams reported a &lt;em&gt;decrease&lt;/em&gt; in real productivity after leaning heavily into AI code generation. Not a slowdown in growth. An actual decrease. The companies that moved fastest on the AI-first bet were the ones watching their best engineers burn out on review work while the backlog stayed exactly where it was.&lt;/p&gt;
&lt;p id="404f"&gt;The cost calculation didn’t just fail to improve. It inverted. More code to maintain, more errors to catch, more senior attention consumed by supervision work that hadn’t existed before. The salary savings from the layoffs got eaten by the hidden cost of running a permanent AI audit operation inside the engineering org.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="d725"&gt;The self-correction problem nobody talks about&lt;/h2&gt;
&lt;p id="53c0"&gt;Here’s the detail that changes the entire calculation. The one that didn’t make the headlines because it’s less dramatic than a layoff announcement but more important than anything else in this conversation.&lt;/p&gt;
&lt;p id="a6e0"&gt;Princeton researchers found that AI models failed to self-correct in more than 60% of cases even when explicitly asked to review their own code.&lt;/p&gt;
&lt;p id="1ec6"&gt;Let that sit for a second.&lt;/p&gt;
&lt;p id="e91a"&gt;You can tell the AI “check your work.” It will check its work. It will tell you the work looks fine. And in 60% of cases where the work is not fine, it will still tell you the work looks fine. Confidently. With no visible uncertainty. The same energy it uses when it’s actually right.&lt;/p&gt;
&lt;p id="7268"&gt;This is not a minor limitation. It’s a structural characteristic of how these tools work.&lt;/p&gt;
&lt;ul&gt;&lt;li id="d57f"&gt;Human engineers accumulate judgment from failure.&lt;/li&gt;&lt;/ul&gt;
&lt;blockquote&gt;&lt;p id="9f8f"&gt;The feedback loop is: write code → run it → watch it break → understand why → update your mental model → write better code next time.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="f6aa"&gt;That loop is uncomfortable and slow and it’s also exactly how engineers develop the instincts that make them valuable. Every production incident a developer survives makes them slightly harder to fool the next time.&lt;/p&gt;
&lt;p id="6df2"&gt;AI doesn’t have that loop. It generates output at the same confidence level regardless of whether the output is correct. The mistakes it makes on day one of a project are structurally identical to the mistakes it makes on day ninety. It doesn’t accumulate experience from failures because it doesn’t experience failure. It just generates the next token.&lt;/p&gt;
&lt;p id="031a"&gt;Think about the senior dev who’s been on your team for four years. Ask them why the auth service has that weird retry logic and they’ll tell you a story about a cascade failure, a 3am incident call, a decision made under pressure that turned out to be right, and three things they’d do differently now. That story is not in the codebase. It’s not in the docs. It exists entirely in their head and it shapes every decision they make.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="382" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2AzPHRg3s8zo6ZKrbyRWhhmA.jpeg"&gt;&lt;p id="bdd3"&gt;The AI read the README. It did not survive the incidents.&lt;/p&gt;
&lt;p id="1377"&gt;This means AI-generated code requires permanent human supervision. Not as a transitional phase while the tools mature. Not something that gets better as the models improve. A fundamental characteristic of a system that generates confident output without closing the feedback loop that produces judgment.&lt;/p&gt;
&lt;p id="13bf"&gt;Someone has to close that loop. That someone is a developer.&lt;/p&gt;
&lt;p id="4d4d"&gt;The companies that laid off their developers didn’t eliminate the need for loop-closing. They just left the loop open and hoped nobody would notice until the quarterly numbers came in.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="fa1a"&gt;&lt;strong&gt;What the rehiring actually reveals&lt;/strong&gt;&lt;/h2&gt;
&lt;p id="52d1"&gt;Nobody is issuing a correction. That’s not how large organizations handle being wrong about a strategy that made headlines. What they’re doing instead is more revealing than any admission would be they’re showing it in job postings.&lt;/p&gt;
&lt;p id="77b6"&gt;The boomerang is already moving. Around 40% of new hires at some companies are former employees who were let go after the AI pivot. Google rehired roughly 20% of its 2025 engineering intake from people it had previously cut. No announcement. No acknowledgment. Just a recruiter email and a slightly awkward first week back.&lt;/p&gt;
&lt;p id="c116"&gt;But here’s what’s actually interesting about &lt;em&gt;who&lt;/em&gt; is getting rehired.&lt;/p&gt;
&lt;ul&gt;

&lt;li id="a1d2"&gt;

&lt;strong&gt;&lt;em&gt;Not junior engineers to ship new features.&lt;/em&gt;&lt;/strong&gt;&lt;em&gt; Senior engineers who understand existing systems, can supervise AI output intelligently, and can catch the class of errors that AI produces reliably but cannot catch itself.&lt;/em&gt;
&lt;/li&gt;

&lt;li id="2495"&gt;

&lt;strong&gt;&lt;em&gt;Not warm bodies to fill seats.&lt;/em&gt;&lt;/strong&gt;&lt;em&gt; More than 54% of companies indicated they plan to specifically increase senior developer hiring while reducing junior positions.&lt;/em&gt;
&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="f272"&gt;The structure of engineering teams is changing but in the opposite direction from what the original thesis predicted. AI isn’t replacing experienced engineers. It’s replacing the entry-level work that used to build the pipeline of future experienced engineers. Which sounds like a solved problem until you realize you’ve stopped planting trees and are now surprised there’s no shade.&lt;/p&gt;
&lt;p id="56d9"&gt;The boomerang hires succeed for one specific reason. They already know the context the AI cannot learn from reading the codebase. They know why certain architectural decisions were made, what the system was designed to handle at scale, where the dangerous assumptions live, and which services will silently misbehave if you change the wrong config value. That knowledge doesn’t exist in any file the AI can access. It exists in the heads of the people who were present for the history of the system.&lt;/p&gt;
&lt;p id="b2c5"&gt;Institutional memory turns out to be an actual competitive asset. Who knew.&lt;/p&gt;
&lt;p id="73fd"&gt;The companies that moved fastest to replace developers with AI are now the ones moving fastest to hire them back. Not because AI coding tools don’t work. They work. They generate code quickly and handle repetitive tasks well and genuinely accelerate certain parts of the development workflow. But they work as a layer on top of human judgment, not as a replacement for it.&lt;/p&gt;
&lt;p id="6aae"&gt;The experiment ran at scale with real consequences. The results are visible in the hiring data even if they’re nowhere in the press releases.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="bca8"&gt;So what do you actually do with this?&lt;/h2&gt;
&lt;p id="0041"&gt;The “90% replacement” thesis that was making the rounds a few years ago was wrong in a specific and instructive way. It assumed that writing code was the core value engineers provided. Turns out the core value was something harder to name and much harder to replicate.&lt;/p&gt;
&lt;p id="16df"&gt;Understanding systems. Maintaining context over time. Catching the errors that confident tools produce invisibly. Making judgment calls that require knowing things that aren’t written down anywhere. Closing the feedback loop that turns failure into instinct.&lt;/p&gt;
&lt;p id="7e63"&gt;None of that is replaceable by current AI tools. It may not be replaceable by near-future AI tools either, because it depends on the kind of accumulated, context-specific knowledge that only comes from being present for the history of a system. You can’t fine-tune your way into knowing why the checkout flow has that bizarre edge case handling you had to be in the room when the decision got made.&lt;/p&gt;
&lt;p id="4133"&gt;The real slow-burn crisis isn’t senior developers getting replaced. It’s the junior pipeline collapsing. AI is doing the entry-level work that used to teach people how systems break, how production behaves differently from staging, and how to develop the instincts that eventually make someone a senior engineer worth rehiring. That pipeline doesn’t disappear overnight. It disappears over five years, quietly, and then suddenly every company is competing for the same shrinking pool of experienced engineers who survived the transition.&lt;/p&gt;
&lt;p id="dfba"&gt;That’s the uncomfortable part of the story nobody’s writing the press release about either.&lt;/p&gt;
&lt;p id="ab8e"&gt;For the developers reading this the ones who kept learning during the uncertainty, kept building context, kept sharpening the skills that don’t show up in a GitHub commit you’re the ones getting the calls now. The recruiters reaching back out aren’t doing you a favor. They’re correcting a calculation error.&lt;/p&gt;
&lt;blockquote&gt;

&lt;p id="6ed0"&gt;The question worth sitting with: what does the knowledge you have about your current system look like if you tried to write it down, and how much of it exists nowhere except in your own head?&lt;/p&gt;

&lt;p id="31f7"&gt;That’s not a liability. That’s the job.&lt;/p&gt;


&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p id="035e"&gt;Drop a comment with your take are companies actually learning from this, or are we six months away from the next round of “AI will replace developers” headlines?&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="7f3f"&gt;Helpful resources&lt;/h2&gt;
&lt;ul&gt;

&lt;li id="b636"&gt;&lt;a href="https://www.gartner.com/en/newsroom/press-releases" rel="noopener ugc nofollow noreferrer"&gt;&lt;em&gt;Gartner: 50% of AI-driven layoffs will reverse by 2027&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li id="4db3"&gt;&lt;a href="https://www.ibm.com/thought-leadership/institute-business-value" rel="noopener ugc nofollow noreferrer"&gt;&lt;em&gt;IBM Institute for Business Value AI and developer productivity research&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li id="de58"&gt;&lt;a href="https://octoverse.github.com/" rel="noopener ugc nofollow noreferrer"&gt;&lt;em&gt;GitHub Octoverse 2024 Developer productivity and AI&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li id="20da"&gt;&lt;a href="https://arxiv.org/abs/2310.01848" rel="noopener ugc nofollow noreferrer"&gt;&lt;em&gt;Princeton NLP LLM self-correction research&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li id="9d08"&gt;&lt;a href="https://layoffs.fyi/" rel="noopener ugc nofollow noreferrer"&gt;&lt;em&gt;Layoffs.fyi 2024 tech layoff tracker&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li id="82bf"&gt;&lt;a href="https://dev.to/" rel="noopener ugc nofollow"&gt;&lt;em&gt;Dev.to “Is AI pair programming actually slowing you down?”&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>I replaced GitHub Copilot with 3 tools. My team noticed within a week.</title>
      <dc:creator>&lt;devtips/&gt;</dc:creator>
      <pubDate>Thu, 21 May 2026 06:27:02 +0000</pubDate>
      <link>https://dev.to/dev_tips/i-replaced-github-copilot-with-3-tools-my-team-noticed-within-a-week-244k</link>
      <guid>https://dev.to/dev_tips/i-replaced-github-copilot-with-3-tools-my-team-noticed-within-a-week-244k</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="d477"&gt;&lt;strong&gt;Cursor, Claude Code, and Windsurf didn’t just change how I code they changed what my PRs look like.&lt;/strong&gt;&lt;/h2&gt;
&lt;span&gt;&lt;/span&gt;&lt;h2 id="34a1"&gt;The Copilot breakup nobody talks about&lt;/h2&gt;
&lt;p id="f80a"&gt;I didn’t plan to replace GitHub Copilot. It was fine. It’s still fine. But “fine” stopped being good enough somewhere around month three of watching teammates ship faster than me while I was still waiting for an autocomplete suggestion that missed the point.&lt;/p&gt;
&lt;p id="84a1"&gt;So I started experimenting. Not with one tool. With everything. Forty-something IDEs, agents, plugins, and CLI tools over four months, run against real work not demos, not tutorials, actual PRs that had to pass review. Most of them lasted a week. A few lasted a day. One deleted a file I needed and I don’t want to talk about it.&lt;/p&gt;
&lt;p id="7f36"&gt;Three survived.&lt;/p&gt;
&lt;p id="d4a8"&gt;Cursor for multi-file feature work. Claude Code for everything that lives in the terminal. Windsurf for the days when I need to stay in flow without managing the AI every five minutes. By the end of week one my team lead asked what I’d changed. By week three two teammates had switched.&lt;/p&gt;
&lt;p id="057d"&gt;This isn’t a sponsored comparison post. None of these companies know I exist. It’s just what happened when I stopped treating AI coding tools as a category and started treating them as team members with different strengths.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="fd63"&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Copilot is fine for autocomplete. If that’s still your whole AI coding stack in 2026 you’re leaving serious velocity on the table. Cursor, Claude Code, and Windsurf each own a different slot in the workflow and together they cover everything Copilot never could.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="1365"&gt;Why Copilot stopped being enough&lt;/h2&gt;
&lt;p id="1430"&gt;Let me be fair to Copilot first. It’s not bad. For a dev who mostly works in one file at a time, writes straightforward code, and doesn’t need much more than smart autocomplete it does the job. I used it for over a year and I was mostly happy.&lt;/p&gt;
&lt;p id="e0d1"&gt;The problem isn’t what Copilot does. It’s what it doesn’t do.&lt;/p&gt;
&lt;p id="777e"&gt;Copilot watches what you’re typing and tries to finish the sentence. That’s the whole model. It doesn’t know why you’re writing what you’re writing. It doesn’t know what the rest of the codebase looks like. It doesn’t know that the function you’re building has to fit into an auth system three files away or that your team has a convention for error handling that isn’t in any docs. It makes educated guesses based on what’s visible in the current file and what it saw during training.&lt;/p&gt;
&lt;p id="5c97"&gt;For a while that’s enough. Then your codebase grows. Your features get more interconnected. A change to one interface ripples through six files. A refactor touches the API layer, the service layer, and the tests simultaneously. And suddenly you’re doing all the thinking that the AI should be helping with, while it cheerfully suggests the wrong variable name.&lt;/p&gt;
&lt;p id="8a03"&gt;The real cost isn’t the bad suggestions. It’s what you do with them. You stop, evaluate, reject, retype. You context switch out of the problem you were solving to manage the tool that was supposed to help you solve it. That friction is small per instance and significant over a day.&lt;/p&gt;
&lt;p id="c286"&gt;I started noticing it when I’d spend twenty minutes on something I expected to take five. Not because the problem was hard. Because I was fighting my tools to get there.&lt;/p&gt;
&lt;p id="c17e"&gt;That’s when I started looking for something different. Not a better autocomplete. A different category of help entirely.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="a575"&gt;Cursor the IDE that started arguing back&lt;/h2&gt;
&lt;p id="25f9"&gt;I thought AI-assisted coding meant better autocomplete. Then Cursor refactored a function I didn’t ask it to touch, the PR passed review without a single comment, and I had to sit with that for a minute.&lt;/p&gt;
&lt;p id="d6f4"&gt;Cursor isn’t a smarter Copilot. It’s a different thing entirely. Where Copilot watches what you’re typing and tries to finish the sentence, Cursor reads your whole codebase and forms opinions about it. You can ask it to build a feature and it’ll touch six files, write the tests, and explain what it changed and why.&lt;/p&gt;
&lt;h3 id="ef22"&gt;What I actually use it for:&lt;/h3&gt;
&lt;ul&gt;

&lt;li id="2569"&gt;

&lt;strong&gt;Multi-file edits&lt;/strong&gt; This is where it earns its keep. I don’t use Cursor for writing single functions. I use it when a change needs to ripple across the codebase updating an interface, migrating an API, refactoring auth logic. It plans the changes, shows you the diff across every affected file, and lets you approve before anything gets written.&lt;/li&gt;

&lt;li id="9cf9"&gt;

&lt;strong&gt;.cursorrules&lt;/strong&gt; Drop this file in the root of your project. Cursor reads it at the start of every session preferred patterns, things to avoid, naming conventions and actually respects that context every time.&lt;/li&gt;

&lt;/ul&gt;
&lt;pre&gt;&lt;span id="c241"&gt;# .cursorrules&lt;br&gt;You are working on a Node.js REST API.&lt;br&gt;Always use async/await. Never use callbacks.&lt;br&gt;Prefer explicit error handling. Never swallow errors silently.&lt;br&gt;Add JSDoc comments to all exported functions.&lt;/span&gt;&lt;/pre&gt;
&lt;p id="b56e"&gt;&lt;em&gt;This alone cuts the back-and-forth in half.&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="aca7"&gt;

&lt;strong&gt;Cmd+K inline editing&lt;/strong&gt; Highlight a block, hit Cmd+K, describe what you want. Rewrites in place. No sidebar, no context switching, no copy-paste. Fastest way to refactor something small without losing your train of thought.&lt;/li&gt;

&lt;li id="1e18"&gt;

&lt;strong&gt;Agent window Cursor 3&lt;/strong&gt; April 2026, Cursor shipped a new interface built from scratch around agents. Multiple agents running in parallel across different repos, all visible in one sidebar. You dispatch tasks, review diffs, approve changes. It looks less like a code editor and more like an engineering manager’s dashboard. In a good way.&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="d055"&gt;I still write code manually. Cursor doesn’t replace that. But for anything involving more than one file, more than one decision, or more than five minutes of thinking I hand it off and review the output. That’s a different relationship with your tools than most devs are used to, and it takes about a week to actually trust it.&lt;/p&gt;
&lt;p id="fc2e"&gt;Worth it.&lt;/p&gt;
&lt;p id="7055"&gt;&lt;a href="https://cursor.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;cursor.com&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="382" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2AkOVbgHHwXHsuAxxqYYEzdw.png"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="2eae"&gt;Claude Code the terminal agent I didn’t know I needed&lt;/h2&gt;
&lt;p id="c445"&gt;Most AI coding tools live inside your editor. Claude Code lives in your terminal. No IDE. No sidebar. No chat window. You talk to it like a senior engineer who already read the repo, and it writes files, runs commands, fixes test failures, and ships. It’s not a chatbot with code awareness. It’s a collaborator.&lt;/p&gt;
&lt;p id="97aa"&gt;The first time it ran a full test suite, found a failing edge case I hadn’t noticed, fixed it, and committed while I was reviewing a completely different PR I had to close my laptop and think about my life choices for a moment.&lt;/p&gt;
&lt;p id="6e94"&gt;&lt;strong&gt;What I actually use it for:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;&lt;li id="15ff"&gt;

&lt;strong&gt;Natural language task execution&lt;/strong&gt; You describe what you want in plain English. It reads the repo, makes a plan, executes it, and tells you what it did. No hand-holding, no step-by-step prompting.&lt;/li&gt;&lt;/ul&gt;
&lt;pre&gt;&lt;span id="19ff"&gt;$ claude &lt;span&gt;"refactor the auth middleware to use JWT RS256,&lt;br&gt;run the tests, and fix anything that breaks"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p id="2e9f"&gt;&lt;em&gt;It reads the codebase, plans the changes, runs the tests, iterates on failures without you touching anything.&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="523d"&gt;

&lt;strong&gt;Terminal-native workflow&lt;/strong&gt; Claude Code lives where backend and DevOps work actually happens. No tab switching, no copy-pasting output into a chat window, no losing context. You stay in the terminal, it stays in the terminal, and the whole session feels like pairing with someone who never gets tired or distracted.&lt;/li&gt;

&lt;li id="5aff"&gt;

&lt;strong&gt;MCP tool integrations&lt;/strong&gt; Claude Code connects to external tools via MCP GitHub, databases, deployment pipelines. You can give it real reach beyond just the codebase and it handles multi-step workflows that would normally take you 20 manual commands.&lt;/li&gt;

&lt;li id="1da8"&gt;

&lt;strong&gt;Computer use&lt;/strong&gt; On Mac, Claude Code can open apps, click through UI, take a screenshot, and verify the result. Build a feature, launch it, test it visually from one terminal session. That one still gets me every time.&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="fb33"&gt;I still keep human oversight on anything production-critical. But for local dev, test environments, and deploy pipelines it runs, I review. That’s the whole loop.&lt;/p&gt;
&lt;p id="4801"&gt;&lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;docs.anthropic.com/claude-code&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="cdad"&gt;Windsurf the dark horse nobody warned me about&lt;/h2&gt;
&lt;p id="339c"&gt;Everyone I know is on Cursor. Windsurf kept coming up in my Discord as the tool people switched to and then got annoyingly quiet about like they’d found something they didn’t want to share yet.&lt;/p&gt;
&lt;p id="88f7"&gt;I tried it out of mild spite. They were right and I was annoyed about it.&lt;/p&gt;
&lt;p id="55b0"&gt;Windsurf isn’t trying to beat Cursor on features. It’s trying to beat it on feel. The whole thing is built around Cascade an agentic AI that doesn’t wait for you to ask it something. It watches what you’re doing, understands the context, and acts. The difference between using Cursor and using Windsurf is the difference between having a very capable assistant and having a very capable assistant who also pays attention.&lt;/p&gt;
&lt;p id="d6e0"&gt;&lt;strong&gt;What I actually use it for:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="a686"&gt;

&lt;strong&gt;Cascade agent&lt;/strong&gt; Multi-file edits, terminal commands, test runs all without you directing every step. You describe the goal, Cascade figures out the path. It feels less like “here’s what I’m going to do, approve?” and more like “here’s what I did, check it.” That distinction matters more than it sounds when you’re deep in a feature and don’t want to break flow.&lt;/li&gt;

&lt;li id="01bd"&gt;

&lt;strong&gt;Codemaps&lt;/strong&gt; Windsurf indexes your repo and builds a visual map of your architecture how files relate, where the entry points are, what connects to what. Genuinely useful when you’re jumping into a codebase you didn’t write, and it gives the AI accurate context on large projects without you having to explain the structure manually.&lt;/li&gt;

&lt;li id="c4d3"&gt;

&lt;strong&gt;Drag-and-drop screenshot → UI generation&lt;/strong&gt; Drop a screenshot of a design into Cascade and it generates the frontend code. For anyone doing UI work this is the kind of feature that makes you feel like you skipped two steps.&lt;/li&gt;

&lt;li id="a3c9"&gt;

&lt;strong&gt;$15/month&lt;/strong&gt; Cursor Pro is $20. Windsurf Pro is $15. Same capability tier, lower price. Not the reason to pick it, but not nothing either.&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="8776"&gt;As of February 2026 Windsurf sits at number one in the LogRocket AI Dev Tool Power Rankings ahead of Cursor and GitHub Copilot. And with the Cognition AI acquisition bringing Devin integration into the roadmap, it’s about to get significantly more powerful.&lt;/p&gt;
&lt;p id="5e4e"&gt;I run Windsurf when I want to stay in flow on a feature without stopping to manage the AI. It gets out of the way in a way that Cursor, for all its power, sometimes doesn’t.&lt;/p&gt;
&lt;p id="d791"&gt;&lt;a href="https://windsurf.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;windsurf.com&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="4569"&gt;How Cursor, Claude Code, and Windsurf work together&lt;/h2&gt;
&lt;p id="118a"&gt;Each tool on its own is solid. Stacked right they cover every layer of the workflow without overlap and without gaps. It’s not about having three tools open at once it’s about each one owning a different job.&lt;/p&gt;
&lt;p id="4756"&gt;&lt;strong&gt;Here’s how they actually fit into my day:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="a1ad"&gt;

&lt;strong&gt;Windsurf for active feature work&lt;/strong&gt; When I’m building something new and want to stay in flow, Windsurf is open. Cascade handles the context, suggests the next move, runs the changes. I steer, it executes. I don’t stop to manage it and it doesn’t ask me to.&lt;/li&gt;

&lt;li id="3bc9"&gt;

&lt;strong&gt;Cursor for multi-file refactors and reviews&lt;/strong&gt; When a change is complex, touches multiple services, or needs careful diff review before anything gets committed Cursor. The agent window and &lt;code&gt;.cursorrules&lt;/code&gt; context make it the right tool for surgical, deliberate work where I want to see exactly what's changing before it changes.&lt;/li&gt;

&lt;li id="8818"&gt;

&lt;strong&gt;Claude Code for everything terminal&lt;/strong&gt; Tests, deploys, migrations, environment setup, CI debugging. Anything that lives in the command line. Claude Code handles the full sequence while I’m doing something else, then tells me what it did.&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="2c42"&gt;&lt;strong&gt;The real-world flow looks like this:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span id="1459"&gt;Feature branch&lt;br&gt;→ Windsurf/Cascade writes the feature&lt;br&gt;→ Cursor agent reviews multi-file diffs&lt;br&gt;→ Claude Code runs tests + deploys to staging&lt;br&gt;→ Push PR&lt;/span&gt;&lt;/pre&gt;
&lt;p id="acae"&gt;&lt;em&gt;Three tools, zero browser tabs open to paste errors into&lt;/em&gt;&lt;/p&gt;
&lt;p id="7fd1"&gt;The week my team lead asked what I’d changed, this was the answer. Not one tool. Not a new IDE. A stack where every part of the workflow had the right tool behind it. Windsurf for flow, Cursor for precision, Claude Code for the terminal. Nothing falling through the cracks.&lt;/p&gt;
&lt;p id="fc74"&gt;That’s the whole thing. It took four months and forty tools to figure out. Now it just runs.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="6595"&gt;Final thoughts: you don’t need 40 tools&lt;/h2&gt;
&lt;p id="dce2"&gt;I didn’t set out to replace Copilot. I set out to stop feeling like my tools were slowing me down. Those are different problems and they lead to different solutions.&lt;/p&gt;
&lt;p id="045b"&gt;Copilot isn’t the villain here. It’s a solid tool that does exactly what it promises. The issue is that what it promises stopped being enough once the rest of the ecosystem caught up and then kept going. Standing still while everything around you accelerates is its own kind of falling behind.&lt;/p&gt;
&lt;p id="a6c1"&gt;&lt;strong&gt;After four months and forty experiments the three tools that actually changed my output were:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="01c8"&gt;Cursor for multi-file feature work and agentic refactors&lt;/li&gt;

&lt;li id="2123"&gt;Claude Code for terminal tasks, deploys, and anything CLI&lt;/li&gt;

&lt;li id="7389"&gt;Windsurf for staying in flow with Cascade running alongside you&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="80b6"&gt;None of them require you to change how you think about code. They just remove the parts that were slowing you down the context switching, the tab juggling, the manual command sequences, the back-and-forth with a tool that doesn’t know what you’re building or why.&lt;/p&gt;
&lt;p id="de72"&gt;My PRs got cleaner. My review cycles got shorter. My team noticed before I even said anything. That’s the only metric that matters.&lt;/p&gt;
&lt;p id="4464"&gt;If you’re still on vanilla Copilot and shipping just fine genuinely, keep going. But if you’ve been feeling the friction and just assumed that was normal, it isn’t. The gap between what Copilot does and what this stack does is real and it’s only getting wider.&lt;/p&gt;
&lt;p id="3efd"&gt;Start with one. Cursor if you spend most of your time in the editor. Claude Code if you live in the terminal. Windsurf if you keep getting pulled out of flow. Run it for two weeks on real work. You’ll know by then.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h3 id="16b4"&gt;&lt;strong&gt;Helpful resources and links&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;

&lt;li id="2fa1"&gt;

&lt;a href="https://cursor.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Cursor&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;multi-file AI editor with agent window and .cursorrules support&lt;/li&gt;

&lt;li id="4929"&gt;

&lt;a href="https://cursor.com/blog/cursor-3" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Cursor 3 announcement&lt;/strong&gt;&lt;/a&gt; the April 2026 agent-first rebuild&lt;/li&gt;

&lt;li id="fd9e"&gt;

&lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;&lt;/a&gt; terminal-native AI coding agent&lt;/li&gt;

&lt;li id="9a4f"&gt;

&lt;a href="https://windsurf.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Windsurf&lt;/strong&gt;&lt;/a&gt; agentic IDE with Cascade and Codemaps&lt;/li&gt;

&lt;li id="86f9"&gt;

&lt;a href="https://github.com/features/copilot" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;GitHub Copilot&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;still worth knowing what you’re moving away from&lt;/li&gt;

&lt;li id="ffe1"&gt;

&lt;a href="https://blog.logrocket.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;LogRocket AI Dev Tool Power Rankings&lt;/strong&gt;&lt;/a&gt; February 2026 edition&lt;/li&gt;

&lt;li id="a401"&gt;

&lt;a href="https://www.reddit.com/r/cursor/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;r/cursor&lt;/strong&gt;&lt;/a&gt; real-world Cursor workflows and community feedback&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>github</category>
    </item>
    <item>
      <title>Cursor, Claude Code, Windsurf?! My AI coding stack after 40 dev experiments</title>
      <dc:creator>&lt;devtips/&gt;</dc:creator>
      <pubDate>Tue, 19 May 2026 03:22:53 +0000</pubDate>
      <link>https://dev.to/dev_tips/cursor-claude-code-windsurf-my-ai-coding-stack-after-40-dev-experiments-2llk</link>
      <guid>https://dev.to/dev_tips/cursor-claude-code-windsurf-my-ai-coding-stack-after-40-dev-experiments-2llk</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="fb5e"&gt;&lt;strong&gt;For devs drowning in AI tool hype who just want to know what actually stuck&lt;/strong&gt;&lt;/h2&gt;
&lt;span&gt;&lt;/span&gt;&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;h2 id="8e99"&gt;40 tools, 3 survivors&lt;/h2&gt;
&lt;p id="1f27"&gt;If you’ve ever installed a new AI coding tool on a Monday, spent the whole evening configuring it, been genuinely impressed for about three days, and then quietly gone back to your old setup by Thursday you’re not alone. Some devs binge Netflix. I install AI tools.&lt;/p&gt;
&lt;p id="5903"&gt;I went through a phase (fine, a four-month spiral) where I tested nearly every tool promising to make me ship faster, code smarter, or finally stop copy-pasting stack traces into a browser tab. Most of them were fine. A few were actually good. One deleted a file I needed. But out of the 40+ IDEs, agents, plugins, extensions, and CLI tools I ran through real work projects not toy repos, actual PRs three made it through.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="94fa"&gt;Cursor. Claude Code. Windsurf.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="f83a"&gt;This isn’t a “best of 2026” roundup written by someone who ran each tool for 20 minutes. It’s what survived four months of daily use across real backend work, DevOps tasks, and the occasional frontend emergency. Tools I open every morning without thinking about it. The ones that made my workflow cleaner, faster, and way less frustrating.&lt;/p&gt;
&lt;p id="4477"&gt;If you’re still on vanilla Copilot wondering why things still feel slow, or you’re deep in comparison hell and just want someone to cut through it this one’s for you.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="0115"&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Cursor handles multi-file feature work and agentic refactors. Claude Code owns the terminal tests, deploys, migrations, anything CLI. Windsurf runs Cascade in the background while you stay in flow. Together they cover every slot in a serious dev workflow. Separately, each one is still better than most of the 37 tools I’m not writing about.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="0903"&gt;Let’s get into it.&lt;/p&gt;
&lt;h3 id="0308"&gt;&lt;strong&gt;Table of contents&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;

&lt;li id="5d2c"&gt;Why your AI tool stack actually matters now&lt;/li&gt;

&lt;li id="32b5"&gt;

&lt;strong&gt;Cursor &lt;/strong&gt;the IDE that started arguing back&lt;/li&gt;

&lt;li id="7a3d"&gt;

&lt;strong&gt;Claude Code&lt;/strong&gt; the terminal agent I didn’t know I needed&lt;/li&gt;

&lt;li id="ab5a"&gt;

&lt;strong&gt;Windsurf &lt;/strong&gt;the dark horse nobody warned me about&lt;/li&gt;

&lt;li id="36b5"&gt;How Cursor, Claude Code, and Windsurf work together&lt;/li&gt;

&lt;li id="73c3"&gt;

&lt;strong&gt;Final thoughts:&lt;/strong&gt; you don’t need 40 tools&lt;/li&gt;

&lt;li id="0612"&gt;&lt;strong&gt;Helpful resources and links&lt;/strong&gt;&lt;/li&gt;

&lt;/ul&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="e748"&gt;Why your AI tool stack actually matters now&lt;/h2&gt;
&lt;p id="4302"&gt;Here’s the thing nobody tells you when you first install an AI coding tool: the tool isn’t the hard part. The hard part is figuring out which problems you actually have, and whether what you just installed solves any of them.&lt;/p&gt;
&lt;p id="6880"&gt;Most devs treat AI tooling like a plugin they’ll figure out later. They install Copilot, use it for autocomplete, occasionally ask it to explain a regex, and call it done. That worked fine in 2024. In 2026 it’s the equivalent of using a GPS only to check if it’s raining.&lt;/p&gt;
&lt;p id="d28b"&gt;The conversation has moved. Agents are the standard now, not the novelty. The tools worth your time aren’t the ones that finish your line of code they’re the ones that read your whole codebase, plan a multi-step task, execute it, run your tests, fix the failures, and come back when it’s done. That’s not autocomplete. That’s a different category of tool entirely.&lt;/p&gt;
&lt;p id="cf27"&gt;And the cost of getting this wrong isn’t just a wasted subscription. It’s the context-switching penalty. Every time you break flow to copy an error into a chat window, switch tabs to ask a question you should be able to ask inside your editor, or manually run a command sequence an agent could handle that’s compounding friction. The &lt;a href="https://dora.dev/research/2024/dora-report/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;DORA 2025 report&lt;/strong&gt;&lt;/a&gt; found that high-performing engineering teams are pulling significantly ahead of the rest, and tooling decisions are a real part of that gap.&lt;/p&gt;
&lt;p id="02db"&gt;The developers figuring out the right AI stack right now aren’t the ones with the most tools installed. They’re the ones who stopped treating AI like a fancy tab completion and started treating it like a collaborator with a job description. You wouldn’t hire one person to do your backend, frontend, DevOps, and code review. Same logic applies here.&lt;/p&gt;
&lt;p id="fd6c"&gt;That’s the frame for everything that follows not which AI tool is best, but which tool owns which job, and how you stack them so nothing falls through the cracks.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="8aae"&gt;Cursor the IDE that started arguing back&lt;/h2&gt;
&lt;p id="969d"&gt;I thought AI-assisted coding meant better autocomplete. Then Cursor refactored a function I didn’t ask it to touch, the PR passed review without a single comment, and I had to sit with that for a minute.&lt;/p&gt;
&lt;p id="00b9"&gt;Cursor isn’t a smarter Copilot. It’s a different thing entirely. Where Copilot watches what you’re typing and tries to finish the sentence, Cursor reads your whole codebase and forms opinions about it. You can ask it to build a feature and it’ll touch six files, write the tests, and explain what it changed and why.&lt;/p&gt;
&lt;p id="3857"&gt;&lt;strong&gt;What I actually use it for:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="f430"&gt;

&lt;strong&gt;Multi-file edits&lt;/strong&gt; This is where it earns its keep. I don’t use Cursor for writing single functions. I use it when a change needs to ripple across the codebase updating an interface, migrating an API, refactoring auth logic. It plans the changes, shows you the diff across every affected file, and lets you approve before anything gets written.&lt;/li&gt;

&lt;li id="9c37"&gt;

&lt;strong&gt;cursorrules&lt;/strong&gt; Drop this file in the root of your project. Cursor reads it at the start of every session preferred patterns, things to avoid, naming conventions and actually respects that context every time.&lt;/li&gt;

&lt;/ul&gt;
&lt;pre&gt;&lt;span id="cf2b"&gt;# .cursorrules&lt;br&gt;You are working on a Node.js REST API.&lt;br&gt;Always use async/await. Never use callbacks.&lt;br&gt;Prefer explicit error handling. Never swallow errors silently.&lt;br&gt;Add JSDoc comments to all exported functions.&lt;/span&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p id="f226"&gt;&lt;em&gt;This alone cuts the back-and-forth in half.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;ul&gt;

&lt;li id="4fc3"&gt;

&lt;strong&gt;Cmd+K inline editing&lt;/strong&gt; Highlight a block, hit Cmd+K, describe what you want. Rewrites in place. No sidebar, no context switching, no copy-paste. Fastest way to refactor something small without losing your train of thought.&lt;/li&gt;

&lt;li id="f837"&gt;

&lt;strong&gt;Agent window Cursor 3&lt;/strong&gt; April 2026, Cursor shipped a new interface built from scratch around agents. Multiple agents running in parallel across different repos, all visible in one sidebar. You dispatch tasks, review diffs, approve changes. It looks less like a code editor and more like an engineering manager’s dashboard. In a good way.&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="2c47"&gt;I still write code manually. Cursor doesn’t replace that. But for anything involving more than one file, more than one decision, or more than five minutes of thinking I hand it off and review the output. That’s a different relationship with your tools than most devs are used to, and it takes about a week to actually trust it.&lt;/p&gt;
&lt;p id="d1d0"&gt;Worth it.&lt;/p&gt;
&lt;p id="d9cd"&gt;&lt;a href="https://cursor.com" rel="noopener ugc nofollow noreferrer"&gt;cursor.com&lt;/a&gt;&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="385" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2AZArXTN6layGEiF0u7NeoXA.png"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="34a0"&gt;Claude Code the terminal agent I didn’t know I needed&lt;/h2&gt;
&lt;p id="20da"&gt;Most AI coding tools live inside your editor. Claude Code lives in your terminal. No IDE. No sidebar. No chat window. You talk to it like a senior engineer who already read the repo, and it writes files, runs commands, fixes test failures, and ships. It’s not a chatbot with code awareness. It’s a collaborator.&lt;/p&gt;
&lt;p id="cac0"&gt;The first time it ran a full test suite, found a failing edge case I hadn’t noticed, fixed it, and committed while I was reviewing a completely different PR I had to close my laptop and think about my career choices for a moment.&lt;/p&gt;
&lt;p id="b0ed"&gt;&lt;strong&gt;Why I stuck with it:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="967a"&gt;

&lt;strong&gt;Terminal-native workflow&lt;/strong&gt; Claude Code lives where backend and DevOps work actually happens. No tab switching, no copy-pasting output into a chat window, no losing context. You stay in the terminal, it stays in the terminal, and the whole session feels like pairing with someone who never gets tired or distracted.&lt;/li&gt;

&lt;li id="bde2"&gt;

&lt;strong&gt;Natural language task execution&lt;/strong&gt; You describe what you want in plain English. It reads the repo, makes a plan, executes it, and tells you what it did.&lt;/li&gt;

&lt;/ul&gt;
&lt;pre&gt;&lt;span id="c2af"&gt;$ claude &lt;span&gt;"refactor the auth middleware to use JWT RS256,&lt;br&gt;run the tests, and fix anything that breaks"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p id="1a5c"&gt;&lt;em&gt;It reads the codebase, plans the changes, runs the tests, iterates on failures without you touching anything.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;ul&gt;

&lt;li id="7ddc"&gt;

&lt;strong&gt;MCP tool integrations&lt;/strong&gt; Claude Code connects to external tools via MCP GitHub, databases, deployment pipelines. You can give it real reach beyond just the codebase and it handles multi-step workflows that would normally take you 20 manual commands.&lt;/li&gt;

&lt;li id="3775"&gt;

&lt;strong&gt;Computer use&lt;/strong&gt; On Mac, Claude Code can open apps, click through UI, screenshot the result, and verify it worked. Build a feature, launch it, test it visually from one terminal session. That one still gets me every time.&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="1078"&gt;I still use it with human oversight on anything production-critical. But for local dev, test environments, and deploy pipelines it runs. I review. That’s the whole loop.&lt;/p&gt;
&lt;p id="dd6c"&gt;&lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;docs.anthropic.com/claude-code&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="794" height="505" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A794%2F1%2ADLKd9VHOXWQcIMu6WRI-Pw.png"&gt;&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="507" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1029%2F1%2APPV6ww80gZP1AdHOjjenTQ.png"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="bb51"&gt;Windsurf the dark horse nobody warned me about&lt;/h2&gt;
&lt;p id="ba85"&gt;Everyone I know is on Cursor. Windsurf kept coming up in my Discord as the tool that people switched to and then got annoyingly quiet about like they’d found something they didn’t want to share yet.&lt;/p&gt;
&lt;p id="03ea"&gt;I tried it out of mild spite. They were right and I was annoyed about it.&lt;/p&gt;
&lt;p id="fbb5"&gt;Windsurf isn’t trying to beat Cursor on features. It’s trying to beat it on &lt;em&gt;feel&lt;/em&gt;. The whole thing is built around Cascade an agentic AI that doesn’t wait for you to ask it something. It watches what you’re doing, understands the context, and acts. The difference between using Cursor and using Windsurf is the difference between having a very capable assistant and having a very capable assistant who also pays attention.&lt;/p&gt;
&lt;p id="8771"&gt;&lt;strong&gt;What makes it essential:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="863d"&gt;

&lt;strong&gt;Cascade agent&lt;/strong&gt; Multi-file edits, terminal commands, test runs all without you directing every step. You describe the goal, Cascade figures out the path. It’s similar to Cursor’s agent mode but the flow feels less interrupted. Less “here’s what I’m going to do, approve?” and more “here’s what I did, check it.”&lt;/li&gt;

&lt;li id="d4b6"&gt;

&lt;strong&gt;Codemaps&lt;/strong&gt; Windsurf indexes your repo and builds a visual map of your architecture how files relate, where the entry points are, what connects to what. Useful when you’re jumping into a codebase you didn’t write, and genuinely helpful for giving the AI accurate context on large projects.&lt;/li&gt;

&lt;li id="f5ba"&gt;

&lt;strong&gt;Drag-and-drop screenshot → UI generation&lt;/strong&gt; Drop a screenshot of a design into Cascade and it generates the frontend code. For anyone doing UI work this is the kind of feature that makes you feel like you skipped two steps.&lt;/li&gt;

&lt;li id="a3e8"&gt;

&lt;strong&gt;$15/month&lt;/strong&gt; Cursor Pro is $20. Windsurf Pro is $15. Same tier, same power level, lower price. Not the reason to pick it, but not nothing either.&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="65dd"&gt;As of February 2026, Windsurf sits at number one in the LogRocket AI Dev Tool Power Rankings ahead of Cursor and GitHub Copilot. And with the Cognition AI acquisition bringing Devin integration into the roadmap, it’s about to get significantly more powerful.&lt;/p&gt;
&lt;p id="1c6c"&gt;I run Windsurf when I want to stay in flow on a feature without stopping to manage the AI. It gets out of the way in a way that Cursor, for all its power, sometimes doesn’t.&lt;/p&gt;
&lt;p id="6a4e"&gt;&lt;a href="https://windsurf.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;windsurf.com&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="700" height="384" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F1%2ALQhsuzIkqjp3UkeytDl8vA.png"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="83f8"&gt;How Cursor, Claude Code, and Windsurf work together&lt;/h2&gt;
&lt;p id="4a25"&gt;Each tool on its own is solid. Stacked right, they cover every layer of the workflow without overlap and without gaps. It’s not about having three tools open at once it’s about each one owning a different job.&lt;/p&gt;
&lt;p id="3a4b"&gt;&lt;strong&gt;Here’s how they fit into my actual day:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="404f"&gt;

&lt;strong&gt;Windsurf for active feature work&lt;/strong&gt; When I’m building something new and want to stay in flow, Windsurf is open. Cascade handles the context, suggests the next move, runs the changes. I steer, it executes.&lt;/li&gt;

&lt;li id="b941"&gt;

&lt;strong&gt;Cursor for multi-file refactors and reviews&lt;/strong&gt; When a change is complex, touches multiple services, or needs careful diff review before anything gets committed Cursor. The agent window and &lt;code&gt;.cursorrules&lt;/code&gt; context make it the right tool for surgical, deliberate work.&lt;/li&gt;

&lt;li id="5e23"&gt;

&lt;strong&gt;Claude Code for everything terminal&lt;/strong&gt; Tests, deploys, migrations, environment setup, CI debugging. Anything that lives in the command line. Claude Code handles the full sequence while I’m doing something else.&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="0df9"&gt;&lt;strong&gt;The real-world flow looks like this:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span id="77e4"&gt;Feature branch&lt;br&gt;→ Windsurf/Cascade writes the feature&lt;br&gt;→ &lt;span&gt;Cursor&lt;/span&gt; agent reviews multi-file diffs&lt;br&gt;→ Claude &lt;span&gt;Code&lt;/span&gt; runs tests + deploys &lt;span&gt;to&lt;/span&gt; staging&lt;br&gt;→ Push PR&lt;/span&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p id="695a"&gt;&lt;em&gt;Three tools, zero browser tabs open to paste errors into.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="a846"&gt;I stopped thinking of these as “AI tools” and started thinking of them as team members with specializations. Different strengths, different contexts, different jobs. Once you frame it that way the stack stops feeling like overkill and starts feeling obvious.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="e1a8"&gt;Final thoughts: you don’t need 40 tools&lt;/h2&gt;
&lt;p id="c5fa"&gt;I didn’t set out to build some ultimate AI coding stack. I just wanted something that worked without me having to think about it every week.&lt;/p&gt;
&lt;p id="6ba6"&gt;&lt;strong&gt;After testing more than 40 tools, the three that actually made my workflow better were:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="1b23"&gt;Cursor for multi-file feature work and agentic refactors&lt;/li&gt;

&lt;li id="5b85"&gt;Claude Code for terminal tasks, deploys, and anything CLI&lt;/li&gt;

&lt;li id="598c"&gt;Windsurf for staying in flow with Cascade running alongside you&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="15cd"&gt;They’re not trying to do the same thing. They don’t step on each other. And none of them require you to change how you code they just remove the parts that were slowing you down.&lt;/p&gt;
&lt;p id="2523"&gt;If you’re still running vanilla Copilot and calling it your AI stack that’s fine. But you’re leaving a lot on the table. The tooling gap between devs who’ve figured this out and devs who haven’t is real, and it’s only getting wider.&lt;/p&gt;
&lt;p id="eb27"&gt;Start with one. Get comfortable. Add the next. By the time you’ve run all three for a month you won’t remember what the friction felt like.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="945a"&gt;&lt;strong&gt;Helpful resources and links&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;

&lt;li id="6cae"&gt;

&lt;a href="https://cursor.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Cursor&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;multi-file AI editor with agent window and .cursorrules support&lt;/li&gt;

&lt;li id="74b6"&gt;

&lt;a href="https://cursor.com/blog/cursor-3" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Cursor 3 announcement&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;the April 2026 agent-first interface rebuild&lt;/li&gt;

&lt;li id="c53d"&gt;

&lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;&lt;/a&gt; terminal-native AI coding agent&lt;/li&gt;

&lt;li id="8058"&gt;

&lt;a href="https://windsurf.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Windsurf&lt;/strong&gt;&lt;/a&gt; agentic IDE with Cascade, Codemaps, and Devin integration incoming&lt;/li&gt;

&lt;li id="6e8a"&gt;

&lt;a href="https://dora.dev/research/2024/dora-report/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;DORA 2025 report&lt;/strong&gt;&lt;/a&gt; state of DevOps and AI-assisted development&lt;/li&gt;

&lt;li id="1c37"&gt;

&lt;a href="https://blog.logrocket.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;LogRocket AI Dev Tool Power Rankings&lt;/strong&gt;&lt;/a&gt; February 2026 rankings&lt;/li&gt;

&lt;li id="ee92"&gt;

&lt;a href="https://www.reddit.com/r/cursor/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;r/cursor&lt;/strong&gt;&lt;/a&gt; community discussion and real-world Cursor workflows&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>webdev</category>
      <category>powerplatform</category>
    </item>
    <item>
      <title>You’re the reason your React app is slow</title>
      <dc:creator>&lt;devtips/&gt;</dc:creator>
      <pubDate>Mon, 18 May 2026 05:09:41 +0000</pubDate>
      <link>https://dev.to/dev_tips/youre-the-reason-your-react-app-is-slow-aim</link>
      <guid>https://dev.to/dev_tips/youre-the-reason-your-react-app-is-slow-aim</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="9707"&gt;You didn’t hit a framework limit. You wrote the bottleneck yourself and it’s been quietly billing you in FPS ever since.&lt;/h2&gt;
&lt;span&gt;&lt;/span&gt;&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p id="09b6"&gt;There’s a specific kind of suffering that happens when you open the React DevTools Profiler for the first time on a project that’s been “running fine.” You hit record. You click a button. You stop recording. And then you just sit there, staring at a flame graph that looks like a city on fire, wondering how a todo app is re-rendering 47 components when you clicked “add item.”&lt;/p&gt;
&lt;p id="91f5"&gt;That was me, about three years into thinking I was pretty decent at React.&lt;/p&gt;
&lt;p id="1d41"&gt;I wasn’t bad. My components looked clean. My PRs got approved. The app shipped. But under the hood, I was doing roughly eight things wrong simultaneously, and the only reason nobody noticed was that our user base was small enough that the jank felt like a “network thing.” It was not a network thing.&lt;/p&gt;
&lt;p id="b0d2"&gt;The React ecosystem has an interesting culture around performance: everyone knows it matters, most articles cover the same four hooks, and almost nobody talks about the architectural decisions that create the problem in the first place. The React Compiler landing in React 19 is going to paper over some of this it automatically memoizes components and values, essentially applying &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt; everywhere it's safe to do so but here's the honest truth: it won't save you from architectural issues like overly broad context providers or massive component trees. You can't compile your way out of a bad design. &lt;a href="https://dev.to/alex_bobes/react-performance-optimization-15-best-practices-for-2025-17l9" rel="noopener ugc nofollow"&gt;&lt;strong&gt;DEV CommunityDEV Community&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p id="44d0"&gt;This article is the one I wish someone had thrown at me back then. No cargo-cult hooks. No “just add &lt;code&gt;React.memo&lt;/code&gt;" advice. Just the actual mistakes, why they happen, and what they cost you in the real world.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="7aed"&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; React is fast by default. You are often the problem. These 10 mistakes are the most common ways engineers including experienced ones quietly murder their own app’s performance, and most of them have nothing to do with the hooks you’ve been memorizing.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="15dc"&gt;The re-render killers&lt;/h2&gt;
&lt;p id="fa34"&gt;Let’s start with the category that accounts for maybe 60% of React performance complaints I’ve seen in the wild. Not slow APIs. Not bad algorithms. Just components re-rendering when they absolutely did not need to, because of decisions made in the five seconds it took to write a JSX prop.&lt;/p&gt;
&lt;p id="0ee0"&gt;React’s re-render model is simple enough that it’s easy to underestimate. React re-renders when state or props change by reference, not by value. That one sentence is responsible for more production slowdowns than any framework bug ever was. It sounds obvious until you realize how many ways you’re accidentally creating new references on every render without thinking about it. &lt;a href="https://dev.to/ar_abid_641aa302d5c68b2ae/react-app-re-renders-too-much-the-hidden-performance-bug-and-the-correct-fix-3a3e" rel="noopener ugc nofollow"&gt;&lt;strong&gt;DEV Community&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="62a5"&gt;Mistake 1: Inline functions in JSX&lt;/h2&gt;
&lt;p id="434c"&gt;This is the one that gets everyone eventually, usually when they’re moving fast and the code looks clean.&lt;/p&gt;
&lt;pre&gt;&lt;span id="67e9"&gt;&lt;span&gt;// you write this and it feels fine&lt;/span&gt;&lt;br&gt;&amp;lt;&lt;span&gt;Button&lt;/span&gt; onClick={&lt;span&gt;() =&amp;gt;&lt;/span&gt; &lt;span&gt;handleDelete&lt;/span&gt;(item.&lt;span&gt;id&lt;/span&gt;)} label=&lt;span&gt;"Delete"&lt;/span&gt; /&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p id="6167"&gt;Here’s what’s actually happening: every time the parent component renders, that arrow function is a brand new function object in memory. React does a shallow comparison on props. New reference equals “props changed” equals re-render even if &lt;code&gt;item.id&lt;/code&gt; hasn't moved an inch. JavaScript creates a new object or function reference on every render. React does a shallow comparison when deciding whether to re-render a child, and since the reference is always new, the child always re-renders, even when nothing meaningful has changed. &lt;a href="https://dev.to/khris_breezy/5-react-performance-mistakes-that-are-slowing-your-app-down-4b2n" rel="noopener ugc nofollow"&gt;&lt;strong&gt;DEV Community&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p id="be5a"&gt;The fix is boring and correct: move static handlers outside the component, and for dynamic ones that depend on state or props, reach for &lt;code&gt;useCallback&lt;/code&gt;. But there's a catch which brings us directly to mistake number two.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="47c1"&gt;Mistake 2: Using &lt;code&gt;useCallback&lt;/code&gt; as a good luck charm&lt;/h2&gt;
&lt;p id="5837"&gt;So you read about inline functions, you start wrapping everything in &lt;code&gt;useCallback&lt;/code&gt;, and you feel like you've leveled up. You haven't. You've just moved the problem around and added overhead.&lt;/p&gt;
&lt;p id="8de4"&gt;&lt;code&gt;useCallback&lt;/code&gt; only does anything useful when the component receiving that function is actually memoized wrapped in &lt;code&gt;React.memo&lt;/code&gt;. Without that, you're paying the cost of memoization (React has to store the previous function, compare dependencies, and make a decision) while getting zero benefit, because the child rerenders anyway. &lt;code&gt;useCallback&lt;/code&gt; only helps if the child component is memoized (&lt;code&gt;React.memo&lt;/code&gt;) or uses the callback in its own dependency arrays. Otherwise, you're adding overhead for no benefit. &lt;a href="https://dev.to/pockit_tools/why-your-react-app-re-renders-too-much-a-deep-dive-into-performance-optimization-2oh3" rel="noopener ugc nofollow"&gt;&lt;strong&gt;DEV Community&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p id="0262"&gt;I’ve seen codebases where someone went on a &lt;code&gt;useCallback&lt;/code&gt; spree across the entire app, felt productive for a day, and then wondered why nothing got faster. There is a cost to memoization. React must store the previous props, compare them, and make a decision this adds overhead. If your component is fast to render and frequently changing, this comparison step may become more expensive than the render itself. &lt;a href="https://www.growin.com/blog/react-performance-optimization-2025/" rel="noopener ugc nofollow noreferrer"&gt;Growin&lt;/a&gt;&lt;/p&gt;
&lt;p id="f1a3"&gt;&lt;strong&gt;The actual rule:&lt;/strong&gt; &lt;code&gt;useCallback&lt;/code&gt; is a tool for reference stability, not a performance incantation. Use it when you have a memoized child that receives the function as a prop, or when the function is in a &lt;code&gt;useEffect&lt;/code&gt; dependency array and you want control over when the effect fires. That's basically it. Profile first, reach for it second.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="201f"&gt;Mistake 3: Using array index as &lt;code&gt;key&lt;/code&gt; in lists&lt;/h2&gt;
&lt;p id="3447"&gt;This one feels harmless until you have a list that changes items get added, removed, or reordered and suddenly your UI starts doing weird things. State ends up in the wrong component. Inputs keep the wrong value. Animations fire on the wrong element. You spend an hour blaming a library that did nothing wrong.&lt;/p&gt;
&lt;p id="ae43"&gt;The &lt;code&gt;key&lt;/code&gt; prop is React's identity system for list items. When you use the array index, you're telling React "the first item is always the first item, regardless of what it actually is." Reorder the list, and React thinks all the same items are still there just with different content. It patches the DOM in place instead of remounting, which is fast but wrong.&lt;/p&gt;
&lt;pre&gt;&lt;span id="9670"&gt;&lt;span&gt;// this looks fine and is not fine&lt;/span&gt;&lt;br&gt;{items.&lt;span&gt;map&lt;/span&gt;(&lt;span&gt;(&lt;span&gt;item, i&lt;/span&gt;) =&amp;gt;&lt;/span&gt; &amp;lt;Card key={i} data={item} /&amp;gt;)}&lt;br&gt;&lt;br&gt;&lt;span&gt;// this is fine&lt;/span&gt;&lt;br&gt;{items.&lt;span&gt;map&lt;/span&gt;(&lt;span&gt;&lt;span&gt;item&lt;/span&gt; =&amp;gt;&lt;/span&gt; &amp;lt;Card key={item.id} data={item} /&amp;gt;)}&lt;/span&gt;&lt;/pre&gt;
&lt;p id="33a0"&gt;If your data genuinely has no stable IDs which happens more than it should generate them when the data is created, not at render time. A &lt;code&gt;crypto.randomUUID()&lt;/code&gt; call in your fetch handler costs nothing. A &lt;code&gt;Math.random()&lt;/code&gt; call inside &lt;code&gt;map&lt;/code&gt; gives every item a new key on every render, which tells React to unmount and remount the entire list. That costs a lot.&lt;/p&gt;
&lt;blockquote&gt;

&lt;p id="579b"&gt;All three of these mistakes share the same root: React’s rendering model is predictable once you understand it, but it punishes you quietly. No errors. No warnings. Just a Profiler graph that looks increasingly unwell.&lt;/p&gt;

&lt;p id="708a"&gt;The good news is that the React DevTools Profiler will catch all three almost immediately. Record a session, look for components highlighted in yellow or red, and ask yourself: “did this actually need to re-render?” Usually the answer is no, and usually one of these three is why.&lt;/p&gt;


&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="e2c5"&gt;State architecture sins&lt;/h2&gt;
&lt;p id="1abe"&gt;Re-renders from inline functions are annoying. State architecture mistakes are a different category of problem entirely. They’re the ones that survive a code review, pass all your tests, and then slowly make your app feel like it’s running through wet concrete as the feature count grows. They’re structural. And they’re almost always invisible until you’re already in pain.&lt;/p&gt;
&lt;p id="faf6"&gt;The pattern is consistent across every codebase I’ve seen it in: someone makes a reasonable decision early, the app grows around that decision, and by the time the jank is obvious there are forty components depending on the thing that’s wrong. Refactoring it feels like surgery on a patient who’s still running a marathon.&lt;/p&gt;
&lt;p id="af68"&gt;Understanding why these happen is more useful than just memorizing the fix.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="2c44"&gt;Mistake 4: Putting state too high up the tree&lt;/h2&gt;
&lt;p id="763d"&gt;This is the most common architectural mistake in React, and it’s almost always made with good intentions. You want state to be accessible from multiple places, so you lift it up to a common ancestor. Reasonable. Except that ancestor is now &lt;code&gt;App&lt;/code&gt;, and every time a checkbox toggles in a deeply nested form, your entire component tree re-renders.&lt;/p&gt;
&lt;p id="a9d0"&gt;If your App component’s state changes, every child component re-renders even if their props didn’t change. This cascading effect kills performance at scale. The mental model that helps here: state should live as close as possible to the components that actually use it. Not one level above. Not in a global provider “just in case something else needs it later.” Right next to the thing that needs it. This is called state colocation, and it’s one of those ideas that sounds obvious until you see how rarely it’s actually practiced.&lt;/p&gt;
&lt;p id="f175"&gt;If only two components in a subtree share a piece of state, their nearest common ancestor is the right home for it not the root. If state is only used by one component, it belongs inside that component, full stop. Split your components into two types: container components that handle the logic, and presentational components that are pure display. Because they have no local state, presentational components only re-render when their props actually change.&lt;/p&gt;
&lt;p id="2045"&gt;The performance difference between “state at the root” and “state colocated” can be dramatic in a large component tree. It’s also the kind of change that makes every subsequent optimization easier, because you’re no longer fighting against a re-render cascade every time you try to fix something specific.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="0dc5"&gt;Mistake 5: Context without memoization&lt;/h2&gt;
&lt;p id="c5d5"&gt;React Context is one of those features that feels like a complete solution right up until it isn’t. You set up a provider, everything can access your global state, PRs get merged, life is good. Then someone notices that updating the user’s theme preference is somehow causing the entire dashboard to re-render, including the charts, the sidebar, and the table that has nothing to do with theming.&lt;/p&gt;
&lt;p id="6ea9"&gt;Here’s why. When you pass an object as the context value which almost everyone does that object is recreated on every render of the provider component. Every consumer sees a new reference. Every consumer re-renders. Even the ones that only care about one field in that object that didn’t change.&lt;/p&gt;
&lt;pre&gt;&lt;span id="3a52"&gt;&lt;span&gt;// every render of AppProvider creates a new value object&lt;/span&gt;&lt;br&gt;&lt;span&gt;// every consumer re-renders every time anything changes&lt;/span&gt;&lt;br&gt;&lt;span&gt;function&lt;/span&gt; &lt;span&gt;AppProvider&lt;/span&gt;(&lt;span&gt;{ children }&lt;/span&gt;) {&lt;br&gt;  &lt;span&gt;const&lt;/span&gt; [user, setUser] = &lt;span&gt;useState&lt;/span&gt;(&lt;span&gt;null&lt;/span&gt;);&lt;br&gt;  &lt;span&gt;const&lt;/span&gt; [theme, setTheme] = &lt;span&gt;useState&lt;/span&gt;(&lt;span&gt;'dark'&lt;/span&gt;);&lt;br&gt;  &lt;span&gt;const&lt;/span&gt; value = { user, setUser, theme, setTheme };&lt;br&gt;  &lt;span&gt;return&lt;/span&gt; &amp;lt;AppContext.Provider value={value}&amp;gt;{children}&amp;lt;/AppContext.Provider&amp;gt;;&lt;br&gt;}&lt;/span&gt;&lt;/pre&gt;
&lt;p id="83b8"&gt;Every state update created a new value object. Every context consumer re-rendered. Including components that only cared about the theme, not the user.&lt;/p&gt;
&lt;p id="3828"&gt;Two fixes, and you’ll usually want both. First, wrap the context value in &lt;code&gt;useMemo&lt;/code&gt; so the reference only changes when the actual data changes. Second, split large contexts into smaller ones by concern a &lt;code&gt;UserContext&lt;/code&gt; and a &lt;code&gt;ThemeContext&lt;/code&gt; rather than one &lt;code&gt;AppContext&lt;/code&gt; that holds everything. A component that only reads theme should never re-render because the user object updated.&lt;/p&gt;
&lt;p id="36b9"&gt;This one is particularly brutal in larger apps because context is often set up early, before the component tree is complex enough to make the problem visible. By the time you feel it, the context is load-bearing and everything’s wired to it.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="447" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A840%2F1%2ABtXc4zf0H4KPMoOEFttuhA.jpeg"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="dbd7"&gt;Mistake 6: &lt;code&gt;useEffect&lt;/code&gt; doing too much&lt;/h2&gt;
&lt;p id="195a"&gt;&lt;code&gt;useEffect&lt;/code&gt; is the Swiss Army knife of React hooks, which is both its strength and the reason developers use it to solve problems it was never designed for. The classic version of this mistake: a &lt;code&gt;useEffect&lt;/code&gt; with a dependency array that's either wrong, empty when it shouldn't be, or so long it fires on basically every render anyway.&lt;/p&gt;
&lt;p id="bd59"&gt;The subtler version is using &lt;code&gt;useEffect&lt;/code&gt; for things that are actually derived state or event-driven logic. If you're running an effect to compute a value from existing state, that should probably be &lt;code&gt;useMemo&lt;/code&gt;. If you're running an effect in response to a user action, that logic belongs in the event handler not in a side effect that triggers after the render. Using &lt;code&gt;useEffect&lt;/code&gt; without proper dependencies, or too many, can trigger unnecessary logic or cause infinite loops.&lt;/p&gt;
&lt;p id="e275"&gt;The infinite loop variant is a rite of passage. You set state inside a &lt;code&gt;useEffect&lt;/code&gt;, that state triggers a re-render, the effect fires again, sets state again, render again, and so on until your browser tab starts sweating. It happens to everyone. The less dramatic version an effect that fires twice as often as it should because the dependency array includes an object that gets recreated on every render is more insidious because it's harder to notice and easy to shrug off as "just how React works."&lt;/p&gt;
&lt;p id="4a91"&gt;It’s not just how React works. It’s a dependency array that needs fixing.&lt;/p&gt;
&lt;p id="c556"&gt;A useful heuristic: if you can’t explain in one sentence what your &lt;code&gt;useEffect&lt;/code&gt; is synchronizing with the outside world, there's a decent chance it shouldn't be a &lt;code&gt;useEffect&lt;/code&gt; at all.&lt;/p&gt;
&lt;blockquote&gt;

&lt;p id="75fc"&gt;These three mistakes compound each other in particularly ugly ways. State too high up means more components consuming context. Context without memoization means all those components re-render together. Overloaded effects means those re-renders trigger more side effects. By the time you feel it, the performance problem isn’t one thing it’s a system of bad decisions that arrived gradually and now all need untangling at once.&lt;/p&gt;

&lt;p id="d8ab"&gt;The good news: fixing any one of them improves things measurably. Fixing all three feels like discovering your app had been running with the handbrake on.&lt;/p&gt;


&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="418e"&gt;Bundle crimes&lt;/h2&gt;
&lt;p id="b304"&gt;Everything so far has been about what happens at runtime components re-rendering when they shouldn’t, state living in the wrong place, effects misfiring. These next two mistakes happen before a single line of your React code executes. They happen at load time, and they’re the reason some apps feel slow before the user has even done anything.&lt;/p&gt;
&lt;p id="3b69"&gt;The bundle is the thing you’re shipping. It’s the JavaScript your users have to download, parse, and execute before they can interact with your product. Most developers think about it roughly once during the initial setup and then stop thinking about it entirely as the codebase grows. Features get added, dependencies get installed, and the bundle gets quietly heavier with every sprint until your Lighthouse score starts looking embarrassing.&lt;/p&gt;
&lt;p id="aadd"&gt;According to HTTP Archive data, the median JavaScript payload for desktop users is over 500 KB, with mobile users often downloading significantly more. That’s the median. Plenty of production React apps are shipping multiples of that. On a mid-range phone on a decent connection, that’s a real wait before anything is interactive and most users won’t stick around for it. &lt;a href="https://www.growin.com/blog/react-performance-optimization-2025/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Growin&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="5925"&gt;Mistake 7: Not using &lt;code&gt;React.lazy&lt;/code&gt; and &lt;code&gt;Suspense&lt;/code&gt;&lt;br&gt;
&lt;/h2&gt;
&lt;p id="7055"&gt;The default behavior in a React app without code splitting is simple: everything ships together. Your landing page bundle includes the code for your settings panel, your admin dashboard, your onboarding flow, and every modal you’ve ever built. The user visiting your homepage for the first time downloads all of it, even though they haven’t navigated anywhere yet and statistically might never open half of those routes.&lt;/p&gt;
&lt;p id="a628"&gt;&lt;code&gt;React.lazy&lt;/code&gt; and &lt;code&gt;Suspense&lt;/code&gt; exist specifically for this. They let you split your bundle at the route or component level, loading chunks only when they're actually needed.&lt;/p&gt;
&lt;pre&gt;&lt;span id="ec24"&gt;&lt;span&gt;const&lt;/span&gt; &lt;span&gt;AdminDashboard&lt;/span&gt; = &lt;span&gt;React&lt;/span&gt;.&lt;span&gt;lazy&lt;/span&gt;(&lt;span&gt;() =&amp;gt;&lt;/span&gt; &lt;span&gt;import&lt;/span&gt;(&lt;span&gt;'./AdminDashboard'&lt;/span&gt;));&lt;br&gt;&lt;br&gt;&lt;span&gt;function&lt;/span&gt; &lt;span&gt;App&lt;/span&gt;() {&lt;br&gt;  &lt;span&gt;return&lt;/span&gt; (&lt;br&gt;    &amp;lt;Suspense fallback={&amp;lt;Spinner /&amp;gt;}&amp;gt;&lt;br&gt;      &amp;lt;AdminDashboard /&amp;gt;&lt;br&gt;    &amp;lt;/Suspense&amp;gt;&lt;br&gt;  );&lt;br&gt;}&lt;/span&gt;&lt;/pre&gt;
&lt;p id="d881"&gt;&lt;code&gt;React.lazy()&lt;/code&gt; and &lt;code&gt;Suspense&lt;/code&gt;, when combined with route-level code splitting or dynamic imports, offer a reliable and modern solution to optimize loading behavior. &lt;a href="https://www.growin.com/blog/react-performance-optimization-2025/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Growin&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p id="2744"&gt;The gains here can be significant. A route-level split on a mid-sized app commonly cuts the initial bundle by 30–50%, which translates directly into faster time-to-interactive. The user landing on your homepage gets only what they need to render that page. Everything else loads on demand, in the background, when it becomes relevant.&lt;/p&gt;
&lt;p id="7382"&gt;The reason developers skip this is usually that the app worked fine without it during development. Local development has no network latency and no cold cache, so a 2MB bundle feels instantaneous. Your users on mobile networks in the real world are having a measurably worse time, and the Profiler won’t show you that you have to look at your bundle analyzer and your Core Web Vitals to see it.&lt;/p&gt;
&lt;p id="7ef8"&gt;If you’re on Next.js or Remix, a lot of this is handled for you at the framework level. If you’re on a custom Vite or Webpack setup, route-level lazy loading is one of the highest-leverage changes you can make with the least amount of code.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="6b36"&gt;Mistake 8: Importing entire libraries when you need one function&lt;/h2&gt;
&lt;p id="b1f9"&gt;This one has been a known issue for years and it still shows up constantly. The pattern looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;span id="305e"&gt;&lt;span&gt;import&lt;/span&gt; _ &lt;span&gt;from&lt;/span&gt; &lt;span&gt;'lodash'&lt;/span&gt;;&lt;br&gt;&lt;br&gt;&lt;span&gt;const&lt;/span&gt; result = _.&lt;span&gt;groupBy&lt;/span&gt;(data, &lt;span&gt;'category'&lt;/span&gt;);&lt;/span&gt;&lt;/pre&gt;
&lt;p id="0feb"&gt;You needed &lt;code&gt;groupBy&lt;/code&gt;. You imported all of lodash. Lodash is around 70KB minified and gzipped which is not catastrophic on its own, but it's also not free, and it compounds with every other library you're doing the same thing with.&lt;/p&gt;
&lt;p id="8d54"&gt;Importing an entire library rather than just one or more components can dramatically increase your bundle size. A large bundle can slow download times, thereby negatively affecting the user experience. Use named imports rather than default imports, and use code-splitting so you can load only the code you need. &lt;a href="https://www.tftus.com/blog/common-bugs-in-reactjs-development" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;TFTUS Official Blog&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p id="e8cb"&gt;&lt;strong&gt;The fix is named imports, which tree-shake correctly:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span id="c9eb"&gt;&lt;span&gt;import&lt;/span&gt; groupBy &lt;span&gt;from&lt;/span&gt; &lt;span&gt;'lodash/groupBy'&lt;/span&gt;;&lt;br&gt;&lt;span&gt;// or&lt;/span&gt;&lt;br&gt;&lt;span&gt;import&lt;/span&gt; { groupBy } &lt;span&gt;from&lt;/span&gt; &lt;span&gt;'lodash-es'&lt;/span&gt;;&lt;/span&gt;&lt;/pre&gt;
&lt;p id="ae53"&gt;The &lt;code&gt;lodash-es&lt;/code&gt; variant is the ESM version of lodash, which plays nicely with modern bundlers and tree-shaking. Only the functions you actually import end up in your bundle.&lt;/p&gt;
&lt;p id="2db9"&gt;Lodash is just the most common example. The same mistake gets made with &lt;code&gt;moment.js&lt;/code&gt; a library that is famously large and should almost always be replaced with &lt;code&gt;date-fns&lt;/code&gt; or the native &lt;code&gt;Intl&lt;/code&gt; API at this point. It gets made with UI component libraries that export everything from a single entry point. It gets made with icon packs where someone imports the entire icon set to use three icons.&lt;/p&gt;
&lt;p id="d463"&gt;The way to catch this at scale is the &lt;a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Webpack Bundle Analyzer&lt;/strong&gt;&lt;/a&gt; or the equivalent for Vite. Run it once on your production build and look at what’s actually inside your bundle. The results are often surprising in ways that are equal parts educational and mortifying. You will almost certainly find at least one dependency in there that you forgot you installed, and at least one that you installed for something you ended up not shipping.&lt;/p&gt;
&lt;p id="663b"&gt;This is worth doing before you spend time on any runtime optimization. It doesn’t matter how well-memoized your components are if you’re making users wait three seconds to download the JavaScript before any of that code runs.&lt;/p&gt;
&lt;blockquote&gt;

&lt;p id="84f6"&gt;These two mistakes live in a different category from the previous six because they’re invisible during development and only show up in production metrics. Your app feels fast locally. Your users experience something different. The fix for both requires adding a step to your workflow looking at bundle output, running Lighthouse against a production build, checking Core Web Vitals in Search Console rather than changing how you write components.&lt;/p&gt;

&lt;p id="69e5"&gt;That habit of looking at what you’re actually shipping is one of the things that separates engineers who ship fast apps from engineers who ship apps that feel fast on their own machine.&lt;/p&gt;


&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="045a"&gt;The tools you’ve been ignoring&lt;/h2&gt;
&lt;p id="086b"&gt;The previous eight mistakes were all things you did. These last two are things you didn’t do which somehow makes them worse. There’s a special category of performance problem that exists not because you wrote something wrong, but because you never reached for the tool that would have either prevented the problem or shown you it existed.&lt;/p&gt;
&lt;p id="23df"&gt;These aren’t obscure. They’re not advanced. They ship with React or have been in the ecosystem for years. They just require you to stop and think about scale before scale becomes the problem, and most of us don’t do that until a user complains or a Lighthouse score goes red.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="cecc"&gt;Mistake 9: Rendering a thousand items when you could render twelve&lt;/h2&gt;
&lt;p id="a261"&gt;At some point in almost every data-heavy React app, someone builds a list. The list works great with twenty items in development. It goes to production. The list now has two thousand items. Scrolling feels like dragging furniture across carpet. The component that renders each row is perfectly optimized memoized, no inline functions, stable keys and it doesn’t matter at all, because you’re rendering all two thousand of them simultaneously into the DOM whether the user can see them or not.&lt;/p&gt;
&lt;p id="d614"&gt;This is the problem that virtualization solves, and it’s one of those solutions that feels almost too simple once you understand it. Instead of rendering every item in the list, you render only the ones currently visible in the viewport plus a small buffer above and below for smooth scrolling. As the user scrolls, items that leave the viewport get unmounted, new ones get mounted. The DOM stays small. Performance stays flat regardless of dataset size.&lt;/p&gt;
&lt;pre&gt;&lt;span id="cab9"&gt;&lt;span&gt;import&lt;/span&gt; { &lt;span&gt;FixedSizeList&lt;/span&gt; &lt;span&gt;as&lt;/span&gt; &lt;span&gt;List&lt;/span&gt; } &lt;span&gt;from&lt;/span&gt; &lt;span&gt;'react-window'&lt;/span&gt;;&lt;br&gt;&lt;br&gt;&amp;lt;List&lt;br&gt;  height={600}&lt;br&gt;  itemCount={items.length}&lt;br&gt;  itemSize={72}&lt;br&gt;  width="100%"&lt;br&gt;&amp;gt;&lt;br&gt;  {({ index, style }) =&amp;gt; (&lt;br&gt;    &amp;lt;div style={style}&amp;gt;&lt;br&gt;      &amp;lt;UserCard user={items[index]} /&amp;gt;&lt;br&gt;    &amp;lt;/div&amp;gt;&lt;br&gt;  )}&lt;br&gt;&amp;lt;/List&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p id="d032"&gt;[&lt;code&gt;react-window&lt;/code&gt;] only renders visible items, making scrolling smooth. Rendering thousands of DOM nodes at once kills performance. &lt;a href="https://dev.to/frontendtoolstech/react-performance-optimization-best-practices-for-2025-2g6b" rel="noopener ugc nofollow"&gt;&lt;strong&gt;DEV Community&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p id="5ee9"&gt;&lt;code&gt;&lt;a href="https://github.com/bvaughn/react-window" rel="noopener ugc nofollow noreferrer"&gt;react-window&lt;/a&gt;&lt;/code&gt; is the standard library for this, maintained by Brian Vaughn who also built the React DevTools. It's small, fast, and well-documented. &lt;code&gt;&lt;a href="https://virtuoso.dev/" rel="noopener ugc nofollow noreferrer"&gt;react-virtuoso&lt;/a&gt;&lt;/code&gt; is a newer alternative with more built-in features if you need variable item heights or grouped lists. Both work on the same principle.&lt;/p&gt;
&lt;p id="dca5"&gt;The reason developers skip virtualization is usually that they don’t anticipate scale. The list has twenty items, the list works, ship it. Then the list grows. The performance cost of rendering a DOM node for every item in a list scales linearly double the items, roughly double the render time, double the memory usage, double the layout work the browser has to do on every scroll event. By the time you feel it, you often have a list that’s expensive to refactor because everything downstream depends on how it currently works.&lt;/p&gt;
&lt;p id="b91d"&gt;The rule of thumb: if a list could plausibly ever exceed fifty items in production, plan for virtualization from the start. It’s much easier to set up on a new component than to retrofit it onto a complex one that’s been in production for six months.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="e8de"&gt;Mistake 10: Never opening the React DevTools Profiler&lt;/h2&gt;
&lt;p id="5d06"&gt;This is the one that quietly enables every other mistake on this list to survive as long as it does. The Profiler has been part of React DevTools since React 16.5. It shows you exactly which components re-rendered, how long each render took, and why the render was triggered. It is, genuinely, one of the most useful debugging tools in the frontend ecosystem. Most developers have it installed and have never clicked the record button.&lt;/p&gt;
&lt;p id="fb30"&gt;The workflow is straightforward. Open DevTools. Go to the Profiler tab. Hit record. Interact with the part of your app that feels slow. Stop recording. Look at the flame graph.&lt;/p&gt;
&lt;p id="3a33"&gt;What you’re looking for: components that re-render more often than they should, and components whose render time is disproportionately long. The Profiler color-codes this for you gray means fast, yellow means moderate, orange and red mean you should probably look at this. You can click any bar in the graph to see exactly why that component rendered, including which prop or state value changed to trigger it.&lt;/p&gt;
&lt;p id="e01f"&gt;React DevTools Profiler will show you whether a component re-render is expensive enough to justify memoization. In React development, especially as applications grow more interactive and component-driven, it’s easy to introduce performance issues without realizing it. &lt;a href="https://www.growin.com/blog/react-performance-optimization-2025/" rel="noopener ugc nofollow noreferrer"&gt;Growin&lt;/a&gt;&lt;/p&gt;
&lt;p id="f1cb"&gt;This matters because performance optimization without measurement is just guessing. You add &lt;code&gt;useCallback&lt;/code&gt; somewhere because it seems like the right area. You split a component because it feels too big. You might be right. You might be optimizing something that was already fast while the actual bottleneck sits three components over, untouched. No cargo-cult programming just understanding the system and applying targeted fixes. &lt;a href="https://dev.to/pockit_tools/why-your-react-app-re-renders-too-much-a-deep-dive-into-performance-optimization-2oh3" rel="noopener ugc nofollow"&gt;&lt;strong&gt;DEV Community&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p id="0e20"&gt;The Profiler removes the guessing. It tells you where the fire actually is, not where you think it might be. Every fix on this list inline functions, context memoization, colocated state, code splitting lands differently depending on your specific app. The Profiler is how you know which ones to prioritize and whether your changes actually did anything.&lt;/p&gt;
&lt;p id="27dd"&gt;A practical habit: run the Profiler on any feature before you ship it, not after someone complains. It takes three minutes and it has saved me from shipping performance regressions more than once. It’s also genuinely interesting seeing exactly how React thinks about your component tree teaches you things about the framework that no article can.&lt;/p&gt;
&lt;blockquote&gt;

&lt;p id="4a95"&gt;These two mistakes share the same underlying cause: not thinking about scale and measurement until after the problem exists. Virtualization is what you add when you think ahead about large datasets. The Profiler is what you use when you want to stop guessing and start knowing.&lt;/p&gt;

&lt;p id="b0a7"&gt;Together, they close the loop on the entire list. The first eight mistakes give you specific patterns to avoid. These two give you the habit of catching what slips through anyway.&lt;/p&gt;


&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="4284"&gt;You’re not off the hook just because React 19 exists&lt;/h2&gt;
&lt;p id="f42c"&gt;Here’s the take that’s going to age either very well or very poorly: most of what’s on this list is going to become less relevant over the next two years, and that should make you more careful, not less.&lt;/p&gt;
&lt;p id="17b7"&gt;React 19’s biggest change is the React Compiler, which automatically optimizes components without manual &lt;code&gt;useMemo&lt;/code&gt; or &lt;code&gt;useCallback&lt;/code&gt; wrappers. It analyzes your code at build time and applies memoization where it's safe. The inline function problem, the &lt;code&gt;useCallback&lt;/code&gt; cargo-culting, a chunk of the re-render chaos the compiler handles a meaningful portion of that automatically. That's genuinely good news and the React team deserves credit for it. &lt;a href="https://dev.to/alex_bobes/react-performance-optimization-15-best-practices-for-2025-17l9" rel="noopener ugc nofollow"&gt;DEV Community&lt;/a&gt;&lt;/p&gt;
&lt;p id="c6b4"&gt;But I keep coming back to something that doesn’t change regardless of what the compiler does. Architecture isn’t a compiler problem. State living in the wrong place, context providers that re-render everything downstream, &lt;code&gt;useEffect&lt;/code&gt; being used as a catch-all for logic that should live elsewhere none of that gets fixed at build time. The compiler won't save you from architectural issues like overly broad context providers or massive component trees. You still have to understand the system well enough to design it correctly. &lt;a href="https://dev.to/alex_bobes/react-performance-optimization-15-best-practices-for-2025-17l9" rel="noopener ugc nofollow"&gt;&lt;strong&gt;DEV Community&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p id="daef"&gt;And here’s the uncomfortable part: if you learned React by reaching for &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt; without understanding why, the compiler bailing you out doesn't mean you understood what it fixed. It means you got lucky. The next framework, the next abstraction, the next performance problem that falls outside what the compiler covers that's where the gap shows up.&lt;/p&gt;
&lt;p id="204a"&gt;Performance isn’t a checklist you run through once before a release. It’s a design skill. It lives in the decisions you make about where state goes, how components are composed, what you ship in the initial bundle, and whether you ever actually look at what your app is doing under load. The engineers I’ve seen consistently ship fast products aren’t the ones who know the most hooks. They’re the ones who profile before they optimize, think about scale before it’s a crisis, and treat the bundle as something they’re responsible for not something that happens automatically.&lt;/p&gt;
&lt;p id="91eb"&gt;The React compiler is a great tool. So is the Profiler. So is &lt;code&gt;react-window&lt;/code&gt;. None of them replace the judgment call about whether your component tree makes sense.&lt;/p&gt;
&lt;p id="c352"&gt;&lt;strong&gt;If this list had a single takeaway it’d be this:&lt;/strong&gt; open the Profiler on your current project today, before you read another article, before you install another dependency. Record thirty seconds of normal usage. See what’s actually happening. You might be surprised. You might be horrified. Either way, you’ll know something real and that’s where every fix on this list actually starts.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="87b2"&gt;Drop your worst React performance horror story in the comments. Bonus points if the root cause was on this list and you didn’t know it until a user complained.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="be4a"&gt;Helpful resources&lt;/h2&gt;
&lt;ul&gt;

&lt;li id="dd9f"&gt;

&lt;a href="https://react.dev/reference/react/Profiler" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;React DevTools Profiler docs&lt;/strong&gt;&lt;/a&gt; official guide to actually using the tool&lt;/li&gt;

&lt;li id="5efd"&gt;

&lt;a href="https://react.dev/reference/react/lazy" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;React.lazy and Suspense&lt;/strong&gt;&lt;/a&gt; React docs on code splitting&lt;/li&gt;

&lt;li id="f212"&gt;

&lt;strong&gt;R&lt;/strong&gt;&lt;a href="https://github.com/bvaughn/react-window" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;eact-window on GitHub&lt;/strong&gt;&lt;/a&gt; list virtualization by Brian Vaughn&lt;/li&gt;

&lt;li id="1eaa"&gt;

&lt;a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Webpack Bundle Analyzer&lt;/strong&gt;&lt;/a&gt; see what you’re actually shipping&lt;/li&gt;

&lt;li id="02c6"&gt;

&lt;a href="https://react.dev/learn/react-compiler" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;React Compiler docs&lt;/strong&gt;&lt;/a&gt; what it does and doesn’t do&lt;/li&gt;

&lt;li id="f34f"&gt;

&lt;a href="https://web.dev/explore/learn-core-web-vitals" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;web.dev Core Web Vitals&lt;/strong&gt;&lt;/a&gt; the metrics that actually matter to users&lt;/li&gt;

&lt;li id="eb08"&gt;

&lt;a href="https://kentcdodds.com/blog/state-colocation-will-make-your-react-app-faster" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Kent C. Dodds on state colocation&lt;/strong&gt;&lt;/a&gt; the best deep-dive on mistake #4&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>react</category>
      <category>devops</category>
    </item>
    <item>
      <title>The only AI agents article you’ll ever need</title>
      <dc:creator>&lt;devtips/&gt;</dc:creator>
      <pubDate>Fri, 15 May 2026 00:47:34 +0000</pubDate>
      <link>https://dev.to/dev_tips/the-only-ai-agents-article-youll-ever-need-51f8</link>
      <guid>https://dev.to/dev_tips/the-only-ai-agents-article-youll-ever-need-51f8</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="332c"&gt;&lt;strong&gt;From ReAct loops to production multi-agent systems everything in one place, nothing left out.&lt;/strong&gt;&lt;/h2&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p id="5f35"&gt;Somewhere between the fifteenth “AI agent” LinkedIn post and the third vendor announcing their autonomous workflow platform, I stopped nodding along and started asking the obvious question nobody seemed to want to answer:&lt;/p&gt;
&lt;p id="2848"&gt;&lt;em&gt;What is actually running here?&lt;/em&gt;&lt;/p&gt;
&lt;p id="4bbe"&gt;Because the word “agent” has been doing a lot of heavy lifting lately. It’s been stretched over chatbots with a search button, over multi-step pipelines glued together with vibes, and over genuinely sophisticated systems that can decompose a task, call external APIs, reflect on their own output, and course-correct all without you touching a keyboard. Those are not the same thing. Not even close.&lt;/p&gt;
&lt;p id="e8ac"&gt;I’ve spent the last few months going deep on this. Built agents that failed in embarrassing ways. Read the research, the docs, the Reddit threads where someone’s agent looped itself into a $600 API bill at midnight. Took the courses. Talked to people shipping this stuff in production at scale. And I distilled all of it down into this article.&lt;/p&gt;
&lt;p id="b2d2"&gt;This is not a hype piece. There are no screenshots of a ChatGPT conversation doing something mildly impressive. This is the full picture from the first-principles question of what an agent actually is, through the design patterns that separate demos from real systems, all the way to the production concerns that nobody tweets about: evaluation, latency, cost, observability, and security.&lt;/p&gt;
&lt;p id="530f"&gt;Whether you’re just trying to understand what your team keeps talking about in standups, or you’re actively building agent systems and hitting walls, this is the article you keep open in a tab and come back to.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="7a52"&gt;&lt;strong&gt;&lt;em&gt;TL;DR:&lt;/em&gt;&lt;/strong&gt; An AI agent is an LLM inside a loop, equipped with tools, memory, and decision-making logic. Building one that demos well takes an afternoon. Building one you’d actually trust with real work takes a different mindset closer to distributed systems design than prompt engineering. This article covers both ends of that spectrum and everything in between.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="4b47"&gt;What an agent actually is&lt;/h2&gt;
&lt;p id="0f51"&gt;If you’ve used ChatGPT to write an email, you’ve used an LLM. You gave it a prompt, it gave you an output, done. One shot. Linear. The model doesn’t remember what it did, doesn’t check its own work, doesn’t go looking for missing information. It just generates the next most likely tokens until it hits the end and stops.&lt;/p&gt;
&lt;p id="aecf"&gt;An agent is what happens when you take that same model and put it inside a loop.&lt;/p&gt;
&lt;p id="2e37"&gt;Instead of prompt-in, response-out, you get something closer to how a human actually tackles a non-trivial task. You plan a little. You gather some information. You do a first pass. You read it back, notice what’s wrong, and fix it. You check one more thing. You finish. That back-and-forth, that iterative reasoning over multiple steps that’s the core of what makes something an agent rather than a fancy autocomplete.&lt;/p&gt;
&lt;p id="2c9b"&gt;The technical name for this loop is the &lt;strong&gt;ReAct pattern&lt;/strong&gt;: Reason, Act, Observe, repeat. The model reasons about what to do next. It acts usually by calling a tool, running a search, querying a database, executing some code. It observes the result. Then it either gives you a final answer or loops back to reason again based on what it just learned. That cycle is the engine underneath almost every agent system you’ll encounter, from the simplest LangChain pipeline to Claude Code rewriting your entire test suite.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="436" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A840%2F1%2Ai4R5izLDEXWzeABGuCpg4g.jpeg"&gt;&lt;p id="9e56"&gt;Here’s what makes this more powerful than it sounds. Each pass through the loop adds depth. The model isn’t trying to solve everything in one shot under the pressure of a single context window it’s allowed to work iteratively, to gather information it didn’t have at step one, to catch mistakes it made in step two. The output at the end of three loops is almost always better than the output of one. Not because the model got smarter, but because the architecture gave it room to think.&lt;/p&gt;
&lt;p id="a6ba"&gt;The practical upside of this shows up immediately in tasks that need accuracy and sourcing. Legal research where you have to cite specific cases. Customer support that requires pulling account details before responding. Code generation that needs to run the code, read the error, and try again. Any domain where a single-pass answer is almost certainly incomplete or wrong that’s where agents earn their keep.&lt;/p&gt;
&lt;p id="d242"&gt;Here’s the mental model I use: a regular LLM call is a consultant who reads your brief once and writes a report on the plane. An agent is that same consultant who actually does the research, drafts something, reads it back, realizes they missed a key detail, goes and finds it, then rewrites the section. Same intelligence, different process. The process is what changes the output quality.&lt;/p&gt;
&lt;p id="babd"&gt;One thing worth clearing up early: the model itself isn’t what makes something an agent. You can build a mediocre agent on GPT-4 and a great one on a smaller, faster model with a well-designed loop and the right tools. The architecture and the task decomposition matter more than the leaderboard position of the underlying LLM. Remember that when someone tries to sell you on “the most agentic model” the model is one part of the system, not the whole thing.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="8764"&gt;The core building blocks&lt;/h2&gt;
&lt;p id="2c79"&gt;Before you write a single line of agent code, you need to understand four things. Get these right and everything else becomes easier to reason about. Get them wrong and you’ll spend weeks debugging behavior that feels random but isn’t.&lt;/p&gt;
&lt;p id="344b"&gt;&lt;strong&gt;Context is the agent’s entire world.&lt;/strong&gt; Whatever isn’t in the context window doesn’t exist as far as the model is concerned. Context engineering deciding what goes in there is one of the most underrated skills in agent development. It includes the task description, the agent’s role, any memory from previous steps, available tools, and relevant background knowledge. A poorly engineered context produces an agent that hallucinates, repeats itself, or completely ignores the instructions you thought were obvious. Most agent bugs aren’t model bugs. They’re context bugs.&lt;/p&gt;
&lt;p id="58bc"&gt;&lt;strong&gt;Memory comes in two flavors.&lt;/strong&gt; Short-term memory is what the agent writes down as it works intermediate results, tool outputs, notes to itself. Long-term memory is lessons from previous runs, stored and loaded at the start of each new task. The combination is what lets an agent improve over time rather than starting from zero on every execution. Knowledge is different from both it’s static reference material you load upfront. Documentation, PDFs, database access. The agent reads from it but doesn’t update it.&lt;/p&gt;
&lt;p id="fce3"&gt;&lt;strong&gt;Task decomposition is the part nobody talks about enough.&lt;/strong&gt; The rule is simple: break each step down until a single LLM call or a single tool can handle it cleanly. If a step is too big, the output gets sloppy. The exercise is to think about how you’d do the task yourself what are the actual discrete steps then figure out which of those steps map to an LLM call, which map to a tool call, and which map to a bit of regular code. When something isn’t working, nine times out of ten a step is too coarse.&lt;/p&gt;
&lt;p id="e30f"&gt;&lt;strong&gt;Guardrails are the bouncer at the door.&lt;/strong&gt; Because LLMs are non-deterministic, you can’t assume the output will always be in the right format, the right length, or factually consistent with the sources the agent just retrieved. Guardrails are the layer that catches these failures before they reach the user or before they get passed to the next step in the pipeline and silently corrupt everything downstream. Some guardrails are just code: check the output format, validate the schema, enforce length limits. Others use a second LLM to judge quality. And sometimes the right guardrail is a human checkpoint especially for anything irreversible.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="436" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A840%2F1%2AqO9GCQwhvRGryKoeCusIsQ.jpeg"&gt;&lt;p id="968f"&gt;Four concepts. Everything else in agent design is built on top of them.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="6fbf"&gt;Four design patterns that actually work&lt;/h2&gt;
&lt;p id="f8cd"&gt;Once your building blocks are solid, the next question is how you structure the actual behavior of the agent. There are four patterns that show up in almost every serious agent system. You don’t always need all four, but you need to know all four.&lt;/p&gt;
&lt;h3 id="2a93"&gt;&lt;strong&gt;Reflection&lt;/strong&gt;&lt;/h3&gt;
&lt;p id="f441"&gt;The simplest and most effective upgrade you can make to any agent. Instead of shipping the first output, the agent critiques it and rewrites it.&lt;/p&gt;
&lt;p id="ef85"&gt;The model produces something, reads it back with a prompt like “what’s wrong with this and how would you fix it,” then revises. That second pass almost always improves the result not because the model is smarter on round two, but because reviewing is an easier cognitive task than generating from scratch. You’re offloading the hard part across two steps instead of cramming it into one.&lt;/p&gt;
&lt;p id="e4f5"&gt;Reflection is especially powerful when you can add external feedback to the loop. Write code, run it, feed the error back, try again. Generate JSON, validate it against a schema, send the validation errors back if it fails. That concrete feedback signal is what separates reflection from just asking the model to “try harder.”&lt;/p&gt;
&lt;p id="1fa5"&gt;The tradeoff is latency and cost you’re doing multiple passes. Test with and without it before you commit.&lt;/p&gt;
&lt;h3 id="4953"&gt;&lt;strong&gt;Tool use&lt;/strong&gt;&lt;/h3&gt;
&lt;p id="ee79"&gt;An LLM by itself is a text generator. It doesn’t know what time it is, can’t query your database, can’t run code, and has no idea what’s in your company’s internal docs. Tools fix that.&lt;/p&gt;
&lt;p id="9ebc"&gt;You define a set of functions web search, database query, code execution, calendar access, whatever your use case needs and the model decides when and which ones to call. Under the hood, the model doesn’t actually execute anything. It outputs a structured request, your code runs the function, and the result gets fed back into the context. The model uses that result to continue.&lt;/p&gt;
&lt;p id="349b"&gt;Well-designed tools have clear names, plain-English descriptions of when to use them, typed input schemas, and clean error handling. Think of them as an API your agent uses. Document them like one.&lt;/p&gt;
&lt;h3 id="b7aa"&gt;&lt;strong&gt;Planning&lt;/strong&gt;&lt;/h3&gt;
&lt;p id="2934"&gt;Instead of following a hardcoded sequence of steps, the agent decides what to do and in what order.&lt;/p&gt;
&lt;p id="96d9"&gt;You give it a toolkit, prompt it to create a step-by-step plan, and execute that plan running each tool, feeding results back, and repeating until the task is done. The model acts as its own project manager. This is powerful for tasks where you can’t anticipate every possible path upfront, like a customer service agent handling wildly different request types.&lt;/p&gt;
&lt;p id="208a"&gt;The catch: more autonomy means more unpredictability. Planning agents need tight guardrails, permission checks, and good logging. The strongest current use case is agentic coding systems where the task space is well-defined even if the exact steps aren’t.&lt;/p&gt;
&lt;h3 id="e9cf"&gt;&lt;strong&gt;Multi-agent collaboration&lt;/strong&gt;&lt;/h3&gt;
&lt;p id="71f5"&gt;Some tasks are too complex, too long, or too varied for one agent to handle well. The answer is the same one humans figured out a long time ago: build a team.&lt;/p&gt;
&lt;p id="ff95"&gt;Each agent gets a specific role and only the tools that role needs. A researcher agent does web search and retrieval. A writer agent handles drafting. A reviewer agent checks quality. A manager agent coordinates the others. Specialization produces better output than one generalist trying to do everything inside a single sprawling context window.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="436" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A840%2F1%2AmxOW-Al381je8GLuLiPq3g.jpeg"&gt;&lt;p id="be0a"&gt;The coordination patterns range from simple sequential handoffs researcher finishes, passes to writer, writer passes to reviewer to parallel execution where independent agents run simultaneously and merge results. Most production systems start sequential and add parallelism only where latency actually matters.&lt;/p&gt;
&lt;p id="3da0"&gt;Multi-agent systems are not the default answer. They add real complexity: agents can conflict, communication overhead adds up, and debugging a failure that happened three agents deep is genuinely painful. Start with one agent. Add a second only when the first one has a clear ceiling it can’t break through.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="0dd3"&gt;Shipping to production&lt;/h2&gt;
&lt;p id="f7df"&gt;This is the section that doesn’t make it into the demo videos. Everything up to this point gets you a working agent. This is what gets you a trustworthy one.&lt;/p&gt;
&lt;h3 id="b63a"&gt;&lt;strong&gt;Evaluate before you optimize&lt;/strong&gt;&lt;/h3&gt;
&lt;p id="90f8"&gt;The most common mistake people make with agents is trying to improve something they haven’t measured. Before you touch a prompt, swap a model, or restructure a pipeline, you need to know what’s actually failing and how often.&lt;/p&gt;
&lt;p id="6080"&gt;Some evals are simple.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="1f46"&gt;&lt;strong&gt;&lt;em&gt;Does the customer service agent correctly identify whether an item is in stock?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="295d"&gt;That’s a pass/fail check you can automate. Others are harder is this research report actually good? For those, use a second LLM as a judge. Give it a consistent rubric, have it score outputs on a 1–5 scale, and track that score across runs.&lt;/p&gt;
&lt;p id="20cb"&gt;Evaluate at two levels. Component-level tells you which specific step is underperforming. End-to-end tells you whether the final output is actually good. If end-to-end scores are low but every component scores fine, the problem is in the handoffs between steps that’s a different fix than a bad prompt.&lt;/p&gt;
&lt;p id="5edd"&gt;Start evaluating on day one. An imperfect eval that exists beats a perfect eval you’re still designing.&lt;/p&gt;
&lt;h3 id="11f2"&gt;&lt;strong&gt;Latency and cost are the same problem&lt;/strong&gt;&lt;/h3&gt;
&lt;p id="1291"&gt;Every extra LLM call costs time and money. In agent systems, those calls stack up fast.&lt;/p&gt;
&lt;p id="aa64"&gt;The fix is the same for both: measure each step, then attack the biggest buckets. Parallelize anything that doesn’t depend on the step before it multiple web searches, multiple document fetches, multiple sub-tasks that can run simultaneously. Right-size your models use a smaller, faster model for simple steps like keyword extraction or format validation, and reserve the expensive one for actual reasoning. Cache aggressively search results, embeddings, intermediate summaries. If the input is identical, don’t recompute.&lt;/p&gt;
&lt;p id="9b1b"&gt;One research agent run might cost a few cents. At a thousand runs a day that’s hundreds of dollars a month. Know your per-run cost before you scale.&lt;/p&gt;
&lt;h3 id="c00b"&gt;&lt;strong&gt;Log everything, assume nothing&lt;/strong&gt;&lt;/h3&gt;
&lt;p id="905d"&gt;Traditional software fails with stack traces. Agent systems fail silently the output looks plausible, the logs show no errors, and something is still wrong.&lt;/p&gt;
&lt;p id="1ff4"&gt;Observability for agents means tracing every decision: what did the agent plan to do, what tool did it call, what came back, what did it decide next. Tools like &lt;a href="https://www.langchain.com/langsmith" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;LangSmith&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://wandb.ai" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Weights &amp;amp; Biases&lt;/strong&gt;&lt;/a&gt; are built for exactly this. When something breaks and it will you want to be able to replay the exact sequence of steps that produced the bad output and see precisely where it went sideways.&lt;/p&gt;
&lt;p id="3dd6"&gt;Beyond individual traces, track aggregate metrics over time. Hallucination rate. Task success rate. Average cost per run. These trend lines tell you whether your changes are actually helping or just moving the problem around.&lt;/p&gt;
&lt;p id="d30d"&gt;&lt;strong&gt;Sandbox your code execution&lt;/strong&gt;&lt;/p&gt;
&lt;p id="54fe"&gt;If your agent can write and run code and the useful ones usually can you need to treat that capability like a loaded gun. Run all code in an isolated container that gets destroyed after each execution. Set hard timeouts and memory limits. Whitelist the libraries it’s allowed to use. Never let agent-generated code write to anywhere that matters or reach the network unless you explicitly decided it should.&lt;/p&gt;
&lt;p id="a649"&gt;The failure mode here isn’t theoretical. An agent with unrestricted code execution and a bad prompt is a very expensive, very fast way to ruin your afternoon.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="436" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A840%2F1%2AXd0Ii0m-AlKhBz_wbSBFSA.jpeg"&gt;&lt;p id="e7fd"&gt;Production isn’t a different version of your demo. It’s a different discipline entirely.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="96e9"&gt;The job changed. Most people haven’t caught up yet.&lt;/h2&gt;
&lt;p id="5c06"&gt;Here’s the take I’ll leave you with, and you can disagree with me in the comments: the bottleneck in AI development right now isn’t the models. The models are good. The bottleneck is engineers who understand how to build reliable systems around them.&lt;/p&gt;
&lt;p id="eb60"&gt;Prompting was never the skill. It was always the entry point. The actual work designing context, decomposing tasks, wiring tools, evaluating outputs, controlling costs, tracing failures that’s systems design. It always was. The wrapper just changed.&lt;/p&gt;
&lt;p id="ecdc"&gt;The developers who are going to do interesting things with agents in the next few years aren’t the ones who found the best jailbreak or the cleverest chain-of-thought trick. They’re the ones who treat agents the way they treat any other distributed system: with logging, with testing, with failure modes they planned for, with an understanding of what happens when one component does something unexpected.&lt;/p&gt;
&lt;p id="3f92"&gt;That’s not a pessimistic take. If anything it’s the opposite. It means the skills you already have debugging, systems thinking, knowing when to add complexity and when not to transfer directly. You’re not starting from zero. You’re applying what you know to a new kind of component.&lt;/p&gt;
&lt;p id="01d7"&gt;Agents are going to keep getting more capable, more autonomous, and more embedded in real workflows. The tooling is improving fast. The patterns are stabilizing. This is a good time to actually understand the stack rather than just use the abstraction on top of it.&lt;/p&gt;
&lt;p id="2ecd"&gt;Build something small. Evaluate it honestly. Add one pattern at a time. Log everything from day one. That’s the whole playbook.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="a7d7"&gt;Helpful resources&lt;/h2&gt;
&lt;ul&gt;

&lt;li id="52e3"&gt;

&lt;a href="https://www.anthropic.com/research/building-effective-agents" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Anthropic’s guide to building effective agents&lt;/strong&gt;&lt;/a&gt; the clearest first-principles breakdown of agent design patterns available&lt;/li&gt;

&lt;li id="0223"&gt;

&lt;a href="https://docs.langchain.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;LangChain documentation&lt;/strong&gt;&lt;/a&gt; practical starting point for building agent pipelines in Python&lt;/li&gt;

&lt;li id="1663"&gt;

&lt;a href="https://www.langchain.com/langsmith" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;LangSmith&lt;/strong&gt;&lt;/a&gt; tracing and evaluation tooling built specifically for LLM applications&lt;/li&gt;

&lt;li id="fe64"&gt;

&lt;a href="https://gerred.github.io/building-an-agentic-system/core-architecture.html" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Building an agentic system&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;deep technical breakdown of how tools like Claude Code are architected under the hood&lt;/li&gt;

&lt;li id="4bb6"&gt;

&lt;a href="https://wandb.ai" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Weights &amp;amp; Biases&lt;/strong&gt;&lt;/a&gt; production monitoring and experiment tracking for ML systems&lt;/li&gt;

&lt;li id="5e8f"&gt;

&lt;a href="https://cookbook.openai.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;OpenAI Cookbook agent examples&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;real code examples for tool use, multi-agent patterns, and evals&lt;/li&gt;

&lt;li id="b0f6"&gt;

&lt;a href="https://www.reddit.com/r/LocalLLaMA/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;r/LocalLLaMA&lt;/strong&gt;&lt;/a&gt; where practitioners actually talk about what’s working and what isn’t&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Go vs Rust: the only backend language debate that actually matters in 2026</title>
      <dc:creator>&lt;devtips/&gt;</dc:creator>
      <pubDate>Thu, 14 May 2026 08:02:23 +0000</pubDate>
      <link>https://dev.to/dev_tips/go-vs-rust-the-only-backend-language-debate-that-actually-matters-in-2026-4foi</link>
      <guid>https://dev.to/dev_tips/go-vs-rust-the-only-backend-language-debate-that-actually-matters-in-2026-4foi</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="a41c"&gt;&lt;strong&gt;You don’t need to pick one. You need to know which fight each one was built for.&lt;/strong&gt;&lt;/h2&gt;
&lt;span&gt;&lt;/span&gt;&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p id="2baa"&gt;There’s a certain kind of developer who treats language choice like a religion.&lt;/p&gt;
&lt;p id="4298"&gt;Go devs will tell you Rust is overkill.&lt;/p&gt;
&lt;p id="ee09"&gt;Rust devs will tell you Go is for people who don’t understand memory.&lt;/p&gt;
&lt;p id="b992"&gt;Both are partially right. Both are completely missing the point.&lt;/p&gt;
&lt;p id="0535"&gt;Here’s the thing: choosing between Go and Rust in 2026 isn’t really a language debate anymore. It’s a system design decision. And most of the hot takes you’ll find online are arguing about the wrong thing comparing raw benchmarks and borrow checker frustration instead of asking the actual question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="8ba8"&gt;&lt;strong&gt;Where in your architecture does this choice matter, and when does it stop mattering?&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="171a"&gt;I’ve watched teams rewrite entire Go services in Rust because a benchmarks blog post made someone nervous. I’ve also watched Rust codebases grind sprint velocity to a halt because the team picked the wrong tool for a job that really just needed a couple of goroutines and a coffee break.&lt;/p&gt;
&lt;p id="c63b"&gt;Neither is winning. Both are expensive.&lt;/p&gt;
&lt;p id="b4b4"&gt;The honest answer in 2026 is boring:&lt;/p&gt;
&lt;p id="7691"&gt;Go and Rust aren’t competing. They’re slotting into different layers of the same system. One builds the thing. The other makes the thing survive.&lt;/p&gt;
&lt;p id="715e"&gt;Understanding &lt;em&gt;which layer needs which tool&lt;/em&gt; is the only skill that actually pays out.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="c7b7"&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Go is your default. Fast enough, ships fast, scales horizontally, and your team won’t hate you for picking it. Rust enters the picture when specific parts of your system hit a wall that Go can’t resolve without throwing more money at AWS. The debate isn’t Go &lt;em&gt;or&lt;/em&gt; Rust it’s Go &lt;em&gt;first&lt;/em&gt;, Rust &lt;em&gt;where it earns its keep&lt;/em&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="2901"&gt;Go: the default loadout every backend team starts with&lt;/h2&gt;
&lt;p id="9e49"&gt;There’s a reason Go became the lingua franca of cloud-native backend development.&lt;/p&gt;
&lt;p id="3188"&gt;It’s not because Google has a good marketing department.&lt;/p&gt;
&lt;p id="1b0c"&gt;&lt;strong&gt;It’s because Go made a specific, opinionated bet:&lt;/strong&gt;&lt;/p&gt;
&lt;p id="2378"&gt;&lt;em&gt;Developer velocity and system predictability matter more than raw performance.&lt;/em&gt;&lt;/p&gt;
&lt;p id="5b1b"&gt;For most production systems, that bet pays out every single time.&lt;/p&gt;
&lt;p id="5503"&gt;Think of it like picking your starter in an RPG. Go is balanced stats across the board. Not the highest damage output, not the tankiest build but the one that gets you through 80% of the game without hitting a wall. You pick it, you learn the basics, and you’re shipping features by the end of the week.&lt;/p&gt;
&lt;p id="124e"&gt;The concurrency model is where Go genuinely earns its reputation.&lt;/p&gt;
&lt;p id="87e2"&gt;Goroutines are cheap enough to spin up thousands without sweating memory. The channel model gives you a mental framework for concurrent work that doesn’t require a degree in threading theory to reason about.&lt;/p&gt;
&lt;p id="2d4c"&gt;&lt;strong&gt;Building an SQS consumer that fans out to 20 parallel workers?&lt;/strong&gt;&lt;/p&gt;
&lt;p id="f782"&gt;That’s an afternoon in Go.&lt;/p&gt;
&lt;p id="b0d9"&gt;Writing a gRPC service sitting behind an ALB on ECS?&lt;/p&gt;
&lt;p id="4ecd"&gt;Go makes that boring in the best possible way. And boring is exactly what you want in a production system .&lt;/p&gt;
&lt;p id="83e6"&gt;&lt;strong&gt;The AWS ecosystem leans into this hard:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="d9f0"&gt;Fast startup times mean tighter Lambda cold starts&lt;/li&gt;

&lt;li id="dc38"&gt;Low memory footprint means cheaper ECS containers&lt;/li&gt;

&lt;li id="7fac"&gt;The &lt;a href="https://github.com/aws/aws-sdk-go-v2" rel="noopener ugc nofollow noreferrer"&gt;AWS SDK for Go v2&lt;/a&gt; covers everything from S3 to EventBridge without fighting you&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="c290"&gt;The broader ecosystem is settled too. &lt;a href="https://github.com/gin-gonic/gin" rel="noopener ugc nofollow noreferrer"&gt;Gin&lt;/a&gt; and &lt;a href="https://github.com/go-chi/chi" rel="noopener ugc nofollow noreferrer"&gt;Chi&lt;/a&gt; for HTTP routing, &lt;a href="https://sqlc.dev/" rel="noopener ugc nofollow noreferrer"&gt;sqlc&lt;/a&gt; for type-safe queries, &lt;a href="https://github.com/google/wire" rel="noopener ugc nofollow noreferrer"&gt;Wire&lt;/a&gt; for dependency injection if that’s your thing. The compiler errors are readable. Onboarding a new engineer onto a Go codebase takes days, not weeks.&lt;/p&gt;
&lt;p id="8a16"&gt;That last point matters more than most architecture blogs admit.&lt;/p&gt;
&lt;p id="5ab6"&gt;The best language for your system is the one your team can debug at midnight without wanting to quit their job. Go clears that bar repeatedly.&lt;/p&gt;
&lt;p id="b857"&gt;Where Go starts showing cracks is predictable if you know where to look.&lt;/p&gt;
&lt;p id="129f"&gt;The garbage collector has improved dramatically but GC pauses are still a reality in latency-sensitive workloads. Memory efficiency plateaus when you’re doing genuinely CPU-heavy work. And if you’re processing high-throughput data streams where every microsecond of predictability matters, Go’s runtime is making decisions for you that you might not agree with.&lt;/p&gt;
&lt;p id="0708"&gt;That’s not a criticism. That’s Go doing exactly what it was designed to do.&lt;/p&gt;
&lt;p id="d3b3"&gt;The tradeoff is explicit. Most teams never hit the ceiling.&lt;/p&gt;
&lt;p id="2c9d"&gt;The ones that do need a different tool for that specific corner of the system.&lt;/p&gt;
&lt;p id="a2eb"&gt;That tool has a crab mascot and a notoriously opinionated compiler.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="3130"&gt;Rust: the late-game unlock you didn’t know you needed&lt;/h2&gt;
&lt;p id="5a22"&gt;Nobody picks up Rust on day one.&lt;/p&gt;
&lt;p id="5ad1"&gt;You come to Rust after you’ve shipped something. After you’ve scaled something. After you’ve sat in an incident review trying to explain why your Go service started spiking p99 latency at a completely unpredictable interval and the only honest answer was “the GC decided it was time.”&lt;/p&gt;
&lt;p id="0bbd"&gt;That’s the origin story for most Rust adoption in production. Not ideology. Not a rewrite-everything agenda. Just a specific part of the system that stopped behaving and needed a tool with harder guarantees.&lt;/p&gt;
&lt;p id="5fe4"&gt;Think of it like unlocking a late-game weapon in an RPG. You don’t get it at the start. You earn it. And once you have it, you don’t use it on every enemy you save it for the boss fights.&lt;/p&gt;
&lt;p id="c3b1"&gt;The core promise of Rust is control.&lt;/p&gt;
&lt;p id="c1b2"&gt;No garbage collector means no GC pauses. No runtime surprises. What you write is what runs, with predictable memory behavior from the first request to the millionth. The borrow checker the thing everyone complains about until they don’t is just the compiler enforcing that promise at build time instead of letting it blow up in production at 3am.&lt;/p&gt;
&lt;p id="3dc8"&gt;&lt;strong&gt;In AWS terms, this matters in very specific places:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="2f77"&gt;High-throughput &lt;a href="https://aws.amazon.com/kinesis/" rel="noopener ugc nofollow noreferrer"&gt;Kinesis&lt;/a&gt; or Kafka consumers where processing latency compounds&lt;/li&gt;

&lt;li id="f3f7"&gt;Lambda functions where cold start time and memory ceiling are both constrained&lt;/li&gt;

&lt;li id="6afd"&gt;ECS services doing CPU-heavy transformation work on tight instance budgets&lt;/li&gt;

&lt;li id="1445"&gt;Real-time fraud detection or risk scoring pipelines where a GC pause is a business problem&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="e810"&gt;The async ecosystem has matured to the point where this is actually enjoyable to build now. &lt;a href="https://tokio.rs/" rel="noopener ugc nofollow noreferrer"&gt;Tokio&lt;/a&gt; is the async runtime most production Rust services are built on, and &lt;a href="https://github.com/tokio-rs/axum" rel="noopener ugc nofollow noreferrer"&gt;Axum&lt;/a&gt; gives you an ergonomic HTTP layer that won’t make you miss Go’s simplicity quite as much as you’d expect.&lt;/p&gt;
&lt;p id="4cb5"&gt;&lt;a href="https://crates.io/" rel="noopener ugc nofollow noreferrer"&gt;Crates.io&lt;/a&gt; has filled in the gaps that used to make Rust feel incomplete for backend work. There’s a crate for almost everything now serialization, database access, observability, AWS integrations. It’s not Go’s ecosystem in terms of breadth, but it’s not the frontier territory it was three years ago either.&lt;/p&gt;
&lt;p id="7b33"&gt;The WASM angle is worth mentioning too.&lt;/p&gt;
&lt;p id="98b8"&gt;Rust compiles to WebAssembly better than almost anything else, which opens up edge compute scenarios &lt;a href="https://workers.cloudflare.com/" rel="noopener ugc nofollow noreferrer"&gt;Cloudflare Workers&lt;/a&gt;, &lt;a href="https://www.fastly.com/products/edge-compute" rel="noopener ugc nofollow noreferrer"&gt;Fastly Compute&lt;/a&gt; where you want near-native performance in a serverless container with a sub-millisecond cold start. Go can do this too, but Rust’s output is leaner and the toolchain support is stronger.&lt;/p&gt;
&lt;p id="9689"&gt;The honest cost of Rust is team velocity at least upfront.&lt;/p&gt;
&lt;p id="ce87"&gt;The borrow checker has opinions. Strong ones. Loudly expressed. Onboarding an engineer who hasn’t written Rust before is a multi-week investment, not a multi-day one. Code review takes longer. Simple things that would take an hour in Go can take a morning in Rust while you negotiate with the compiler about who owns what.&lt;/p&gt;
&lt;p id="366f"&gt;&lt;strong&gt;But here’s the flip side nobody puts in the benchmarks post:&lt;/strong&gt;&lt;/p&gt;
&lt;p id="1d8a"&gt;Once the code compiles, it tends to just work.&lt;/p&gt;
&lt;p id="a8c8"&gt;Not “works in staging” work. Not “works until load testing” work. The class of bugs that Rust’s type system eliminates at compile time null pointer dereferences, data races, use-after-free are the exact bugs that cause 2am incidents in Go and every other language that trusts you more than the compiler does.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="7aef"&gt;The tradeoff isn’t really speed vs safety. It’s upfront cost vs long-term stability.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="4567"&gt;For the right part of your system, that’s an easy trade.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="1667"&gt;Real architecture patterns: how teams actually use both&lt;/h2&gt;
&lt;p id="c7bc"&gt;Here’s what nobody tells you in the language comparison posts:&lt;/p&gt;
&lt;p id="e973"&gt;Most production systems that use Rust don’t replace Go.&lt;/p&gt;
&lt;p id="e74e"&gt;They add Rust to specific coordinates in the architecture where Go stopped being enough. The rest stays Go. The team keeps shipping. Nobody rewrites everything and nobody has an existential crisis about the stack.&lt;/p&gt;
&lt;p id="f75f"&gt;These are the three patterns that show up repeatedly in real systems.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="436" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A840%2F1%2AYBjyE6plETSD-FthMyOu_Q.jpeg"&gt;&lt;h3 id="7a93"&gt;Pattern 1: Go orchestrates, Rust crunches&lt;/h3&gt;
&lt;p id="eaf8"&gt;This is the most common one and the cleanest.&lt;/p&gt;
&lt;p id="c653"&gt;Go handles everything that talks to other things the API layer, the service-to-service communication, the SQS consumers that fan out work, the schedulers that trigger jobs. It’s the connective tissue of the system. Fast enough, easy to reason about, easy to change.&lt;/p&gt;
&lt;p id="552e"&gt;Rust handles the part that actually does the heavy lifting.&lt;/p&gt;
&lt;p id="fd4e"&gt;A real example: a data pipeline that ingests raw events from Kinesis, runs enrichment and scoring logic, and writes results to DynamoDB. The Go service manages the consumer group, handles retries, and pushes work into a processing queue. The Rust binary does the actual scoring CPU-bound, memory-intensive, latency-sensitive. Two languages, one coherent system, each doing the job it was built for.&lt;/p&gt;
&lt;h3 id="7511"&gt;Pattern 2: Cost optimization at scale&lt;/h3&gt;
&lt;p id="e409"&gt;This one sneaks up on you.&lt;/p&gt;
&lt;p id="3af5"&gt;Your Go services are fast enough. Response times are fine. Users aren’t complaining. But the AWS bill is climbing faster than your traffic is growing, and when you dig in, you find a handful of services that are just eating CPU and memory disproportionately.&lt;/p&gt;
&lt;p id="62ee"&gt;That’s the cost optimization signal.&lt;/p&gt;
&lt;p id="67ab"&gt;Rewriting those specific services in Rust not everything, just the ones burning resources can drop memory footprint significantly and increase throughput per instance. Fewer instances needed. Smaller instance sizes. Same traffic handled for less money.&lt;/p&gt;
&lt;p id="b3af"&gt;At low scale this is a rounding error. At high scale this is a conversation your CFO notices.&lt;/p&gt;
&lt;p id="2ed2"&gt;The &lt;a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/" rel="noopener ugc nofollow noreferrer"&gt;Benchmarks Game&lt;/a&gt; numbers aren’t perfectly representative of production workloads, but the directional signal is real: Rust is consistently 2–5x more efficient than Go on CPU-bound work, and the memory story is even more pronounced.&lt;/p&gt;
&lt;h3 id="f9b6"&gt;Pattern 3: Latency-sensitive systems where GC pauses are a product problem&lt;/h3&gt;
&lt;p id="2738"&gt;Some systems can’t tolerate unpredictability at the tail.&lt;/p&gt;
&lt;p id="fe68"&gt;Financial systems where a p99 spike causes a missed execution window. Voice and video processing where a pause means a glitch the user hears. Real-time analytics dashboards where a stall breaks the illusion of live data.&lt;/p&gt;
&lt;p id="61a0"&gt;In these cases Go’s GC even with tuning introduces a variance floor that you can’t engineer away. You can shrink it. You can schedule around it. You can’t eliminate it.&lt;/p&gt;
&lt;p id="3e89"&gt;Rust eliminates it.&lt;/p&gt;
&lt;p id="ead1"&gt;No runtime. No collector. The memory behavior of a Rust service under load is the same as the memory behavior of a Rust service at rest deterministic, predictable, boring in exactly the way your SLA needs it to be.&lt;/p&gt;
&lt;p id="6ad1"&gt;This isn’t about raw speed. It’s about the shape of the latency distribution. Go might average faster on a given workload and still lose this comparison because the tail is worse.&lt;/p&gt;
&lt;p id="afbe"&gt;&lt;strong&gt;The common thread across all three patterns is the same:&lt;/strong&gt;&lt;/p&gt;
&lt;p id="6bc2"&gt;You don’t choose Rust instead of Go. You choose Rust for the specific part of the system where Go’s tradeoffs stop working in your favor. Everything else stays exactly as it was.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="bfa9"&gt;Build with Go. Optimize with Rust. Ship both.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="69f7"&gt;The wall: when Go stops being enough&lt;/h2&gt;
&lt;p id="67f5"&gt;Every backend system hits a wall.&lt;/p&gt;
&lt;p id="8308"&gt;At first, everything is fine.&lt;/p&gt;
&lt;p id="927d"&gt;You spin up services, deploy to ECS, wire up queues, add a scheduler, scale horizontally things just work. APIs respond fast enough. The team ships features. The infra bill is acceptable.&lt;/p&gt;
&lt;p id="3d30"&gt;&lt;strong&gt;Then growth happens.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="1a17"&gt;Traffic increases&lt;/li&gt;

&lt;li id="3034"&gt;Latency spikes in weird places&lt;/li&gt;

&lt;li id="92a0"&gt;Costs start climbing faster than usage&lt;/li&gt;

&lt;li id="0bb0"&gt;Debugging gets harder&lt;/li&gt;

&lt;li id="3dd1"&gt;Small inefficiencies compound into real problems&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="6330"&gt;And suddenly the question changes.&lt;/p&gt;
&lt;p id="4dfa"&gt;&lt;strong&gt;It’s no longer:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="ca8f"&gt;&lt;em&gt;“How fast can we build this?”&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="1a19"&gt;&lt;strong&gt;It becomes:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="4cc3"&gt;&lt;em&gt;“How do we keep this system predictable, efficient, and scalable without slowing the team down?”&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="0f8d"&gt;That’s where the Go vs Rust decision actually starts to matter. Not at the beginning. Right when you hit that wall.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="436" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A840%2F1%2A9h-HKQk7LWmitkUr4JK5LA.jpeg"&gt;&lt;h3 id="708a"&gt;How to know if you’ve actually hit it&lt;/h3&gt;
&lt;p id="8540"&gt;Before you start a Rust migration conversation, run this check first.&lt;/p&gt;
&lt;p id="504a"&gt;Most systems that feel slow aren’t CPU-bound. They’re waiting on databases, on network calls, on downstream services that are slower than they should be. Rewriting a Go service in Rust doesn’t fix a Postgres query that’s missing an index. It doesn’t fix an N+1 problem in your ORM. It doesn’t fix a Lambda that’s cold-starting because you gave it 128MB of memory and called it done.&lt;/p&gt;
&lt;p id="87d8"&gt;Profile before you decide.&lt;/p&gt;
&lt;p id="f528"&gt;If your bottleneck is I/O and it usually is Go is not your problem. Fix the query. Add the cache. Right-size the instance. Go home.&lt;/p&gt;
&lt;p id="a216"&gt;If your bottleneck is genuinely CPU sustained high utilization, processing-bound work, memory pressure that doesn’t resolve with horizontal scalingthen you have a real signal. That’s the wall. That’s where Rust earns the conversation.&lt;/p&gt;
&lt;h3 id="9d19"&gt;The team cost is real too&lt;/h3&gt;
&lt;p id="cddd"&gt;Here’s the part that gets skipped in every benchmark post.&lt;/p&gt;
&lt;p id="ddfe"&gt;&lt;strong&gt;Introducing Rust into a Go shop isn’t free. You’re adding a second language to your codebase, which means:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;

&lt;li id="4923"&gt;Two build pipelines to maintain&lt;/li&gt;

&lt;li id="d8c8"&gt;Two sets of idioms for new engineers to learn&lt;/li&gt;

&lt;li id="5e9e"&gt;Code review that requires Rust-literate reviewers&lt;/li&gt;

&lt;li id="4ad5"&gt;A slower ramp for anyone joining the team fresh&lt;/li&gt;

&lt;/ul&gt;
&lt;p id="7b07"&gt;None of that is disqualifying. All of it is real. The question is whether the performance gain in the specific component you’re optimizing is worth the ongoing operational tax across the whole team.&lt;/p&gt;
&lt;p id="a8c2"&gt;&lt;strong&gt;For most teams, the answer is:&lt;/strong&gt; yes, but only for a small slice of the system.&lt;/p&gt;
&lt;h3 id="5c94"&gt;The quick reference&lt;/h3&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="391" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A840%2F1%2A8HFH8J1PhAK6VOjTXAtTGw.png"&gt;&lt;h3 id="7a9a"&gt;&lt;strong&gt;The actual decision&lt;/strong&gt;&lt;/h3&gt;
&lt;p id="135f"&gt;Use Go to build systems.&lt;/p&gt;
&lt;p id="6633"&gt;Use Rust to optimize systems.&lt;/p&gt;
&lt;p id="9cd3"&gt;Start in Go. Ship in Go. Run in Go. When a specific component starts costing you more than it should in latency, in money, in stability isolate it. Profile it. And if the data points at a genuine CPU or memory problem that Go can’t resolve without throwing more hardware at it, that’s your Rust entry point.&lt;/p&gt;
&lt;p id="7771"&gt;Don’t migrate the whole system. Rewrite the one service that earned it.&lt;/p&gt;
&lt;p id="f70c"&gt;Then go back to shipping in Go.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="945d"&gt;Conclusion: Go builds companies, Rust survives them&lt;/h2&gt;
&lt;p id="d118"&gt;&lt;strong&gt;Here’s the take nobody wants to say out loud:&lt;br&gt;&lt;/strong&gt;Most teams that argue about Go vs Rust don’t have a language problem. They have a prioritization problem. The system isn’t slow because of the runtime. It’s slow because of the decisions made three sprints ago that nobody had time to revisit.&lt;/p&gt;
&lt;p id="e2c4"&gt;Language choice is downstream of system design. Always.&lt;/p&gt;
&lt;p id="f28a"&gt;Go became the default backend language of the cloud-native era not because it’s the fastest or the most elegant or the most technically impressive thing you can put on a resume. It became the default because it consistently produces systems that teams can build quickly, reason about clearly, and operate without a dedicated platform engineering team just to keep the lights on.&lt;/p&gt;
&lt;p id="db8d"&gt;That’s genuinely hard to beat.&lt;/p&gt;
&lt;p id="b17d"&gt;Rust earns its place in the stack the same way any good tool earns its place by solving a specific problem better than everything else available. Not because it’s newer. Not because the crab mascot is charming. Because when you need deterministic memory behavior, zero GC overhead, and the kind of compile-time guarantees that let you sleep through the night without a PagerDuty notification, nothing else in the backend ecosystem comes close.&lt;/p&gt;
&lt;p id="6b3d"&gt;The developers who will win in the next few years aren’t the ones who picked a side in this debate. They’re the ones who got comfortable moving between both who can ship a Go service on a Tuesday and drop into a Rust codebase on a Thursday without losing a step.&lt;/p&gt;
&lt;p id="8be8"&gt;Polyglot isn’t a buzzword anymore. It’s a survival skill.&lt;/p&gt;
&lt;p id="0f20"&gt;The question was never Go &lt;em&gt;or&lt;/em&gt; Rust. It was always Go &lt;em&gt;and&lt;/em&gt; Rust, applied with enough discipline to know which one the problem actually needs.&lt;/p&gt;
&lt;p id="a549"&gt;Pick the tool that fits the job. Ship the thing. Move on.&lt;/p&gt;
&lt;p id="70a8"&gt;And if someone on your team is still writing LinkedIn posts about which language is objectively better in 2026 send them this article and go touch grass.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="7f27"&gt;&lt;strong&gt;What do you think? Is your team running both in production, or did you go all-in on one? Drop your take in the comments I read every one.&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="0699"&gt;Helpful resources&lt;/h2&gt;
&lt;ul&gt;

&lt;li id="2c44"&gt;

&lt;a href="https://go.dev/tour/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;A Tour of Go&lt;/strong&gt;&lt;/a&gt; best starting point if you’re new to Go&lt;/li&gt;

&lt;li id="c85e"&gt;

&lt;a href="https://doc.rust-lang.org/book/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;The Rust Book&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;the official, actually-readable Rust guide&lt;/li&gt;

&lt;li id="da22"&gt;

&lt;a href="https://tokio.rs/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Tokio async runtime&lt;/strong&gt;&lt;/a&gt; where most production Rust backend work lives&lt;/li&gt;

&lt;li id="251f"&gt;

&lt;a href="https://github.com/tokio-rs/axum" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Axum web framework&lt;/strong&gt;&lt;/a&gt; ergonomic HTTP for Rust services&lt;/li&gt;

&lt;li id="6fcd"&gt;

&lt;a href="https://github.com/aws/aws-sdk-go-v2" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;AWS SDK for Go v2&lt;/strong&gt;&lt;/a&gt; the one you actually want to use&lt;/li&gt;

&lt;li id="e321"&gt;

&lt;a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;The Benchmarks Game&lt;/strong&gt;&lt;/a&gt; real language performance comparisons, not blog post numbers&lt;/li&gt;

&lt;li id="4e18"&gt;

&lt;a href="https://crates.io/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Crates.io&lt;/strong&gt;&lt;/a&gt; Rust package registry, wa more mature than it used to be&lt;/li&gt;

&lt;li id="d573"&gt;

&lt;a href="https://github.com/gin-gonic/gin" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Gin HTTP framework&lt;/strong&gt;&lt;/a&gt; Go’s most popular web framework&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>go</category>
      <category>rust</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Junior devs catch exceptions. Senior devs prevent them. Here’s the difference.</title>
      <dc:creator>&lt;devtips/&gt;</dc:creator>
      <pubDate>Wed, 13 May 2026 03:09:14 +0000</pubDate>
      <link>https://dev.to/dev_tips/junior-devs-catch-exceptions-senior-devs-prevent-them-heres-the-difference-241g</link>
      <guid>https://dev.to/dev_tips/junior-devs-catch-exceptions-senior-devs-prevent-them-heres-the-difference-241g</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="a7a2"&gt;&lt;strong&gt;Four patterns that replace most of the try-catch in your codebase and the mental model that makes the right choice obvious.&lt;/strong&gt;&lt;/h2&gt;
&lt;span&gt;&lt;/span&gt;&lt;p id="f648"&gt;Every developer has written this code. You’re building something, you get a compiler warning, or maybe a runtime blow-up in testing, and the fastest fix feels obvious: wrap it. Slap a try-catch around it, log the message, return null, move on. Done. Safe.&lt;/p&gt;
&lt;p id="b4d0"&gt;Except it isn’t safe. You’ve just installed a smoke detector that beeps once and then goes quiet forever.&lt;/p&gt;
&lt;p id="df5d"&gt;I’ve been inside codebases where try-catch was the default answer to every uncomfortable question the compiler asked. Controllers wrapped in it. Services wrapped in it. Utility methods wrapped in it. The team described it as “defensive programming.” What it actually was: a distributed system of silence. Errors happened. Nobody knew. The first sign of a problem was usually a customer email.&lt;/p&gt;
&lt;p id="e21b"&gt;There’s a reason tutorials teach try-catch first. It &lt;em&gt;works&lt;/em&gt;, in the narrowest possible sense. The app doesn’t crash. The stack trace disappears. The demo runs clean. What the tutorial doesn’t show you is the 3am incident six months later when a payment silently failed for two hundred users and your logs say &lt;code&gt;"Something went wrong: null"&lt;/code&gt;.&lt;/p&gt;
&lt;p id="3ccc"&gt;Senior devs don’t write more try-catch than juniors. They write significantly less. Not because they’re reckless, but because they’ve learned to ask a different question before reaching for it: &lt;em&gt;can I stop this from going wrong in the first place?&lt;/em&gt;&lt;/p&gt;
&lt;p id="5df8"&gt;This article is about the four patterns that replace most of the try-catch you’re writing. Each one handles a real category of failure invalid input, unclear errors, duplicated handling logic, and expected outcomes that aren’t actually exceptional. Together they form a mental model that changes how you read code, not just how you write it.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="5f72"&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; try-catch is a reactive tool. Most of the time, the right move is to prevent, name, centralize, or model the failure not catch it in silence.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="e3ef"&gt;&lt;strong&gt;Why junior devs default to try-catch&lt;/strong&gt;&lt;/h2&gt;
&lt;p id="3216"&gt;Here’s the thing: defaulting to try-catch isn’t stupidity. It’s pattern recognition from incomplete data.&lt;/p&gt;
&lt;p id="b77e"&gt;You learn Java, Python, or whatever backend language the bootcamp or degree threw at you. The first exception handling example looks like this: something might fail, so you wrap it, you catch it, you print the error, you move on. The tutorial works. You internalize the shape of the solution. From that point forward, any time the compiler complains or the runtime blows up, your brain pattern-matches straight to the familiar shape.&lt;/p&gt;
&lt;p id="e06b"&gt;Stack Overflow reinforces it. You search for your error, the accepted answer has a try-catch, it has 847 upvotes, someone accepted it in 2014, and it fixes your immediate problem. You copy it. You ship it. Repeat a few hundred times across a year of coding and you’ve built a reflex.&lt;/p&gt;
&lt;p id="f4ba"&gt;The problem isn’t the reflex. The problem is what it actually does to your codebase.&lt;/p&gt;
&lt;p id="3843"&gt;Every time you wrap code in try-catch, you’re making an implicit declaration: &lt;em&gt;I don’t know what’s going to go wrong here, so I’ll just intercept whatever happens and deal with it.&lt;/em&gt; That sounds humble. It isn’t. It’s actually abdication. You’ve handed the decision to runtime and told it to figure things out while you look away.&lt;/p&gt;
&lt;p id="100c"&gt;Think of it this way: try-catch is a bucket under a leaky pipe. It catches the drip. It keeps the floor dry for now. But the pipe is still leaking, the water is still going somewhere it shouldn’t, and the leak is getting slightly worse every week. The senior move is to call a plumber. The junior move is to empty the bucket on a cron job and call it fixed.&lt;/p&gt;
&lt;p id="84ed"&gt;&lt;strong&gt;Senior developers ask a different question before they reach for try-catch:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;

&lt;p id="1782"&gt;should this be able to fail at all?&lt;/p&gt;

&lt;p id="ce96"&gt;If invalid input causes the failure is the input even allowed in?&lt;/p&gt;

&lt;p id="cefb"&gt;If a null reference causes the crash why is null a valid state here?&lt;/p&gt;


&lt;/blockquote&gt;
&lt;p id="682a"&gt;The goal isn’t to catch the explosion. It’s to make the explosion impossible, or at least to make it impossible to ignore.&lt;/p&gt;
&lt;p id="eea5"&gt;That reframe is where the four patterns come from. Each one answers a version of that question for a different category of failure.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="7d36"&gt;&lt;strong&gt;Pattern 1: validate first, catch never&lt;/strong&gt;&lt;/h2&gt;
&lt;p id="e2a3"&gt;The single most common reason junior devs reach for try-catch is bad input. A null slips through. A string arrives where a number was expected. An email field is missing. Something downstream blows up, the catch block fires, and the problem disappears into a log message nobody reads.&lt;/p&gt;
&lt;p id="b8a4"&gt;Here’s the thing though: none of that is an exception handling problem. It’s a validation problem. The failure didn’t happen because the system encountered something genuinely unexpected it happened because you let garbage through the front door and were surprised when it broke something in the kitchen.&lt;/p&gt;
&lt;p id="bb60"&gt;I spent three hours debugging a production issue once that turned out to be a null email field on a user registration request. Three hours. The stack trace was unhelpful, the catch block had swallowed the context, and the only log entry was something like &lt;code&gt;"User creation failed"&lt;/code&gt;. The fix itself took forty seconds once I found it. The try-catch didn't protect anything it just made the debugging slower and more painful.&lt;/p&gt;
&lt;p id="7499"&gt;&lt;strong&gt;The senior pattern here is straightforward:&lt;/strong&gt; validate at the boundary. Nothing invalid gets past the entry point, so nothing downstream needs to defend against it.&lt;/p&gt;
&lt;p id="017f"&gt;In a Spring application this looks like replacing a defensive catch block with a &lt;code&gt;@Valid&lt;/code&gt; annotation and letting the framework enforce the contract:&lt;/p&gt;
&lt;pre&gt;&lt;span id="4f4a"&gt;&lt;span&gt;// what fear-based programming looks like&lt;/span&gt;&lt;br&gt;&lt;span&gt;@PostMapping("/users")&lt;/span&gt;&lt;br&gt;&lt;span&gt;public&lt;/span&gt; ResponseEntity&amp;lt;User&amp;gt; &lt;span&gt;createUser&lt;/span&gt;&lt;span&gt;(&lt;span&gt;@RequestBody&lt;/span&gt; UserRequest request)&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;try&lt;/span&gt; {&lt;br&gt;        &lt;span&gt;User&lt;/span&gt; &lt;span&gt;user&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; userService.create(request);&lt;br&gt;        &lt;span&gt;return&lt;/span&gt; ResponseEntity.ok(user);&lt;br&gt;    } &lt;span&gt;catch&lt;/span&gt; (NullPointerException e) {&lt;br&gt;        &lt;span&gt;return&lt;/span&gt; ResponseEntity.badRequest().body(&lt;span&gt;null&lt;/span&gt;);&lt;br&gt;    } &lt;span&gt;catch&lt;/span&gt; (IllegalArgumentException e) {&lt;br&gt;        &lt;span&gt;return&lt;/span&gt; ResponseEntity.badRequest().body(&lt;span&gt;null&lt;/span&gt;);&lt;br&gt;    }&lt;br&gt;}&lt;/span&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;span id="4d4a"&gt;&lt;span&gt;// what confidence looks like&lt;/span&gt;&lt;br&gt;&lt;span&gt;@PostMapping("/users")&lt;/span&gt;&lt;br&gt;&lt;span&gt;public&lt;/span&gt; ResponseEntity&amp;lt;User&amp;gt; &lt;span&gt;createUser&lt;/span&gt;&lt;span&gt;(&lt;span&gt;&lt;a class="mentioned-user" href="https://dev.to/valid"&gt;@valid&lt;/a&gt;&lt;/span&gt; &lt;span&gt;@RequestBody&lt;/span&gt; UserRequest request)&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;User&lt;/span&gt; &lt;span&gt;user&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; userService.create(request);&lt;br&gt;    &lt;span&gt;return&lt;/span&gt; ResponseEntity.status(HttpStatus.CREATED).body(user);&lt;br&gt;}&lt;br&gt;&lt;br&gt;&lt;span&gt;public&lt;/span&gt; &lt;span&gt;class&lt;/span&gt; &lt;span&gt;UserRequest&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;&lt;a class="mentioned-user" href="https://dev.to/notblank"&gt;@notblank&lt;/a&gt;(message = "Name is required")&lt;/span&gt;&lt;br&gt;    &lt;span&gt;private&lt;/span&gt; String name;&lt;br&gt;    &lt;span&gt;@Email(message = "Must be a valid email")&lt;/span&gt;&lt;br&gt;    &lt;span&gt;@NotNull(message = "Email is required")&lt;/span&gt;&lt;br&gt;    &lt;span&gt;private&lt;/span&gt; String email;&lt;br&gt;    &lt;span&gt;&lt;a class="mentioned-user" href="https://dev.to/min"&gt;@min&lt;/a&gt;(value = 18, message = "Must be at least 18")&lt;/span&gt;&lt;br&gt;    &lt;span&gt;private&lt;/span&gt; &lt;span&gt;int&lt;/span&gt; age;&lt;br&gt;}&lt;/span&gt;&lt;/pre&gt;
&lt;p id="673a"&gt;The second version is shorter, cleaner, and more honest. The constraints live on the data object itself, which means any developer who opens &lt;code&gt;UserRequest&lt;/code&gt; immediately knows the rules. There's no hidden catch block somewhere else in the call stack silently absorbing violations. If the input is bad, the framework rejects it immediately with a clear message before it touches a single line of your business logic.&lt;/p&gt;
&lt;p id="db71"&gt;This pattern isn’t Spring-specific either. Django has form and serializer validation. Express has middleware validators. Laravel has request classes. Every mature backend framework has a first-class answer to this problem, and that answer is never “catch the NullPointerException deeper in the stack.”&lt;/p&gt;
&lt;p id="60cd"&gt;The rule worth internalizing: if you’re catching an exception caused by bad input, you don’t have an exception handling problem. You have a validation problem. Fix it upstream, not downstream.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="436" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A840%2F1%2AcjAhmPmGcQzeu-iIlMZa3g.jpeg"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="594e"&gt;&lt;strong&gt;Pattern 2: build a custom exception hierarchy&lt;/strong&gt;&lt;/h2&gt;
&lt;p id="cc3e"&gt;So you’ve validated your input. Clean data is flowing through the system. But things still go wrong because they always do. A record doesn’t exist. A business rule gets violated. An order is already processed. These are real failures, and they need real handling.&lt;/p&gt;
&lt;p id="29e6"&gt;The junior response to this is &lt;code&gt;catch (Exception e)&lt;/code&gt;. Log the message. Return a 500. Hope the message is descriptive enough to debug later. It never is.&lt;/p&gt;
&lt;p id="72df"&gt;I’ve been in that 3am incident call. The alert fires, you pull up the logs, and staring back at you is: &lt;code&gt;"Something failed: null"&lt;/code&gt;. That's it. No context. No indication of which service, which record, which rule. Just a generic catch block somewhere in the stack that swallowed everything meaningful and handed you nothing. You start adding log statements, redeploying, waiting for it to happen again. It is not a fun way to spend a night.&lt;/p&gt;
&lt;p id="1350"&gt;The problem is that &lt;code&gt;Exception&lt;/code&gt; is the nuclear option. It catches everything the failure you expected, the one you didn't, the one that's actually a bug in a completely different part of the system. When you catch everything, you lose the ability to distinguish between any of it.&lt;/p&gt;
&lt;p id="5cbe"&gt;Senior devs build a hierarchy instead. You start with a base application exception, then extend it into specific types that are self-describing:&lt;/p&gt;
&lt;pre&gt;&lt;span id="aaed"&gt;&lt;span&gt;// base — everything app-specific extends this&lt;/span&gt;&lt;br&gt;&lt;span&gt;public&lt;/span&gt; &lt;span&gt;class&lt;/span&gt; &lt;span&gt;AppException&lt;/span&gt; &lt;span&gt;extends&lt;/span&gt; &lt;span&gt;RuntimeException&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;private&lt;/span&gt; &lt;span&gt;final&lt;/span&gt; HttpStatus status;&lt;br&gt;    &lt;span&gt;private&lt;/span&gt; &lt;span&gt;final&lt;/span&gt; String errorCode;&lt;br&gt;&lt;br&gt;&lt;span&gt;public&lt;/span&gt; &lt;span&gt;AppException&lt;/span&gt;&lt;span&gt;(String message, HttpStatus status, String errorCode)&lt;/span&gt; {&lt;br&gt;        &lt;span&gt;super&lt;/span&gt;(message);&lt;br&gt;        &lt;span&gt;this&lt;/span&gt;.status = status;&lt;br&gt;        &lt;span&gt;this&lt;/span&gt;.errorCode = errorCode;&lt;br&gt;    }&lt;br&gt;}&lt;br&gt;&lt;span&gt;// specific types that speak for themselves&lt;/span&gt;&lt;br&gt;&lt;span&gt;public&lt;/span&gt; &lt;span&gt;class&lt;/span&gt; &lt;span&gt;NotFoundException&lt;/span&gt; &lt;span&gt;extends&lt;/span&gt; &lt;span&gt;AppException&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;public&lt;/span&gt; &lt;span&gt;NotFoundException&lt;/span&gt;&lt;span&gt;(String resource, Long id)&lt;/span&gt; {&lt;br&gt;        &lt;span&gt;super&lt;/span&gt;(resource + &lt;span&gt;" not found with id: "&lt;/span&gt; + id,&lt;br&gt;              HttpStatus.NOT_FOUND, &lt;span&gt;"NOT_FOUND"&lt;/span&gt;);&lt;br&gt;    }&lt;br&gt;}&lt;br&gt;&lt;span&gt;public&lt;/span&gt; &lt;span&gt;class&lt;/span&gt; &lt;span&gt;BusinessRuleViolatedException&lt;/span&gt; &lt;span&gt;extends&lt;/span&gt; &lt;span&gt;AppException&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;public&lt;/span&gt; &lt;span&gt;BusinessRuleViolatedException&lt;/span&gt;&lt;span&gt;(String rule)&lt;/span&gt; {&lt;br&gt;        &lt;span&gt;super&lt;/span&gt;(&lt;span&gt;"Business rule violated: "&lt;/span&gt; + rule,&lt;br&gt;              HttpStatus.CONFLICT, &lt;span&gt;"BUSINESS_RULE_VIOLATED"&lt;/span&gt;);&lt;br&gt;    }&lt;br&gt;}&lt;/span&gt;&lt;/pre&gt;
&lt;p id="4cbb"&gt;&lt;strong&gt;Now your service code becomes readable without a single try-catch in sight:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span id="12b8"&gt;&lt;span&gt;public&lt;/span&gt; Order &lt;span&gt;processOrder&lt;/span&gt;&lt;span&gt;(OrderRequest request)&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;Order&lt;/span&gt; &lt;span&gt;order&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; orderRepository.findById(request.getOrderId())&lt;br&gt;        .orElseThrow(() -&amp;gt; &lt;span&gt;new&lt;/span&gt; &lt;span&gt;NotFoundException&lt;/span&gt;(&lt;span&gt;"Order"&lt;/span&gt;, request.getOrderId()));&lt;br&gt;&lt;br&gt;&lt;span&gt;if&lt;/span&gt; (order.isAlreadyProcessed()) {&lt;br&gt;        &lt;span&gt;throw&lt;/span&gt; &lt;span&gt;new&lt;/span&gt; &lt;span&gt;BusinessRuleViolatedException&lt;/span&gt;(&lt;span&gt;"Order already processed"&lt;/span&gt;);&lt;br&gt;    }&lt;br&gt;    &lt;span&gt;return&lt;/span&gt; orderRepository.save(order);&lt;br&gt;}&lt;br&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p id="60de"&gt;When &lt;code&gt;NotFoundException&lt;/code&gt; gets thrown anywhere in the system, you already know what failed, why it failed, and what HTTP status to return. You don't need to grep through logs trying to reconstruct context. The exception &lt;em&gt;is&lt;/em&gt; the context.&lt;/p&gt;
&lt;p id="19c8"&gt;Think of it like hospital triage codes versus a nurse shouting “something’s wrong with someone.” Triage codes exist because specificity saves time when time costs lives. Your exception hierarchy exists for the same reason specificity saves debugging time when production is on fire.&lt;/p&gt;
&lt;p id="550d"&gt;There’s also a less obvious benefit here: a good exception hierarchy forces you to think about your failure modes upfront. When you’re defining &lt;code&gt;BusinessRuleViolatedException&lt;/code&gt;, you're implicitly cataloguing what your business rules actually are. That clarity leaks into your design in good ways.&lt;/p&gt;
&lt;p id="5c99"&gt;The rule: if you’re catching a generic exception and then trying to figure out what actually went wrong from the message string, your exceptions aren’t specific enough. Name the failure. Make it impossible to confuse with anything else.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="436" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A840%2F1%2AKgUjxEIJJrBzOoD9aWrz3Q.jpeg"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="4244"&gt;&lt;strong&gt;Pattern 3: handle everything in one place&lt;/strong&gt;&lt;/h2&gt;
&lt;p id="b92e"&gt;You’ve got clean input coming in. You’ve got specific, named exceptions being thrown. Now the question is: where do you actually handle them?&lt;/p&gt;
&lt;p id="c48c"&gt;The junior answer is in every controller. You write a try-catch in the first endpoint, it works, you copy the pattern to the second endpoint, and the third, and the fifteenth. Six months later you have a codebase where every controller method is forty lines long and thirty of those lines are catch blocks doing nearly identical things. Changing the error response format means touching fifteen files. Adding a new exception type means hunting through every controller to add another catch branch. It compounds fast.&lt;/p&gt;
&lt;p id="c489"&gt;This is the copy-paste catch block epidemic, and almost every team above a certain age has lived through it.&lt;/p&gt;
&lt;p id="d0b6"&gt;The senior pattern collapses all of that into one place. In Spring, that’s &lt;code&gt;@RestControllerAdvice&lt;/code&gt; a single global handler that intercepts every unhandled exception from every controller in the application:&lt;/p&gt;
&lt;pre&gt;&lt;span id="e329"&gt;&lt;span&gt;@RestControllerAdvice&lt;/span&gt;&lt;br&gt;&lt;span&gt;public&lt;/span&gt; &lt;span&gt;class&lt;/span&gt; &lt;span&gt;GlobalExceptionHandler&lt;/span&gt; {&lt;br&gt;&lt;span&gt;@ExceptionHandler(NotFoundException.class)&lt;/span&gt;&lt;br&gt;    &lt;span&gt;public&lt;/span&gt; ResponseEntity&amp;lt;ErrorResponse&amp;gt; &lt;span&gt;handleNotFound&lt;/span&gt;&lt;span&gt;(NotFoundException e)&lt;/span&gt; {&lt;br&gt;        &lt;span&gt;return&lt;/span&gt; ResponseEntity.status(HttpStatus.NOT_FOUND)&lt;br&gt;            .body(&lt;span&gt;new&lt;/span&gt; &lt;span&gt;ErrorResponse&lt;/span&gt;(e.getErrorCode(), e.getMessage()));&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    &lt;span&gt;@ExceptionHandler(BusinessRuleViolatedException.class)&lt;/span&gt;&lt;br&gt;    &lt;span&gt;public&lt;/span&gt; ResponseEntity&amp;lt;ErrorResponse&amp;gt; &lt;span&gt;handleBusinessRule&lt;/span&gt;&lt;span&gt;(&lt;br&gt;            BusinessRuleViolatedException e)&lt;/span&gt; {&lt;br&gt;        &lt;span&gt;return&lt;/span&gt; ResponseEntity.status(HttpStatus.CONFLICT)&lt;br&gt;            .body(&lt;span&gt;new&lt;/span&gt; &lt;span&gt;ErrorResponse&lt;/span&gt;(e.getErrorCode(), e.getMessage()));&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    &lt;span&gt;@ExceptionHandler(MethodArgumentNotValidException.class)&lt;/span&gt;&lt;br&gt;    &lt;span&gt;public&lt;/span&gt; ResponseEntity&amp;lt;ErrorResponse&amp;gt; &lt;span&gt;handleValidation&lt;/span&gt;&lt;span&gt;(&lt;br&gt;            MethodArgumentNotValidException e)&lt;/span&gt; {&lt;br&gt;        &lt;span&gt;String&lt;/span&gt; &lt;span&gt;details&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; e.getBindingResult().getFieldErrors().stream()&lt;br&gt;            .map(error -&amp;gt; error.getField() + &lt;span&gt;": "&lt;/span&gt; + error.getDefaultMessage())&lt;br&gt;            .collect(Collectors.joining(&lt;span&gt;", "&lt;/span&gt;));&lt;br&gt;        &lt;span&gt;return&lt;/span&gt; ResponseEntity.status(HttpStatus.BAD_REQUEST)&lt;br&gt;            .body(&lt;span&gt;new&lt;/span&gt; &lt;span&gt;ErrorResponse&lt;/span&gt;(&lt;span&gt;"VALIDATION_FAILED"&lt;/span&gt;, details));&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    &lt;span&gt;@ExceptionHandler(Exception.class)&lt;/span&gt;&lt;br&gt;    &lt;span&gt;public&lt;/span&gt; ResponseEntity&amp;lt;ErrorResponse&amp;gt; &lt;span&gt;handleUnexpected&lt;/span&gt;&lt;span&gt;(Exception e)&lt;/span&gt; {&lt;br&gt;        logger.error(&lt;span&gt;"Unexpected error"&lt;/span&gt;, e);&lt;br&gt;        &lt;span&gt;return&lt;/span&gt; ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)&lt;br&gt;            .body(&lt;span&gt;new&lt;/span&gt; &lt;span&gt;ErrorResponse&lt;/span&gt;(&lt;span&gt;"INTERNAL_ERROR"&lt;/span&gt;, &lt;span&gt;"Something went wrong"&lt;/span&gt;));&lt;br&gt;    }&lt;br&gt;}&lt;/span&gt;&lt;/pre&gt;
&lt;p id="fc74"&gt;&lt;strong&gt;And here’s what your controllers look like after:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span id="b792"&gt;&lt;span&gt;@GetMapping("/orders/{id}")&lt;/span&gt;&lt;br&gt;&lt;span&gt;public&lt;/span&gt; Order &lt;span&gt;getOrder&lt;/span&gt;&lt;span&gt;(&lt;span&gt;@PathVariable&lt;/span&gt; Long id)&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;return&lt;/span&gt; orderService.findById(id);&lt;br&gt;}&lt;/span&gt;&lt;/pre&gt;
&lt;p id="0eec"&gt;That’s it. Three lines. The controller does exactly one thing: call the service and return the result. If &lt;code&gt;NotFoundException&lt;/code&gt; gets thrown, the global handler catches it, formats it correctly, and returns the right HTTP status. The controller never needed to know about any of that.&lt;/p&gt;
&lt;p id="9512"&gt;This is the bouncer analogy applied to architecture. You don’t put a bouncer at every table in a restaurant. You put one at the door. One point of entry, one set of rules, consistently enforced. Your &lt;code&gt;GlobalExceptionHandler&lt;/code&gt; is the door. Every exception has to pass through it, which means every exception gets handled the same way, every time, from one file you can actually reason about.&lt;/p&gt;
&lt;p id="7483"&gt;The practical benefits stack up quickly. Want to change your error response format? One file. Adding a new custom exception type? One new method in the handler. Need to add request ID tracking to every error response for distributed tracing? One change, instantly applied everywhere. What used to be a fifteen-file refactor becomes a three-line addition.&lt;/p&gt;
&lt;p id="ee50"&gt;The framework doesn’t matter much here either the pattern exists everywhere. Django has exception middleware. Express has error-handling middleware with the four-argument signature. Laravel has the &lt;code&gt;Handler&lt;/code&gt; class in &lt;code&gt;app/Exceptions&lt;/code&gt;. The idea is universal: centralize your exception handling, stop duplicating it.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="47bf"&gt;&lt;strong&gt;The rule:&lt;/strong&gt; if you’re writing the same catch logic in more than one place, you need a centralized handler. Every duplicate catch block is future maintenance debt you’re taking out a loan on right now.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="5a90"&gt;&lt;strong&gt;Pattern 4: result objects for expected failures&lt;/strong&gt;&lt;/h2&gt;
&lt;p id="687e"&gt;This is the pattern that tends to produce the most argument in code reviews, which is usually a sign it’s worth understanding properly.&lt;/p&gt;
&lt;p id="5263"&gt;The premise is simple: not every failure is exceptional. “User not found” isn’t a system error it’s a Tuesday. “Payment declined” isn’t a catastrophe it’s an expected outcome of the payment flow. “Item out of stock” isn’t a bug it’s a valid state your business logic needs to handle gracefully.&lt;/p&gt;
&lt;p id="4251"&gt;When you model these as exceptions, you’re using the wrong tool. Exceptions exist for genuinely unexpected conditions things that shouldn’t happen under normal operation. Using them for routine business outcomes is like calling an ambulance because you got a parking ticket. Technically it gets the job done, but it’s expensive, it misleads everyone involved, and it trains the system to treat normal outcomes as emergencies.&lt;/p&gt;
&lt;p id="2db4"&gt;&lt;strong&gt;The result object pattern makes expected outcomes explicit in the return type instead:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span id="d0f7"&gt;&lt;span&gt;public&lt;/span&gt; &lt;span&gt;class&lt;/span&gt; &lt;span&gt;Result&lt;/span&gt;&amp;lt;T&amp;gt; {&lt;br&gt;    &lt;span&gt;private&lt;/span&gt; &lt;span&gt;final&lt;/span&gt; T value;&lt;br&gt;    &lt;span&gt;private&lt;/span&gt; &lt;span&gt;final&lt;/span&gt; String error;&lt;br&gt;    &lt;span&gt;private&lt;/span&gt; &lt;span&gt;final&lt;/span&gt; &lt;span&gt;boolean&lt;/span&gt; success;&lt;br&gt;&lt;br&gt;&lt;span&gt;private&lt;/span&gt; &lt;span&gt;Result&lt;/span&gt;&lt;span&gt;(T value, String error, &lt;span&gt;boolean&lt;/span&gt; success)&lt;/span&gt; {&lt;br&gt;        &lt;span&gt;this&lt;/span&gt;.value = value;&lt;br&gt;        &lt;span&gt;this&lt;/span&gt;.error = error;&lt;br&gt;        &lt;span&gt;this&lt;/span&gt;.success = success;&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    &lt;span&gt;public&lt;/span&gt; &lt;span&gt;static&lt;/span&gt; &amp;lt;T&amp;gt; Result&amp;lt;T&amp;gt; &lt;span&gt;ok&lt;/span&gt;&lt;span&gt;(T value)&lt;/span&gt; {&lt;br&gt;        &lt;span&gt;return&lt;/span&gt; &lt;span&gt;new&lt;/span&gt; &lt;span&gt;Result&lt;/span&gt;&amp;lt;&amp;gt;(value, &lt;span&gt;null&lt;/span&gt;, &lt;span&gt;true&lt;/span&gt;);&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    &lt;span&gt;public&lt;/span&gt; &lt;span&gt;static&lt;/span&gt; &amp;lt;T&amp;gt; Result&amp;lt;T&amp;gt; &lt;span&gt;failure&lt;/span&gt;&lt;span&gt;(String error)&lt;/span&gt; {&lt;br&gt;        &lt;span&gt;return&lt;/span&gt; &lt;span&gt;new&lt;/span&gt; &lt;span&gt;Result&lt;/span&gt;&amp;lt;&amp;gt;(&lt;span&gt;null&lt;/span&gt;, error, &lt;span&gt;false&lt;/span&gt;);&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    &lt;span&gt;public&lt;/span&gt; &lt;span&gt;boolean&lt;/span&gt; &lt;span&gt;isSuccess&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; { &lt;span&gt;return&lt;/span&gt; success; }&lt;br&gt;    &lt;span&gt;public&lt;/span&gt; T &lt;span&gt;getValue&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; { &lt;span&gt;return&lt;/span&gt; value; }&lt;br&gt;    &lt;span&gt;public&lt;/span&gt; String &lt;span&gt;getError&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; { &lt;span&gt;return&lt;/span&gt; error; }&lt;br&gt;}&lt;/span&gt;&lt;/pre&gt;
&lt;p id="4613"&gt;&lt;strong&gt;Now your service communicates both possible outcomes in its signature:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span id="d118"&gt;&lt;span&gt;// before — the caller has no idea this can fail&lt;/span&gt;&lt;br&gt;&lt;span&gt;// until the exception surprises them at runtime&lt;/span&gt;&lt;br&gt;&lt;span&gt;public&lt;/span&gt; User &lt;span&gt;findUser&lt;/span&gt;&lt;span&gt;(Long id)&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;return&lt;/span&gt; userRepository.findById(id)&lt;br&gt;        .orElseThrow(() -&amp;gt; &lt;span&gt;new&lt;/span&gt; &lt;span&gt;NotFoundException&lt;/span&gt;(&lt;span&gt;"User"&lt;/span&gt;, id));&lt;br&gt;}&lt;br&gt;&lt;br&gt;&lt;span&gt;// after - the return type tells the whole story upfront&lt;/span&gt;&lt;br&gt;&lt;span&gt;public&lt;/span&gt; Result&amp;lt;User&amp;gt; &lt;span&gt;findUser&lt;/span&gt;&lt;span&gt;(Long id)&lt;/span&gt; {&lt;br&gt;    &lt;span&gt;return&lt;/span&gt; userRepository.findById(id)&lt;br&gt;        .map(Result::ok)&lt;br&gt;        .orElse(Result.failure(&lt;span&gt;"User not found with id: "&lt;/span&gt; + id));&lt;br&gt;}&lt;/span&gt;&lt;/pre&gt;
&lt;p id="d244"&gt;&lt;strong&gt;And the caller handles both paths without needing a try-catch anywhere:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span id="3615"&gt;Result&amp;lt;User&amp;gt; result = userService.findUser(&lt;span&gt;123L&lt;/span&gt;);&lt;br&gt;&lt;span&gt;if&lt;/span&gt; (result.isSuccess()) {&lt;br&gt;    &lt;span&gt;return&lt;/span&gt; ResponseEntity.ok(result.getValue());&lt;br&gt;}&lt;br&gt;&lt;br&gt;&lt;span&gt;return&lt;/span&gt; ResponseEntity.notFound().build();&lt;/span&gt;&lt;/pre&gt;
&lt;p id="d7df"&gt;The difference in readability is significant. In the exception-throwing version, a developer reading the method signature sees &lt;code&gt;User findUser(Long id)&lt;/code&gt; and assumes it always returns a user. The failure mode is invisible until it bites them. In the result version, &lt;code&gt;Result&amp;lt;User&amp;gt; findUser(Long id)&lt;/code&gt; makes the contract obvious this operation produces either a user or an explanation of why it didn't. The caller is forced to acknowledge both possibilities.&lt;/p&gt;
&lt;p id="4ddc"&gt;This isn’t a new idea and it definitely isn’t Java-specific. Rust builds this directly into the language with &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt; if your function can fail, the type system requires you to say so and requires the caller to handle it. Go uses the idiomatic &lt;code&gt;(value, error)&lt;/code&gt; return pair for the same reason. Haskell has &lt;code&gt;Maybe&lt;/code&gt;. These languages treat explicit failure modeling as a first-class concern, not an afterthought.&lt;/p&gt;
&lt;p id="74c4"&gt;The Java ecosystem is slowly catching up. Libraries like &lt;a href="https://www.vavr.io/" rel="noopener ugc nofollow noreferrer"&gt;Vavr&lt;/a&gt; bring proper &lt;code&gt;Either&lt;/code&gt; and &lt;code&gt;Try&lt;/code&gt; types to the JVM. The direction the industry is moving is clear: make failure visible in the type signature, not hidden behind runtime surprises.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="6032"&gt;&lt;strong&gt;The rule:&lt;/strong&gt; if a scenario is expected not a bug, not a system fault, just a normal outcome your business logic needs to handle don’t throw an exception. Return a result. Make the failure a first-class citizen of your API, not a trap waiting for the next developer.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="436" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A840%2F1%2As-CChVWn-5gi5k4eAWmMDg.jpeg"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="6654"&gt;&lt;strong&gt;The mental model that ties it all together&lt;/strong&gt;&lt;/h2&gt;
&lt;p id="08a1"&gt;Four patterns is a lot to hold in your head at once. The good news is there’s a single question underneath all of them that makes the right choice obvious once you’ve internalized it:&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="23f5"&gt;&lt;em&gt;Is this failure exceptional, or is it expected?&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p id="0549"&gt;That’s it. Two buckets. Everything goes in one of them.&lt;/p&gt;
&lt;p id="adc9"&gt;&lt;strong&gt;Exceptional failures&lt;/strong&gt; are things that shouldn’t happen under normal operating conditions. The database connection drops mid-request. A third-party API times out after working fine for months. The server runs out of memory. A file that should always exist gets deleted by something outside your application. These are infrastructure-level surprises conditions your code didn’t cause and can’t reasonably prevent. They deserve try-catch, proper logging, alerting, and a human looking at them.&lt;/p&gt;
&lt;p id="b8fb"&gt;&lt;strong&gt;Expected failures&lt;/strong&gt; are outcomes that are entirely normal within your business domain. A user searches for a record that doesn’t exist. A payment gets declined because the card is expired. Someone tries to book a seat that just got taken. An order is submitted twice. These aren’t bugs. They’re valid states your system needs to navigate gracefully. They deserve validation, result objects, and clean control flow not try-catch, not stack traces, not PagerDuty alerts at midnight.&lt;/p&gt;
&lt;p id="5933"&gt;Once you start sorting failures into these two buckets automatically, the right pattern follows almost without thinking. Bad input coming in? That’s preventable validate it at the boundary. A business rule getting violated? Name it explicitly with a custom exception. Error handling logic spreading across controllers? Centralize it. A routine “not found” outcome? Model it as a result, not an emergency.&lt;/p&gt;
&lt;p id="23c1"&gt;The reason most codebases end up with try-catch everywhere is that nobody ever drew this line. Everything got treated as equally unpredictable, equally dangerous, equally worthy of the same blunt instrument. The bucket metaphor is simple, but the discipline of actually applying it consistently is what separates a codebase you can debug at 3am from one that makes you want to update your resume at 3am.&lt;/p&gt;
&lt;p id="50fb"&gt;Think of it like your notification system. A fire alarm and a calendar reminder are both alerts. But they belong to completely different categories of urgency, and you’d never design a system that treated them the same way. Your exception handling is your application’s notification system. Design it with the same intentionality.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="436" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A840%2F1%2AK957r6bZ_STvVH-goPwBdg.jpeg"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="fb85"&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h2&gt;
&lt;p id="4451"&gt;Here’s the uncomfortable truth the tutorials won’t tell you: most of the try-catch in production codebases today exists because the people who wrote it were taught a pattern without being taught the &lt;em&gt;reasoning&lt;/em&gt; behind it. Copy, paste, ship. It works until it doesn’t, and when it doesn’t, it fails in the worst possible way silently, slowly, invisibly.&lt;/p&gt;
&lt;p id="3442"&gt;The four patterns in this article aren’t advanced techniques. They’re not senior-level secrets. They’re just what happens when you start asking “why is this failing?” instead of “how do I make this stop crashing?” Validate the input. Name the failure. Centralize the handling. Model the expected outcomes. None of it is complicated. All of it compounds.&lt;/p&gt;
&lt;p id="94c9"&gt;The industry is already moving in this direction. Languages like Rust make explicit error modeling non-negotiable at the compiler level. TypeScript’s ecosystem is increasingly leaning on discriminated unions for the same purpose. The direction is clear: failures belong in the type signature, not hiding behind runtime surprises in catch blocks nobody reads.&lt;/p&gt;
&lt;h3 id="7935"&gt;&lt;strong&gt;Do this tomorrow one change at a time:&lt;/strong&gt;&lt;/h3&gt;
&lt;p id="b78d"&gt;Find one try-catch in your codebase that’s catching bad input. Replace it with proper validation a &lt;code&gt;@Valid&lt;/code&gt; annotation, a schema check, a guard clause. Just one. Find one &lt;code&gt;catch (Exception e)&lt;/code&gt; that's swallowing everything. Replace it with a specific custom exception that names the actual failure. If you have catch blocks duplicated across multiple controllers or handlers, build a centralized exception handler and delete the copies. Find one place where you're throwing an exception for an outcome that's entirely expected. Replace it with a result object.&lt;/p&gt;
&lt;p id="4386"&gt;Four changes. One codebase that’s measurably more honest about what it does and what can go wrong.&lt;/p&gt;
&lt;p id="e924"&gt;The worst try-catch I ever saw in production wrapped an entire service class constructor and returned null on failure. The service would silently initialize as null, work fine for a while, then NullPointerException somewhere completely unrelated, no trail, no context, nothing. Three days of debugging. The fix was six lines.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="cdc9"&gt;What’s yours? Drop the worst try-catch you’ve ever shipped in the comments. No judgment we’ve all got one.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="9373"&gt;&lt;strong&gt;Helpful resources&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;

&lt;li id="82a1"&gt;

&lt;a href="https://jakarta.ee/specifications/bean-validation/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Jakarta Bean Validation specification&lt;/strong&gt;&lt;/a&gt; the standard behind &lt;code&gt;@Valid&lt;/code&gt; and constraint annotations&lt;/li&gt;

&lt;li id="c36d"&gt;

&lt;a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Spring @ControllerAdvice documentation&lt;/strong&gt;&lt;/a&gt; official reference for global exception handling&lt;/li&gt;

&lt;li id="ba08"&gt;

&lt;a href="https://www.vavr.io/" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Vavr library&lt;/strong&gt;&lt;/a&gt; brings &lt;code&gt;Either&lt;/code&gt;, &lt;code&gt;Try&lt;/code&gt;, and &lt;code&gt;Option&lt;/code&gt; types to Java&lt;/li&gt;

&lt;li id="42e1"&gt;

&lt;a href="https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Rust Book: Recoverable errors with Result&lt;/strong&gt;&lt;/a&gt; the gold standard for how explicit error modeling should work&lt;/li&gt;

&lt;li id="5a8a"&gt;

&lt;a href="https://go.dev/blog/error-handling-and-go" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Error Handling in Go&lt;/strong&gt;&lt;/a&gt; Google’s official take on the (value, error) pattern&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>programming</category>
      <category>discuss</category>
    </item>
    <item>
      <title>10 SQL changes. One took 30 seconds. It cut query time by 85%.</title>
      <dc:creator>&lt;devtips/&gt;</dc:creator>
      <pubDate>Tue, 12 May 2026 06:38:34 +0000</pubDate>
      <link>https://dev.to/dev_tips/10-sql-changes-one-took-30-seconds-it-cut-query-time-by-85-4f1f</link>
      <guid>https://dev.to/dev_tips/10-sql-changes-one-took-30-seconds-it-cut-query-time-by-85-4f1f</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="55c3"&gt;&lt;em&gt;A 2015 research paper tested every tip against real data. Most developers have never seen it. The numbers are hard to ignore.&lt;/em&gt;&lt;/h2&gt;
&lt;span&gt;&lt;/span&gt;&lt;blockquote&gt;&lt;p id="cff5"&gt;You wrote a query. It returned the right data. You moved on.&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p id="2977"&gt;That’s the whole story for most developers. The query works, the feature ships, and nobody looks back. Until a senior engineer pulls up the execution plan in a prod review and asks why you’re doing a full table scan on 2 million rows to return twelve records.&lt;/p&gt;
&lt;p id="df59"&gt;That moment has happened to more engineers than will ever admit it publicly.&lt;/p&gt;
&lt;p id="c1a1"&gt;Here’s the thing: most SQL slowness isn’t mysterious. It’s not a missing index, a misconfigured database, or a vendor problem. It’s patterns habits that looked fine when the table had 500 rows and became quietly catastrophic when it hit 5 million.&lt;/p&gt;
&lt;p id="d348"&gt;In 2015, a researcher named Jean Habimana published a paper through IJSTR titled &lt;em&gt;“Query Optimization Techniques: Tips For Writing Efficient And Faster SQL Queries.”&lt;/em&gt; Five pages. Tested against Oracle’s sample Sales database. Every tip benchmarked with actual time reductions. It’s been sitting in academic obscurity ever since, which is a shame, because some of these changes take thirty seconds to make and show query time reductions above 80%.&lt;/p&gt;
&lt;p id="86c4"&gt;&lt;strong&gt;This article is that paper, translated into something you’ll actually read.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="b035"&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; 10 SQL habits ranked by impact. Time reductions range from 11% to 85%. Most fixes take under five minutes. A few will make you retroactively embarrassed about queries you shipped last year.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="df27"&gt;Why your query isn’t just “running”&lt;/h2&gt;
&lt;p id="9242"&gt;Most SQL tutorials skip this part entirely. They go straight to syntax, joins, and GROUP BY, and leave you with a model of SQL that looks roughly like: &lt;em&gt;you write it, the database runs it, data comes back.&lt;/em&gt; Clean. Simple. Wrong.&lt;/p&gt;
&lt;p id="e656"&gt;When you submit a query, it doesn’t execute directly. It goes through a query optimizer first a component that reads your SQL, estimates multiple ways to fetch the result, and picks the one it thinks is cheapest. Cheapest meaning least I/O, least memory, least CPU. The optimizer is making decisions you never see, and those decisions are heavily influenced by how you wrote the query.&lt;/p&gt;
&lt;p id="5e47"&gt;Think of it like a GPS. You give it a destination and it figures out the route. But if you give it bad inputs a vague address, a restricted road it doesn’t know about it picks a worse path. The database optimizer works the same way. Write the query clearly and it finds the fast route. Write it carelessly and it takes the scenic route through every row in your table.&lt;/p&gt;
&lt;p id="95cb"&gt;&lt;strong&gt;The execution path looks roughly like this:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span id="a225"&gt;Your &lt;span&gt;SQL&lt;/span&gt;&lt;br&gt;    ↓&lt;br&gt;Query Parser — checks syntax&lt;br&gt;    ↓&lt;br&gt;Query Optimizer — estimates cheapest execution path&lt;br&gt;    ↓&lt;br&gt;Execution Plan — the actual instruction &lt;span&gt;set&lt;/span&gt;&lt;br&gt;    ↓&lt;br&gt;Data retrieval → &lt;span&gt;Result&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p id="b4f8"&gt;Every tip in this article targets one of two things: either helping the optimizer make a better decision, or removing work it shouldn’t have to do in the first place. That’s the whole framework. Keep it in mind as you read the rest.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="427f"&gt;The lazy habits costing you the most&lt;/h2&gt;
&lt;p id="42c2"&gt;Let’s start with the number that stopped me mid-scroll when I first read this paper: &lt;strong&gt;85% query time reduction&lt;/strong&gt;. Not from adding an index. Not from upgrading hardware. From removing one word.&lt;/p&gt;
&lt;h3 id="5acf"&gt;Tip 3 first because 85% deserves the spotlight&lt;/h3&gt;
&lt;p id="455d"&gt;When you write &lt;code&gt;SELECT DISTINCT &lt;em&gt;&lt;/em&gt;&lt;/code&gt; on a join where the primary key is already in the result, duplicates are mathematically impossible. There are no duplicates to remove. But the database doesn't know that so it sorts the entire result set and checks anyway. You're paying full deduplication cost for zero benefit.&lt;/p&gt;
&lt;pre&gt;&lt;span id="e859"&gt;&lt;span&gt;-- Slow: DISTINCT is doing nothing here (primary key present)&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; &lt;span&gt;DISTINCT&lt;/span&gt; &lt;span&gt;&lt;/span&gt; &lt;span&gt;FROM&lt;/span&gt; sales s&lt;br&gt;&lt;span&gt;JOIN&lt;/span&gt; customers c &lt;span&gt;ON&lt;/span&gt; s.cust_id &lt;span&gt;=&lt;/span&gt; c.cust_id;&lt;br&gt;&lt;br&gt;&lt;span&gt;-- Fast: just don't&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; &lt;span&gt;&lt;em&gt;&lt;/em&gt;&lt;/span&gt; &lt;span&gt;FROM&lt;/span&gt; sales s&lt;br&gt;&lt;span&gt;JOIN&lt;/span&gt; customers c &lt;span&gt;ON&lt;/span&gt; s.cust_id &lt;span&gt;=&lt;/span&gt; c.cust_id;&lt;/span&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p id="4480"&gt;&lt;strong&gt;85% faster. One word removed.&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id="56b3"&gt;Tip 1: SELECT columns, not SELECT * 27% reduction&lt;/h3&gt;
&lt;p id="74d8"&gt;The most universal bad habit in SQL. When you write &lt;code&gt;SELECT *&lt;/code&gt;, the database fetches every column and ships it across the network. If you need two columns from a table with twenty, you're paying for eighteen you'll never use. If any of those columns are large types TEXT, BLOB, JSON you're paying a lot.&lt;/p&gt;
&lt;pre&gt;&lt;span id="182b"&gt;&lt;span&gt;-- Slow&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; &lt;span&gt;&lt;/span&gt; &lt;span&gt;FROM&lt;/span&gt; sales;&lt;br&gt;&lt;br&gt;&lt;span&gt;-- Fast&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; prod_id, cust_id &lt;span&gt;FROM&lt;/span&gt; sales;&lt;/span&gt;&lt;/pre&gt;
&lt;p id="1cc5"&gt;It feels pedantic until your table has 40 columns and half of them are storing documents.&lt;/p&gt;
&lt;h3 id="e9b7"&gt;Tip 2: WHERE before GROUP BY, not HAVING 31% reduction&lt;/h3&gt;
&lt;p id="5be2"&gt;&lt;code&gt;HAVING&lt;/code&gt; filters rows after grouping. &lt;code&gt;WHERE&lt;/code&gt; filters rows before grouping. If your condition doesn't involve an aggregate function, it has no business being in &lt;code&gt;HAVING&lt;/code&gt;. Putting it there means the database groups every row first, then throws away the ones you didn't want work it never needed to do.&lt;/p&gt;
&lt;pre&gt;&lt;span id="98f5"&gt;&lt;span&gt;-- Slow: groups everything, then filters&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; cust_id, &lt;span&gt;COUNT&lt;/span&gt;(cust_id)&lt;br&gt;&lt;span&gt;FROM&lt;/span&gt; sales&lt;br&gt;&lt;span&gt;GROUP&lt;/span&gt; &lt;span&gt;BY&lt;/span&gt; cust_id&lt;br&gt;&lt;span&gt;HAVING&lt;/span&gt; cust_id &lt;span&gt;!=&lt;/span&gt; &lt;span&gt;'1660'&lt;/span&gt;;&lt;br&gt;&lt;br&gt;&lt;span&gt;-- Fast: filters first, groups less&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; cust_id, &lt;span&gt;COUNT&lt;/span&gt;(cust_id)&lt;br&gt;&lt;span&gt;FROM&lt;/span&gt; sales&lt;br&gt;&lt;span&gt;WHERE&lt;/span&gt; cust_id &lt;span&gt;!=&lt;/span&gt; &lt;span&gt;'1660'&lt;/span&gt;&lt;br&gt;&lt;span&gt;GROUP&lt;/span&gt; &lt;span&gt;BY&lt;/span&gt; cust_id;&lt;/span&gt;&lt;/pre&gt;
&lt;h3 id="e500"&gt;Tip 3: Drop unnecessary DISTINCT 85% reduction&lt;/h3&gt;
&lt;p id="5477"&gt;Here it is. The biggest number in the paper.&lt;/p&gt;
&lt;p id="6b32"&gt;When you write &lt;code&gt;SELECT DISTINCT &lt;em&gt;&lt;/em&gt;&lt;/code&gt; on a join where the primary key is already in the result, duplicates are mathematically impossible. There are no duplicates to remove. But the database doesn't know that so it sorts the entire result set and checks anyway. You're paying full deduplication cost for zero benefit.&lt;/p&gt;
&lt;pre&gt;&lt;span id="367f"&gt;&lt;span&gt;-- Slow: DISTINCT is doing nothing here (primary key present)&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; &lt;span&gt;DISTINCT&lt;/span&gt; &lt;span&gt;&lt;/span&gt; &lt;span&gt;FROM&lt;/span&gt; sales s&lt;br&gt;&lt;span&gt;JOIN&lt;/span&gt; customers c &lt;span&gt;ON&lt;/span&gt; s.cust_id &lt;span&gt;=&lt;/span&gt; c.cust_id;&lt;br&gt;&lt;br&gt;&lt;span&gt;-- Fast: just don't&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; &lt;span&gt;&lt;em&gt;&lt;/em&gt;&lt;/span&gt; &lt;span&gt;FROM&lt;/span&gt; sales s&lt;br&gt;&lt;span&gt;JOIN&lt;/span&gt; customers c &lt;span&gt;ON&lt;/span&gt; s.cust_id &lt;span&gt;=&lt;/span&gt; c.cust_id;&lt;/span&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p id="7409"&gt;&lt;strong&gt;85% faster. One word removed.&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id="4c26"&gt;Tip 4: Un-nest your subqueries 61% reduction&lt;/h3&gt;
&lt;p id="e12f"&gt;Correlated subqueries are the silent performance killer most junior devs don’t recognize until it’s too late. A subquery inside a &lt;code&gt;WHERE&lt;/code&gt; clause that references the outer query runs once per row. Not once total once &lt;em&gt;per row&lt;/em&gt;. On a table with 100,000 rows, that's 100,000 executions of your inner query.&lt;/p&gt;
&lt;p id="ac21"&gt;&lt;strong&gt;A JOIN runs once.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span id="4fc4"&gt;&lt;span&gt;-- Slow: subquery executes for every row in products&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; &lt;span&gt;&lt;/span&gt; &lt;span&gt;FROM&lt;/span&gt; products p&lt;br&gt;&lt;span&gt;WHERE&lt;/span&gt; p.prod_id &lt;span&gt;=&lt;/span&gt; (&lt;br&gt;    &lt;span&gt;SELECT&lt;/span&gt; s.prod_id &lt;span&gt;FROM&lt;/span&gt; sales s&lt;br&gt;    &lt;span&gt;WHERE&lt;/span&gt; s.cust_id &lt;span&gt;=&lt;/span&gt; &lt;span&gt;100996&lt;/span&gt;&lt;br&gt;);&lt;br&gt;&lt;br&gt;&lt;span&gt;-- Fast: single join operation&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; p.&lt;span&gt;&lt;em&gt;&lt;/em&gt;&lt;/span&gt; &lt;span&gt;FROM&lt;/span&gt; products p&lt;br&gt;&lt;span&gt;JOIN&lt;/span&gt; sales s &lt;span&gt;ON&lt;/span&gt; p.prod_id &lt;span&gt;=&lt;/span&gt; s.prod_id&lt;br&gt;&lt;span&gt;WHERE&lt;/span&gt; s.cust_id &lt;span&gt;=&lt;/span&gt; &lt;span&gt;100996&lt;/span&gt;;&lt;/span&gt;&lt;/pre&gt;
&lt;p id="670e"&gt;If you’ve ever watched a query go from instant to “still running after 40 seconds” as the table grew, a correlated subquery somewhere is usually the culprit.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="f563"&gt;Four tips. Average time reduction across all four: &lt;strong&gt;51%.&lt;/strong&gt; And none of them required touching your schema, your indexes, or your infrastructure. Just the query.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="436" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A945%2F1%2A6KSFHB2E8tGb6MSE5REjLA.jpeg"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="6458"&gt;The optimizer killers&lt;/h2&gt;
&lt;p id="b62a"&gt;These four are sneakier. The previous section was mostly about obvious waste fetching columns you don’t need, grouping rows you’re about to throw away. This section is about patterns that look perfectly reasonable but quietly prevent the optimizer from using your indexes. That distinction matters a lot at scale.&lt;/p&gt;
&lt;h3 id="ed68"&gt;Tip 5: IN instead of multiple ORs 73% reduction&lt;/h3&gt;
&lt;p id="32d0"&gt;This one surprises people. Both &lt;code&gt;IN&lt;/code&gt; and &lt;code&gt;OR&lt;/code&gt; feel like they're doing the same thing, and syntactically they kind of are. The difference is what the optimizer can do with them.&lt;/p&gt;
&lt;p id="b0b8"&gt;With an &lt;code&gt;IN&lt;/code&gt; list, the optimizer can sort the values and match them against the index in order. With chained &lt;code&gt;OR&lt;/code&gt; conditions, it can't apply that optimization it evaluates each condition independently, often falling back to a full scan.&lt;/p&gt;
&lt;pre&gt;&lt;span id="890c"&gt;&lt;span&gt;-- Slow&lt;/span&gt;&lt;br&gt;&lt;span&gt;WHERE&lt;/span&gt; prod_id &lt;span&gt;=&lt;/span&gt; &lt;span&gt;14&lt;/span&gt; &lt;span&gt;OR&lt;/span&gt; prod_id &lt;span&gt;=&lt;/span&gt; &lt;span&gt;17&lt;/span&gt;;&lt;br&gt;&lt;br&gt;&lt;span&gt;-- Fast&lt;/span&gt;&lt;br&gt;&lt;span&gt;WHERE&lt;/span&gt; prod_id &lt;span&gt;IN&lt;/span&gt; (&lt;span&gt;14&lt;/span&gt;, &lt;span&gt;17&lt;/span&gt;);&lt;/span&gt;&lt;/pre&gt;
&lt;p id="3154"&gt;Small change. The optimizer sees a completely different instruction. &lt;strong&gt;73% faster.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="ca0d"&gt;Tip 6: EXISTS over DISTINCT on one-to-many joins 61% reduction&lt;/h3&gt;
&lt;p id="4ed5"&gt;When you join a parent table to a child table in a one-to-many relationship and slap &lt;code&gt;DISTINCT&lt;/code&gt; on it to collapse the duplicates, the database fetches every matching row from both tables and then deduplicates the whole thing. That's a lot of rows moved around just to throw most of them away.&lt;/p&gt;
&lt;p id="219c"&gt;&lt;code&gt;EXISTS&lt;/code&gt; short-circuits. It checks whether a match exists and stops the moment it finds one. It never fetches the duplicates in the first place.&lt;/p&gt;
&lt;pre&gt;&lt;span id="4075"&gt;&lt;span&gt;-- Slow: fetches everything, then deduplicates&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; &lt;span&gt;DISTINCT&lt;/span&gt; c.country_id, c.country_name&lt;br&gt;&lt;span&gt;FROM&lt;/span&gt; countries c&lt;br&gt;&lt;span&gt;JOIN&lt;/span&gt; customers e &lt;span&gt;ON&lt;/span&gt; e.country_id &lt;span&gt;=&lt;/span&gt; c.country_id;&lt;br&gt;&lt;br&gt;&lt;span&gt;-- Fast: stops at first match per country&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; c.country_id, c.country_name&lt;br&gt;&lt;span&gt;FROM&lt;/span&gt; countries c&lt;br&gt;&lt;span&gt;WHERE&lt;/span&gt; &lt;span&gt;EXISTS&lt;/span&gt; (&lt;br&gt;    &lt;span&gt;SELECT&lt;/span&gt; &lt;span&gt;1&lt;/span&gt; &lt;span&gt;FROM&lt;/span&gt; customers e&lt;br&gt;    &lt;span&gt;WHERE&lt;/span&gt; e.country_id &lt;span&gt;=&lt;/span&gt; c.country_id&lt;br&gt;);&lt;/span&gt;&lt;/pre&gt;
&lt;p id="43db"&gt;Once you understand the short-circuit behavior, you’ll see &lt;code&gt;DISTINCT&lt;/code&gt; on joins differently. It's not wrong it's just usually the expensive way to ask a simple question.&lt;/p&gt;
&lt;h3 id="c6c8"&gt;Tip 7: UNION ALL over UNION 81% reduction&lt;/h3&gt;
&lt;p id="157f"&gt;&lt;code&gt;UNION&lt;/code&gt; deduplicates. &lt;code&gt;UNION ALL&lt;/code&gt; doesn't. That's the entire difference, and it costs &lt;strong&gt;81%&lt;/strong&gt; of your query time when the data can't have duplicates anyway or when you simply don't care.&lt;/p&gt;
&lt;pre&gt;&lt;span id="9c7b"&gt;&lt;span&gt;-- Slow: scans combined result for duplicates&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; cust_id &lt;span&gt;FROM&lt;/span&gt; sales&lt;br&gt;&lt;span&gt;UNION&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; cust_id &lt;span&gt;FROM&lt;/span&gt; customers;&lt;br&gt;&lt;br&gt;&lt;span&gt;-- Fast: skips deduplication entirely&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; cust_id &lt;span&gt;FROM&lt;/span&gt; sales&lt;br&gt;&lt;span&gt;UNION&lt;/span&gt; &lt;span&gt;ALL&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; cust_id &lt;span&gt;FROM&lt;/span&gt; customers;&lt;/span&gt;&lt;/pre&gt;
&lt;p id="40a1"&gt;The rule is simple: if your data sources can’t produce duplicates by definition, or if downstream logic handles it, &lt;code&gt;UNION ALL&lt;/code&gt; is strictly faster. Using &lt;code&gt;UNION&lt;/code&gt; by default because it feels safer is leaving 81% on the table.&lt;/p&gt;
&lt;h3 id="4250"&gt;Tip 8: Split OR in JOIN conditions into UNION ALL 70% reduction&lt;/h3&gt;
&lt;p id="52cd"&gt;This is the least obvious one in the entire paper, and probably the most common silent killer in production codebases. An &lt;code&gt;OR&lt;/code&gt; condition inside a &lt;code&gt;JOIN&lt;/code&gt; prevents index usage on both sides. The optimizer sees it and gives up on the index entirely, falling back to a full scan of both tables.&lt;/p&gt;
&lt;p id="5107"&gt;The fix is to split it into two separate joins and combine them with &lt;code&gt;UNION ALL&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;span id="1acf"&gt;&lt;span&gt;-- Slow: OR blocks index usage on both columns&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; &lt;span&gt;&lt;/span&gt; &lt;span&gt;FROM&lt;/span&gt; costs c&lt;br&gt;&lt;span&gt;INNER&lt;/span&gt; &lt;span&gt;JOIN&lt;/span&gt; products p&lt;br&gt;&lt;span&gt;ON&lt;/span&gt; c.unit_price &lt;span&gt;=&lt;/span&gt; p.prod_min_price&lt;br&gt;&lt;span&gt;OR&lt;/span&gt; c.unit_price &lt;span&gt;=&lt;/span&gt; p.prod_list_price;&lt;br&gt;&lt;br&gt;&lt;span&gt;-- Fast: two indexed joins, combined&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; &lt;span&gt;&lt;em&gt;&lt;/em&gt;&lt;/span&gt; &lt;span&gt;FROM&lt;/span&gt; costs c&lt;br&gt;&lt;span&gt;INNER&lt;/span&gt; &lt;span&gt;JOIN&lt;/span&gt; products p &lt;span&gt;ON&lt;/span&gt; c.unit_price &lt;span&gt;=&lt;/span&gt; p.prod_min_price&lt;br&gt;&lt;span&gt;UNION&lt;/span&gt; &lt;span&gt;ALL&lt;/span&gt;&lt;br&gt;&lt;span&gt;SELECT&lt;/span&gt; &lt;span&gt;&lt;/span&gt; &lt;span&gt;FROM&lt;/span&gt; costs c&lt;br&gt;&lt;span&gt;INNER&lt;/span&gt; &lt;span&gt;JOIN&lt;/span&gt; products p &lt;span&gt;ON&lt;/span&gt; c.unit_price &lt;span&gt;=&lt;/span&gt; p.prod_list_price;&lt;/span&gt;&lt;/pre&gt;
&lt;p id="7eb9"&gt;It looks more verbose. It is more verbose. It’s also 70% faster because both joins can now use their indexes cleanly. Verbose and fast beats clean and slow every time a product manager asks why the report takes three minutes to load.&lt;/p&gt;
&lt;p id="9def"&gt;If you want to see exactly what your optimizer is doing with any of these patterns, MySQL’s optimizer trace and PostgreSQL’s &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; will show you the execution plan in detail and make the index abandonment painfully visible.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="a6ce"&gt;&lt;strong&gt;Four tips.&lt;/strong&gt; Reductions of 73%, 61%, 81%, and 70%. All from patterns that look harmless in a code review because the query returns the right data. Correctness and performance are different problems, and SQL will let you solve one while completely ignoring the other.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;img alt="" width="800" height="436" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A945%2F1%2AWLL1OxLSiRzr55j5J6SH_Q.jpeg"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="3cc9"&gt;The silent ones&lt;/h2&gt;
&lt;p id="b82e"&gt;These two don’t announce themselves. No error, no warning, no slow query log entry when the table is small. You write them, they work, and then six months later when the data has grown and something is mysteriously sluggish, nobody connects it back to these lines.&lt;/p&gt;
&lt;h3 id="8ca9"&gt;Tip 9: No functions on indexed columns in WHERE 70% reduction&lt;/h3&gt;
&lt;p id="85d8"&gt;This one is responsible for more silent performance regressions than almost anything else on this list. The logic feels completely reasonable when you write it: you need to filter by year, the column stores full dates, so you wrap it in &lt;code&gt;EXTRACT()&lt;/code&gt; and move on.&lt;/p&gt;
&lt;pre&gt;&lt;span id="e0bd"&gt;&lt;span&gt;-- Slow: function call prevents index usage&lt;/span&gt;&lt;br&gt;&lt;span&gt;WHERE&lt;/span&gt; &lt;span&gt;EXTRACT&lt;/span&gt;(&lt;span&gt;YEAR&lt;/span&gt; &lt;span&gt;FROM&lt;/span&gt; time_id) &lt;span&gt;=&lt;/span&gt; &lt;span&gt;2001&lt;/span&gt;;&lt;br&gt;&lt;br&gt;&lt;span&gt;-- Fast: BETWEEN works with the index&lt;/span&gt;&lt;br&gt;&lt;span&gt;WHERE&lt;/span&gt; time_id &lt;span&gt;BETWEEN&lt;/span&gt; &lt;span&gt;'01-JAN-2001'&lt;/span&gt; &lt;span&gt;AND&lt;/span&gt; &lt;span&gt;'31-DEC-2001'&lt;/span&gt;;&lt;/span&gt;&lt;/pre&gt;
&lt;p id="8a3e"&gt;Here’s what actually happens: the moment you wrap a column in a function inside a &lt;code&gt;WHERE&lt;/code&gt; clause, the optimizer can't use the index on that column anymore. The index is organized around the raw column values. Once you transform those values with a function, the index has no idea how to help so it doesn't. The database computes &lt;code&gt;EXTRACT()&lt;/code&gt; for every single row, then filters. Full scan. Every time.&lt;/p&gt;
&lt;p id="62a6"&gt;The fix is to move the transformation to the value side, not the column side. Instead of asking “what year does this date belong to,” ask “does this date fall inside this year.” &lt;code&gt;BETWEEN&lt;/code&gt; two known date boundaries does exactly that, leaves the column untouched, and lets the index do its job.&lt;/p&gt;
&lt;p id="e4e6"&gt;This applies beyond &lt;code&gt;EXTRACT()&lt;/code&gt;. Any function wrapping an indexed column in a &lt;code&gt;WHERE&lt;/code&gt; clause &lt;code&gt;UPPER()&lt;/code&gt;, &lt;code&gt;LOWER()&lt;/code&gt;, &lt;code&gt;CAST()&lt;/code&gt;, &lt;code&gt;DATE_TRUNC()&lt;/code&gt;, &lt;code&gt;TO_CHAR()&lt;/code&gt; breaks index usage the same way. It's a category of mistake, not just one specific function.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="efbc"&gt;&lt;strong&gt;70% reduction. Zero schema changes required.&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id="70f6"&gt;Tip 10: Pre-calculate your math 11% reduction&lt;/h3&gt;
&lt;p id="8ca2"&gt;This one is smaller 11% but it might be the most embarrassing item on the list once you understand what’s happening.&lt;/p&gt;
&lt;pre&gt;&lt;span id="c038"&gt;&lt;span&gt;-- Slow: recalculates for every row&lt;/span&gt;&lt;br&gt;&lt;span&gt;WHERE&lt;/span&gt; cust_id &lt;span&gt;+&lt;/span&gt; &lt;span&gt;10000&lt;/span&gt; &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;35000&lt;/span&gt;;&lt;br&gt;&lt;br&gt;&lt;span&gt;-- Fast: constant evaluated once&lt;/span&gt;&lt;br&gt;&lt;span&gt;WHERE&lt;/span&gt; cust_id &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;25000&lt;/span&gt;;&lt;/span&gt;&lt;/pre&gt;
&lt;p id="775c"&gt;When your &lt;code&gt;WHERE&lt;/code&gt; clause contains arithmetic on a column, the database evaluates that expression for every row it scans. Not once every row. The value &lt;code&gt;10000&lt;/code&gt; isn't changing between rows. You already know the answer is &lt;code&gt;25000&lt;/code&gt;. But you made the database do the same addition hundreds of thousands of times because you didn't do it yourself first.&lt;/p&gt;
&lt;p id="55e1"&gt;Do the math before the query runs. It costs you nothing and saves the database from doing redundant arithmetic at scale.&lt;/p&gt;
&lt;p id="ad32"&gt;11% might sound modest compared to the numbers earlier in this article. But this is also the change that takes literally ten seconds. There’s no tradeoff to weigh, no refactor to plan. Just move the arithmetic outside the query. If you’re leaving 11% on the table because the fix felt too small to bother with, that’s a habit worth breaking.&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="9d00"&gt;Both of these fall into the same category: the optimizer wanted to help, and the way the query was written made that impossible. The database didn’t fail it did exactly what you asked. You just didn’t realize what you were asking.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="5210"&gt;The pre-ship checklist&lt;/h2&gt;
&lt;p id="a50e"&gt;Before you push that query whether it’s going into an ORM, a stored procedure, a data pipeline, or directly into prod run it through this. Seven questions. Takes thirty seconds.&lt;/p&gt;
&lt;pre&gt;&lt;span id="60ff"&gt;SELECT * anywhere?          → Specify only the columns you need&lt;br&gt;Filtering in HAVING?        → Move it to WHERE if no aggregate involved&lt;br&gt;Any DISTINCT?               → Do you actually need it, or is the key already there?&lt;br&gt;Nested subquery?            → Can it be rewritten as a JOIN?&lt;br&gt;OR in WHERE or JOIN?        → Try IN or UNION ALL instead&lt;br&gt;Function wrapping a column? → Move the transformation to the value side&lt;br&gt;Math on a column?           → Pre-calculate it before the query runs&lt;/span&gt;&lt;/pre&gt;
&lt;p id="39fa"&gt;None of these require a DBA, a schema change, or a ticket. They’re query-level habits. The kind that separate code that works from code that scales.&lt;/p&gt;
&lt;p id="1b60"&gt;Bookmark this. Drop it in your team’s wiki. Put it in your &lt;code&gt;CLAUDE.md&lt;/code&gt; if you're using Claude Code. Stick it somewhere you'll actually see it before the slow query alert fires at an inconvenient hour.&lt;/p&gt;
&lt;p id="b060"&gt;For deeper reading on index behavior and execution plans, &lt;a href="https://use-the-index-luke.com" rel="noopener ugc nofollow noreferrer"&gt;Use The Index, Luke&lt;/a&gt; is the best free resource on the internet for this topic. No fluff, just mechanics.&lt;/p&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="4d53"&gt;The queries you wrote last year are probably still running&lt;/h2&gt;
&lt;p id="91da"&gt;The paper this article is based on was published in 2015. The database you’re querying today almost certainly has rows that didn’t exist then. The query you wrote last year, maybe last month, is probably still executing exactly as you wrote it habits intact, DISTINCT in place, SELECT * quietly fetching columns nobody asked for.&lt;/p&gt;
&lt;p id="54c8"&gt;That’s the uncomfortable part. These aren’t exotic edge cases. They’re patterns that pass code review because the query returns correct results, and correctness is usually all anyone checks. Performance is someone else’s problem until it becomes everyone’s problem at 11pm on a Tuesday.&lt;/p&gt;
&lt;p id="d63e"&gt;The slightly spicy take: most slow SQL isn’t a database problem. It’s a habits problem. The optimizer is genuinely trying to help you it’s building execution plans, estimating costs, looking for index paths. What it can’t do is save you from instructions that actively prevent it from doing its job. Functions on indexed columns, OR conditions in joins, DISTINCT on results that can’t have duplicates these aren’t bugs the database can route around. They’re the query working exactly as written.&lt;/p&gt;
&lt;p id="e9ce"&gt;The good news is that optimizers are getting smarter. PostgreSQL 16 brought improvements to parallel query execution and partition pruning. AI-assisted query analysis tools are starting to surface execution plan issues automatically. The tooling is moving in the right direction.&lt;/p&gt;
&lt;p id="8202"&gt;But none of it will save you from &lt;code&gt;SELECT *&lt;/code&gt;.&lt;/p&gt;
&lt;p id="4680"&gt;The changes in this article aren’t framework upgrades or infrastructure investments. They’re rewrites. Most take under five minutes. Some show reductions above 80%. The paper tested them on Oracle but the underlying optimizer logic holds across PostgreSQL, MySQL, and SQL Server the principles are the same.&lt;/p&gt;
&lt;p id="34a1"&gt;Go check what your slowest queries are doing. There’s a reasonable chance one of these seven checklist items is in there.&lt;/p&gt;
&lt;p id="87cf"&gt;And if you’ve got a SQL habit that you’re mildly ashamed of a correlated subquery you know you should rewrite, a SELECT * you keep meaning to fix&lt;/p&gt;
&lt;blockquote&gt;&lt;p id="0026"&gt;drop it in the comments. No judgment. We’ve all shipped something we’d rather not explain.&lt;/p&gt;&lt;/blockquote&gt;
&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;h2 id="1cd4"&gt;Resources&lt;/h2&gt;
&lt;ul&gt;

&lt;li id="86ab"&gt;

&lt;strong&gt;Original paper:&lt;/strong&gt; Jean Habimana, &lt;em&gt;“Query Optimization Techniques: Tips For Writing Efficient And Faster SQL Queries”&lt;/em&gt;, IJSTR Vol. 4, Issue 10, October 2015&lt;/li&gt;

&lt;li id="78ec"&gt;

&lt;a href="https://use-the-index-luke.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;Use The Index, Luke&lt;/strong&gt;&lt;/a&gt; index mechanics explained without the academic fog&lt;/li&gt;

&lt;li id="68df"&gt;

&lt;a href="https://www.postgresql.org/docs/current/query-path.html" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;PostgreSQL query planning docs&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;how PG’s optimizer actually works&lt;/li&gt;

&lt;li id="be07"&gt;

&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/optimizer-tracing.html" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;MySQL optimizer trace&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;see exactly what MySQL is doing with your query&lt;/li&gt;

&lt;li id="0814"&gt;

&lt;a href="https://explain.dalibo.com" rel="noopener ugc nofollow noreferrer"&gt;&lt;strong&gt;explain.dalibo.com&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;visual EXPLAIN ANALYZE for Postgres, free and genuinely useful&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
