<?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: Odilon HUGONNOT</title>
    <description>The latest articles on DEV Community by Odilon HUGONNOT (@ohugonnot).</description>
    <link>https://dev.to/ohugonnot</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%2F3833552%2F48d32eab-68ed-4496-8ba6-f01e32806723.png</url>
      <title>DEV Community: Odilon HUGONNOT</title>
      <link>https://dev.to/ohugonnot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ohugonnot"/>
    <language>en</language>
    <item>
      <title>The Ideal Cart: 5 Design Patterns That Earn Their Keep (and 2 Refused)</title>
      <dc:creator>Odilon HUGONNOT</dc:creator>
      <pubDate>Sun, 19 Jul 2026 09:00:03 +0000</pubDate>
      <link>https://dev.to/ohugonnot/the-ideal-cart-5-design-patterns-that-earn-their-keep-and-2-refused-2nli</link>
      <guid>https://dev.to/ohugonnot/the-ideal-cart-5-design-patterns-that-earn-their-keep-and-2-refused-2nli</guid>
      <description>&lt;p&gt;It all starts with a review question. I was reworking my &lt;a href="https://www.web-developpeur.com/en/library/design-patterns/" rel="noopener noreferrer"&gt;notes on the Design Patterns book&lt;/a&gt;, the Gang of Four one, with examples that all came from the same universe: an online shop. Shipping fees for Strategy, the order for State, stock for Observer. And the question landed: "so what would the ideal cart be? One class that uses all the patterns?"&lt;/p&gt;

&lt;p&gt;Excellent question. Wrong direction. And the answer deserves more than a paragraph, because it contains roughly everything I know about design.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap: the ultimate Cart class
&lt;/h2&gt;

&lt;p&gt;The starting intuition is healthy: if all five examples come from the same shop, why not assemble everything? The trap is the word "class". A Cart class stacking Strategy, Decorator, State, Observer and a Facade "to show off" is exactly what the 1994 book calls pattern fever, and it warns against it as early as page 31: a pattern should only be applied when the flexibility it brings is actually needed.&lt;/p&gt;

&lt;p&gt;The ideal cart is not a class. It is a small architecture where each pattern holds the exact post where it earns its keep. And the hiring criterion fits in one question, the most useful one in the book: &lt;strong&gt;what is going to change?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Five hires, five nameable problems
&lt;/h2&gt;

&lt;p&gt;So I built the full checkout flow, starting from zero and introducing each pattern only when a real request demanded it. Here is the hiring log:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shipping fees vary → Strategy.&lt;/strong&gt; Standard post, express, store pickup: three calculations for the same question, "how much?". One &lt;code&gt;ShippingFee&lt;/code&gt; interface, one class per carrier. Adding a new carrier tomorrow: one class, zero modification elsewhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ShippingFee&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Cart&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StandardPost&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ShippingFee&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Cart&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;4.99&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The promo stacks → Decorator.&lt;/strong&gt; "Free shipping over €50" is not a property of the carriers, it is a wrapper around them. Same interface as what it wraps: to the rest of the code, a decorated carrier is a carrier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FreeShippingOver&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ShippingFee&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;ShippingFee&lt;/span&gt; &lt;span class="nv"&gt;$base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nv"&gt;$threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Cart&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;total&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The order has life stages → State.&lt;/strong&gt; "Cancel" does not mean the same thing in the cart (empty it), paid (refund) or shipped (carrier return). Instead of an if/elseif on the status duplicated in every method, the order carries a state object it swaps at each stage, and &lt;code&gt;cancel()&lt;/code&gt; delegates. Zero ifs, forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paying must trigger the unknown → Observer.&lt;/strong&gt; Email, stock, accounting, and the SMS marketing will ask for next month. The &lt;code&gt;pay()&lt;/code&gt; method must not know that list: it announces, and subscribers react. A list of callbacks and one loop: that is the whole pattern, and it is the same one as your &lt;code&gt;addEventListener&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The controller wants one button → Facade.&lt;/strong&gt; A &lt;code&gt;Checkout&lt;/code&gt; object receives the shipping strategy (possibly decorated) and the event bus, and exposes one &lt;code&gt;placeOrder()&lt;/code&gt; method. The code receiving the POST knows only it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The detail that changes everything: where decisions live
&lt;/h2&gt;

&lt;p&gt;The most important piece of the ideal cart is none of the five patterns. It is the assembly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The ONLY place in the program that knows the concrete classes.&lt;/span&gt;
&lt;span class="nv"&gt;$checkout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FreeShippingOver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StandardPost&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// a Decorator around a Strategy&lt;/span&gt;
    &lt;span class="nv"&gt;$events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every concrete choice is made at the root, at assembly time. &lt;code&gt;Checkout&lt;/code&gt; receives interfaces: it does not know whether shipping is decorated, nor who subscribed to the events. If you have read my &lt;a href="https://www.web-developpeur.com/en/library/clean-architecture/" rel="noopener noreferrer"&gt;Clean Architecture notes&lt;/a&gt;, you recognize the dependency rule: the two books, written twenty-three years apart, converge on exactly the same move.&lt;/p&gt;

&lt;p&gt;And the second lesson is my favorite: &lt;strong&gt;the most central class is the dumbest&lt;/strong&gt;. The Cart itself uses no pattern. It adds up lines. The patterns live around it, at the places that change, never at the center.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two refusals (as important as the hires)
&lt;/h2&gt;

&lt;p&gt;Along the way, two patterns applied and were turned down. A &lt;strong&gt;Singleton&lt;/strong&gt; (&lt;code&gt;Cart::getInstance()&lt;/code&gt;, "to access the cart from anywhere"): a global variable in disguise, untestable, and root injection already covers the need. A &lt;strong&gt;Command&lt;/strong&gt; with an undo/redo history, "just in case": nobody asked to cancel step by step, and a pattern installed for an imaginary need is complexity paid upfront.&lt;/p&gt;

&lt;p&gt;A good design shows in its absent patterns as much as its present ones. The list of what your code refuses to do says more than the list of what it can do; it is the same idea as &lt;a href="https://www.web-developpeur.com/en/blog/progres-langages-soustraction" rel="noopener noreferrer"&gt;language progress by subtraction&lt;/a&gt;, one floor down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result, playable
&lt;/h2&gt;

&lt;p&gt;None of this stayed a drawing. The shop actually runs, and I turned it into the 13th guided project of the learning section: &lt;a href="https://www.web-developpeur.com/apprendre/projets/panier-design-patterns/" rel="noopener noreferrer"&gt;"The pattern shop"&lt;/a&gt; (in French), where the checkout gets built step by step, with the real AI prompts, the first drafts that crack, two predictions to make before reading, and a final challenge (a second promo, stacked on the first, without modifying a single existing class).&lt;/p&gt;

&lt;p&gt;Every button in the demo goes through a pattern, and an event log at the bottom shows the Observer subscribers waking up when you pay. If you just want the skeleton, it is also in the &lt;a href="https://www.web-developpeur.com/en/library/design-patterns/" rel="noopener noreferrer"&gt;book notes&lt;/a&gt;, as a boxed aside.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What stays with me from this exercise is not the code, it is the order of operations. Not once did I ask "which pattern should I put here?". I listed the shop's likely requests, and each pattern arrived as the answer to one precise request, with a problem name attached. The two times a pattern showed up without a problem to solve, it was sent home.&lt;/p&gt;

&lt;p&gt;Maybe that is the real definition of the ideal cart: not the one containing the most patterns, but the one where every pattern can answer the question "what are you here for?" without stammering.&lt;/p&gt;

</description>
      <category>designpatterns</category>
      <category>php</category>
      <category>oop</category>
      <category>strategy</category>
    </item>
    <item>
      <title>Big O for the Impatient: Why Your Loop Is Slow (and When It Matters)</title>
      <dc:creator>Odilon HUGONNOT</dc:creator>
      <pubDate>Sat, 18 Jul 2026 09:00:04 +0000</pubDate>
      <link>https://dev.to/ohugonnot/big-o-for-the-impatient-why-your-loop-is-slow-and-when-it-matters-30le</link>
      <guid>https://dev.to/ohugonnot/big-o-for-the-impatient-why-your-loop-is-slow-and-when-it-matters-30le</guid>
      <description>&lt;p&gt;The page loads in 80 ms locally. In prod, on the real database, it takes 9 seconds and freezes the tab. The code did not change. The data did: 60 test rows locally, 14,000 in production.&lt;/p&gt;

&lt;p&gt;The culprit was a ten-line loop, perfectly readable, hunting duplicates in a list. It had no bug. It just had the wrong &lt;em&gt;shape&lt;/em&gt;. And understanding that shape is all Big O is about. No math, promise: just enough to read your own code and know which part will explode as the data grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Big O measures a shape, not a speed
&lt;/h2&gt;

&lt;p&gt;First intuition to break: &lt;code&gt;O(...)&lt;/code&gt; does not tell you "this takes 3 milliseconds". It tells you &lt;strong&gt;how the time reacts when you double the data&lt;/strong&gt;. It is a curve shape, not a stopwatch. Three families cover the overwhelming majority of the job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;O(1)&lt;/code&gt; — constant.&lt;/strong&gt; Data size changes nothing. Reading &lt;code&gt;array[5000]&lt;/code&gt; is as fast as &lt;code&gt;array[5]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;O(n)&lt;/code&gt; — linear.&lt;/strong&gt; Twice the data, twice the time. A loop that walks everything.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;O(n²)&lt;/code&gt; — quadratic.&lt;/strong&gt; Twice the data, &lt;em&gt;four&lt;/em&gt; times the time. The killer class, and the one my loop had.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three growth curves: O(1) flat, O(n) straight, O(n²) exploding amount of data (n) → time → O(1) O(n) O(n²) at small scale, all three look alike then O(n²) breaks away&lt;/p&gt;

&lt;p&gt;The trap: on 60 test rows, all three curves are glued together. It is at production scale that O(n²) breaks away.&lt;/p&gt;

&lt;p&gt;That is why my bug was invisible locally: at n = 60, &lt;code&gt;O(n)&lt;/code&gt; does 60 operations and &lt;code&gt;O(n²)&lt;/code&gt; does 3,600. The difference is microseconds, nobody sees it. At n = 14,000, &lt;code&gt;O(n)&lt;/code&gt; does 14,000 operations, &lt;code&gt;O(n²)&lt;/code&gt; does &lt;strong&gt;196 million&lt;/strong&gt;. That, you feel.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop that breaks away
&lt;/h2&gt;

&lt;p&gt;The offending code, stripped to the bone: for each element, I rescan the whole list to see if it appears twice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✗ O(n²): a loop INSIDE a loop&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dupes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;        &lt;span class="c1"&gt;// ← the rescan that costs&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;dupes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same trap hides in a sneakier shape, the one that got me for years: &lt;code&gt;includes()&lt;/code&gt; inside a loop. It looks like a single loop, but &lt;code&gt;includes&lt;/code&gt; hides a second one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✗ disguised O(n²): each includes() rescans the whole array&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// includes = O(n), in a loop = O(n²)&lt;/span&gt;
    &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is one word: &lt;code&gt;Set&lt;/code&gt;. A &lt;code&gt;Set&lt;/code&gt; (or an object, or a &lt;code&gt;Map&lt;/code&gt;) is a &lt;strong&gt;hash table&lt;/strong&gt;: checking membership with &lt;code&gt;.has()&lt;/code&gt; is &lt;code&gt;O(1)&lt;/code&gt;, whatever the size. The double loop collapses into a single one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✓ O(n): the Set answers in O(1), one pass is enough&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dupes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;dupes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// O(1)&lt;/span&gt;
    &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My 9 seconds dropped to 40 milliseconds. No clever optimization, no cache: just the right curve &lt;em&gt;shape&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Set is magic: array versus linked list
&lt;/h2&gt;

&lt;p&gt;To see where that &lt;code&gt;O(1)&lt;/code&gt; comes from, you have to open the hood of data structures. It all rests on one question: &lt;strong&gt;where do the elements live in memory?&lt;/strong&gt; Memory is one long row of numbered cells.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;array&lt;/strong&gt; stores its elements in cells glued next to each other. Since it knows the start address, it instantly computes where the 5000th lives: read in &lt;code&gt;O(1)&lt;/code&gt;. The flip side: inserting at the front forces everything else to shift over by one, so &lt;code&gt;O(n)&lt;/code&gt;. That is exactly what &lt;code&gt;unshift()&lt;/code&gt; does in JavaScript, where &lt;code&gt;push()&lt;/code&gt; (append at the end) is free.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;linked list&lt;/strong&gt; does the opposite: its elements are scattered anywhere, and each holds the address of the next, like a treasure hunt. Inserting shifts nothing (&lt;code&gt;O(1)&lt;/code&gt;), but reading the 5000th forces you to follow the arrows one by one from the start (&lt;code&gt;O(n)&lt;/code&gt;). You will almost never write one by hand: in PHP it is &lt;code&gt;SplDoublyLinkedList&lt;/code&gt;, in Python &lt;code&gt;collections.deque&lt;/code&gt;, in Go &lt;code&gt;container/list&lt;/code&gt;, and in JavaScript… you code it yourself.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;hash table&lt;/strong&gt; (the &lt;code&gt;Set&lt;/code&gt;, the &lt;code&gt;Map&lt;/code&gt;, Python's &lt;code&gt;dict&lt;/code&gt;, PHP's &lt;code&gt;array&lt;/code&gt;) plays in another league: a function turns the key directly into a memory position. No walking, no shifting: read, write, test membership, all in &lt;code&gt;O(1)&lt;/code&gt;. That is why swapping an &lt;code&gt;array.includes()&lt;/code&gt; for a &lt;code&gt;Set.has()&lt;/code&gt; turns a quadratic curve into a linear one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden O(n) behind an innocent line
&lt;/h2&gt;

&lt;p&gt;The real skill is not reciting these classes, it is &lt;strong&gt;spotting them in your own code&lt;/strong&gt;, often tucked behind innocent syntax:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;array.unshift(x)&lt;/code&gt; and a mid-array &lt;code&gt;array.splice(i, 0, x)&lt;/code&gt;: &lt;code&gt;O(n)&lt;/code&gt;, everything shifts.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;array.includes(x)&lt;/code&gt; or &lt;code&gt;array.indexOf(x)&lt;/code&gt; inside a loop: &lt;code&gt;O(n²)&lt;/code&gt;. A &lt;code&gt;Set&lt;/code&gt; or a &lt;code&gt;Map&lt;/code&gt; brings it back to &lt;code&gt;O(n)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  An SQL query &lt;code&gt;WHERE email = ?&lt;/code&gt; with no index on the column: the database does a &lt;em&gt;full scan&lt;/em&gt;, &lt;code&gt;O(n)&lt;/code&gt; over millions of rows. The index is its version of binary search, &lt;code&gt;O(log n)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  A &lt;code&gt;.find()&lt;/code&gt; inside a &lt;code&gt;.map()&lt;/code&gt; to "join" two lists: again a loop in a loop. Index one of the two lists into a &lt;code&gt;Map&lt;/code&gt; first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Nobody will ask you to rewrite a hash table, your language ships one. That is not what Big O is for. It is for reading a loop and &lt;em&gt;seeing its future&lt;/em&gt;: this one will hold at a million elements, that one will freeze the page. It is a reading skill, not a writing one.&lt;/p&gt;

&lt;p&gt;And the irony is that AI changes nothing here, quite the opposite. An assistant happily produces an &lt;code&gt;O(n²)&lt;/code&gt; loop that passes every test on 50 rows and dies in prod. Recognizing the curve shape in generated code is something you cannot delegate. If the topic clicks, I wrote a whole &lt;a href="https://www.web-developpeur.com/en/library/grokking-algorithms/" rel="noopener noreferrer"&gt;reading note on &lt;em&gt;Grokking Algorithms&lt;/em&gt;&lt;/a&gt;, the book that finally reconciled me with algorithms without a single formal proof.&lt;/p&gt;

</description>
      <category>bigo</category>
      <category>algorithms</category>
      <category>performance</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Programming Language Progress Is Subtraction</title>
      <dc:creator>Odilon HUGONNOT</dc:creator>
      <pubDate>Fri, 17 Jul 2026 09:00:03 +0000</pubDate>
      <link>https://dev.to/ohugonnot/programming-language-progress-is-subtraction-195k</link>
      <guid>https://dev.to/ohugonnot/programming-language-progress-is-subtraction-195k</guid>
      <description>&lt;p&gt;While re-reading &lt;a href="https://www.web-developpeur.com/en/library/clean-architecture/" rel="noopener noreferrer"&gt;Clean Architecture&lt;/a&gt; for my library notes, one sentence stopped me cold. Robert Martin defines the three programming paradigms, and he defines them backwards from every brochure ever printed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Each of the paradigms removes capabilities from the programmer. None of them adds new capabilities." (Clean Architecture, ch. 3)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read it again. Seventy years of language progress, and the three greatest leaps forward are &lt;em&gt;prohibitions&lt;/em&gt;. Nobody ever gave you anything. They confiscated your dangerous toys, one by one, and every confiscation made your code more predictable. Once you see the pattern, you see it everywhere, all the way into the languages of 2026. And it raises a dizzying question: what is left to take away?&lt;/p&gt;

&lt;h2&gt;
  
  
  1968: the first confiscation
&lt;/h2&gt;

&lt;p&gt;The structured paradigm was born from a Dijkstra letter that became legend, "Go To Statement Considered Harmful". The &lt;code&gt;goto&lt;/code&gt; was absolute freedom: jump anywhere in the program, anytime. And that was precisely the problem. Code riddled with gotos cannot be read, only investigated. You cannot reason about a block if anyone can land in it from anywhere.&lt;/p&gt;

&lt;p&gt;The solution was not to add a feature. It was to ban the wild jump. What you are left with, &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;, &lt;code&gt;for&lt;/code&gt;, is the tamed goto: control transfer that announces where it comes from and where it goes. You use the structured paradigm every day without knowing it, the way you speak in prose.&lt;/p&gt;

&lt;p&gt;The next two paradigms follow the same pattern, and that is Martin's actual discovery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;object-oriented&lt;/strong&gt; (Dahl &amp;amp; Nygaard, 1966) removes wild indirect jumps. In C, you could call "whatever code's address happens to be in this variable", with zero guarantee the address was any good. OO replaces that with the method: an indirect call whose target the compiler guarantees. Polymorphism is the function pointer, tamed;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;functional&lt;/strong&gt; (Church, 1936, before computers even existed) removes assignment: once created, a value never changes. And if nothing changes, whole families of bugs (shared state, concurrency) vanish with it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three paradigms, three removals, all discovered between 1958 and 1968. Martin draws a bet from it: there is nothing left to remove, there will be no fourth paradigm.&lt;/p&gt;

&lt;p&gt;Timeline of removals: every language leap takes a capability away 1968 · Structured removes goto → code reads top to bottom 1966 · Object-oriented removes the wild indirect jump → compiler-guaranteed calls 1936 · Functional removes assignment → a value never changes again "there will be no fourth paradigm" (Martin, 2017) 2015 · Rust (ownership) removes shared mutation → data races no longer compile 2018 · Structured concurrency removes the wild spawn → no task outlives its block&lt;/p&gt;

&lt;p&gt;The confiscation timeline. In green, Martin's three paradigms. In red, the two removals that contradict his bet: one already existed, the other arrived a year later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lost bet (and why that's good news)
&lt;/h2&gt;

&lt;p&gt;The bet is printed in Clean Architecture, published in September 2017. And the spicy part: it was already losing when the book came out. One of the two counterexamples had existed for two years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rust&lt;/strong&gt; (1.0 in May 2015) removed shared mutation: it is forbidden to have two places in the code mutating the same data at the same time. That is neither OO's removal (you can still make indirect calls) nor functional's (you can still mutate, just never two at once). The compiler rejects the program, and the entire category of data races disappears at compile time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structured concurrency&lt;/strong&gt;, for its part, arrived one year after the book, and removed the wild spawn: it is forbidden to launch a background task that outlives the block that created it. Nathaniel J. Smith's founding essay (2018) is titled "Go Statement Considered Harmful", a deliberate echo of Dijkstra, because it is exactly the same crime: control flying off to who-knows-where, for who-knows-how-long. The unsupervised &lt;code&gt;spawn&lt;/code&gt; is the goto of concurrency.&lt;/p&gt;

&lt;p&gt;Martin was talking about sequential code, and on that narrow ground his inventory is probably complete. But his mechanics of progress, remove to make predictable, keeps producing. Right on the principle, wrong on the prophecy. That is the best kind of mistake: the one that confirms the theory while breaking the prediction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go, or the art of keeping only the marrow
&lt;/h2&gt;

&lt;p&gt;If subtraction drives progress, then the most instructive language of the era is not the most powerful one, it is the most stubborn refuser. Rob Pike, Go's co-creator, summed up the language's philosophy in a 2012 talk whose title is the whole program: "Less is exponentially more".&lt;/p&gt;

&lt;p&gt;Look at what Go refused: classes, inheritance, exceptions, operator overloading, annotations, and for ten years, generics. The list of absences caused screaming. But look at what it kept from OO: one single thing, the interface, that is, the compiler-guaranteed indirect call. And it even purified it: in Go, a type satisfies an interface &lt;em&gt;automatically&lt;/em&gt;, without declaring it. Business code defines its interface at home, and the database package does not even know it exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// the business package declares its need, at home&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Saver&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="n"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// the mysql package imports NOTHING from the business code.&lt;/span&gt;
&lt;span class="c"&gt;// It has a Save method: it satisfies the interface, period.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="n"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is Clean Architecture's dependency inversion, no longer as a technique you apply but as the language's natural state. Go threw away OO's packaging and kept the only piece that mattered. Seventy years of OOP distilled into a mechanism that fits in ten lines of spec. If you want a closer look at that triage, I wrote up &lt;a href="https://www.web-developpeur.com/en/library/learning-go/" rel="noopener noreferrer"&gt;Learning Go&lt;/a&gt; in the library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Those who removed more, and what it cost them
&lt;/h2&gt;

&lt;p&gt;Go is not the end of the road. Other languages pushed subtraction further, and their trajectories draw the real limit of the exercise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zig&lt;/strong&gt; removes hidden control flow. Its manifesto fits in one line: no exceptions, no overloading, no invisible allocation. You read a line, you know what it executes, all of what it executes. An error is a value the compiler forces you to handle, where Go still lets you write &lt;code&gt;_ = err&lt;/code&gt; while whistling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Elm&lt;/strong&gt; removes undeclared effects: a function can neither mutate nor do sneaky I/O, everything goes through the return type. The famous, measurable result: no runtime exceptions in production. Probably the biggest reliability win ever bought by removal. And yet Elm remains a stagnating niche language: too pure, tiny ecosystem, the market ruled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Erlang&lt;/strong&gt; removes shared memory between tasks, since 1986: isolated processes passing messages, crashes that stay local. The whole industry spent thirty years rediscovering what its creators had figured out for telephone switches.&lt;/p&gt;

&lt;p&gt;The emerging pattern: the first removals were free, nobody mourns the goto. The next ones cost. Rust buys the absence of data races at the price of a brutal learning curve. Elm buys the absence of crashes at the price of isolation. Go is probably a &lt;em&gt;sociological&lt;/em&gt; local optimum: its real product is not purity, it is "any dev on the team reads anyone's code". Remove more, and you often lose that. The Pareto curve bends.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Esperanto of programming?
&lt;/h2&gt;

&lt;p&gt;Hence the question rattling in my head since that re-read: could an AI design the terminal language? The one that synthesizes every winning removal and keeps the essential unit bricks, the marrow and nothing else: mandatory errors-as-values, effects visible in signatures, native structured concurrency, zero hidden control flow. An Esperanto of programming: minimal, regular, no exceptions, no historical quirks.&lt;/p&gt;

&lt;p&gt;The Esperanto analogy is tempting, and it teaches mostly through its failure. Esperanto is objectively simpler than every language it meant to replace. Regular grammar, zero irregular verbs, learnable ten times faster. It lost anyway, because a language does not win on intrinsic qualities: it wins on its ecosystem. You learn English for the people who already speak it, not for its grammar. Replace "people" with "libraries, tutorials, job offers, Stack Overflow answers" and you have exactly the gravity that keeps PHP, Java and JavaScript in orbit decades after "better" languages appeared.&lt;/p&gt;

&lt;p&gt;But there is one parameter Esperanto never had: the primary speaker is changing. A growing share of code is written by AI, and for an AI, the cost calculation flips. Rust's brutal learning curve costs a model nothing. The library ecosystem weighs less when the generator can rewrite the missing brick. And every additional prohibition becomes a windfall: a language that rejects more programs at compile time is a better harness for generated code, every removal turns a class of plausible bugs into immediate compile errors. The removals "too expensive for humans" become profitable when the machine does the writing.&lt;/p&gt;

&lt;p&gt;My honest forecast: the single universal language will not come, because domains pull in opposite directions (SQL removed the loop, shaders removed recursion, both were right, and neither can replace the other). What does seem plausible is a &lt;em&gt;target&lt;/em&gt; language designed for the generated-code era, assembling the all-time best of subtractions. Not the Esperanto everyone speaks; the Latin machines would write to each other, checkable by the most intransigent compiler ever built. And we would review it, because by construction it would be boring to read. That is a compliment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The thing this re-read changed in how I evaluate a tool: stop asking what it enables, ask what it forbids. The feature list is marketing; the prohibition list is engineering. A framework that can do anything protects you from nothing.&lt;/p&gt;

&lt;p&gt;And the final irony deserves savoring: we are building the most powerful code generators in history, AIs capable of producing anything, and the best thing we can hand them is a language that refuses almost everything. Seventy years of progress by subtraction, only to understand that the most precious gift a language can give a programmer, human or not, is a compiler saying "no".&lt;/p&gt;

</description>
      <category>languages</category>
      <category>cleanarchitecture</category>
      <category>go</category>
      <category>rust</category>
    </item>
    <item>
      <title>Claude Code Hooks: Real Examples (PostToolUse, Stop, PreToolUse)</title>
      <dc:creator>Odilon HUGONNOT</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:00:02 +0000</pubDate>
      <link>https://dev.to/ohugonnot/claude-code-hooks-real-examples-posttooluse-stop-pretooluse-620</link>
      <guid>https://dev.to/ohugonnot/claude-code-hooks-real-examples-posttooluse-stop-pretooluse-620</guid>
      <description>&lt;p&gt;For the tenth time today, I type "don't forget to run gofmt" to Claude Code. Then "run the tests before you stop." Then "don't touch the &lt;code&gt;.env&lt;/code&gt;." The day I wired up three hooks, those three sentences vanished from my vocabulary — the machine applies them itself, every time, without me thinking about it.&lt;/p&gt;

&lt;p&gt;Claude Code hooks are poorly documented when it comes to &lt;em&gt;real&lt;/em&gt; examples: the docs list the events, not the configs you actually use day to day. Here are mine, ready to copy, with the traps I hit along the way — including the Stop hook infinite loop, the classic one.&lt;/p&gt;

&lt;h2&gt;
  
  
  A hook is a script wired to an event
&lt;/h2&gt;

&lt;p&gt;A hook is a shell command Claude Code runs automatically at a specific point in its cycle. It receives JSON on &lt;code&gt;stdin&lt;/code&gt; (the tool name, its arguments, the directory…) and can let it through, block it, or inject context. The configuration lives in &lt;code&gt;settings.json&lt;/code&gt; (global, project, or local).&lt;/p&gt;

&lt;p&gt;A tool call lifecycle: PreToolUse before the tool, PostToolUse after, Stop at the end of the turn PreToolUseblock / allow Tool PostToolUseformat / lint Stopend of turn&lt;/p&gt;

&lt;p&gt;The hook points around a tool call. (+ UserPromptSubmit, Notification, SubagentStop.)&lt;/p&gt;

&lt;p&gt;The most useful events:&lt;/p&gt;

&lt;p&gt;Event&lt;/p&gt;

&lt;p&gt;Fires…&lt;/p&gt;

&lt;p&gt;Typical use&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PreToolUse&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;before a tool call&lt;/p&gt;

&lt;p&gt;block a command, validate&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PostToolUse&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;after a tool call&lt;/p&gt;

&lt;p&gt;format, lint, test&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Stop&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;when Claude wants to stop&lt;/p&gt;

&lt;p&gt;force a final check&lt;/p&gt;

&lt;p&gt;&lt;code&gt;UserPromptSubmit&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;on every message you send&lt;/p&gt;

&lt;p&gt;inject context&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Notification&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;when Claude is waiting&lt;/p&gt;

&lt;p&gt;desktop notification&lt;/p&gt;

&lt;h2&gt;
  
  
  PostToolUse: auto-format after every edit
&lt;/h2&gt;

&lt;p&gt;The life-changing hook. As soon as Claude writes or edits a file, format it. Never again a diff polluted by indentation. The &lt;code&gt;matcher&lt;/code&gt; targets the &lt;code&gt;Edit&lt;/code&gt; and &lt;code&gt;Write&lt;/code&gt; tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PostToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Edit|Write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"f=$(jq -r '.tool_input.file_path'); [ &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;${f##*.}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; = go ] &amp;amp;&amp;amp; gofmt -w &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;$f&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;; true"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hook receives the call's JSON on &lt;code&gt;stdin&lt;/code&gt;; &lt;code&gt;jq&lt;/code&gt; extracts the file path. We only format if it's Go. The trailing &lt;code&gt;; true&lt;/code&gt; guarantees a 0 exit code — otherwise a formatting error would block Claude for nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  PreToolUse: block what shouldn't go through
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;PreToolUse&lt;/code&gt; can &lt;strong&gt;refuse&lt;/strong&gt; a tool call before it runs. Two ways: an exit code of &lt;code&gt;2&lt;/code&gt; (blocks and returns stderr to the model), or a finer JSON decision. Example: forbid any edit to &lt;code&gt;.env&lt;/code&gt;, no matter how much the model insists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PreToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Edit|Write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"f=$(jq -r '.tool_input.file_path'); case &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;$f&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; in *.env|*secrets*) echo 'Protected file' &amp;gt;&amp;amp;2; exit 2;; esac"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code &lt;code&gt;2&lt;/code&gt; = block, and the &lt;code&gt;stderr&lt;/code&gt; message is returned to Claude so it understands &lt;em&gt;why&lt;/em&gt;. It's more reliable than an instruction in the prompt: a hook never "forgets" between two turns of context. Same defensive logic as a &lt;a href="https://www.web-developpeur.com/en/blog/go-middleware-best-practices-production" rel="noopener noreferrer"&gt;middleware that filters before the handler&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop: force completion without an infinite loop
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Stop&lt;/code&gt; fires when Claude thinks it's done. You can &lt;strong&gt;send it back to work&lt;/strong&gt; — for example if some files are untested. But careful: if the hook blocks the stop and Claude re-triggers a Stop, and the hook re-blocks… infinite loop. The protection is the &lt;code&gt;stop_hook_active&lt;/code&gt; field in the received JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# stop-guard.sh — refuse to stop if tests fail, ONCE&lt;/span&gt;
&lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# if we're already in a Stop re-run, let it through (anti-loop)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.stop_hook_active'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi
if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; go &lt;span class="nb"&gt;test&lt;/span&gt; ./... &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Tests are failing — fix them before stopping."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;stop_hook_active&lt;/code&gt; guard is &lt;em&gt;the&lt;/em&gt; detail that separates a useful hook from an agent stuck in a loop. Without it, a single red test turns your session into an endless loop. It's exactly the reflex of a &lt;a href="https://www.web-developpeur.com/en/blog/goroutine-leaks-golang" rel="noopener noreferrer"&gt;cancellation context&lt;/a&gt;: plan the exit condition before you start the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  UserPromptSubmit: inject context automatically
&lt;/h2&gt;

&lt;p&gt;This hook runs on every message you send, &lt;em&gt;before&lt;/em&gt; Claude reads it. Whatever the hook writes to &lt;code&gt;stdout&lt;/code&gt; is injected into the context. Handy to remind a project convention, the time, or the git state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"UserPromptSubmit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Git branch: $(git branch --show-current 2&amp;gt;/dev/null)&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use sparingly: everything you inject costs tokens on every message. A short, useful reminder, not a dump.&lt;/p&gt;

&lt;h2&gt;
  
  
  The traps to know
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A hook runs with your permissions.&lt;/strong&gt; It's shell executed on your machine, no confirmation. Never write a hook that executes part of the received JSON without validating it — that's an injection door. Treat the model's output as untrusted input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exit code or JSON, not half of each.&lt;/strong&gt; Exit &lt;code&gt;2&lt;/code&gt; blocks, &lt;code&gt;0&lt;/code&gt; lets through, the rest is non-blocking. For fine control (allow/deny with a structured reason), return JSON on &lt;code&gt;stdout&lt;/code&gt; rather than juggling exit codes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance matters.&lt;/strong&gt; A slow &lt;code&gt;PostToolUse&lt;/code&gt; hook runs after &lt;em&gt;every&lt;/em&gt; edit. A &lt;code&gt;gofmt&lt;/code&gt; is instant; running the whole test suite on every write is not. Keep the heavy stuff for &lt;code&gt;Stop&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A hook isn't one more instruction in your prompt — it's a guarantee. The difference is huge: an instruction, the model can forget on the next turn as context fills up; a hook always runs, identically, because it's no longer the model deciding. Anything you catch yourself repeating to Claude is a candidate for a hook.&lt;/p&gt;

&lt;p&gt;Start with the formatting &lt;code&gt;PostToolUse&lt;/code&gt; — immediate win, zero risk. Add the &lt;code&gt;Stop&lt;/code&gt; with its anti-loop guard when you want to lock in a final check. And keep in mind that you're wiring shell onto a model-driven loop: the rigor you put into your services, put it into your hooks.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>hooks</category>
      <category>ai</category>
      <category>automation</category>
    </item>
    <item>
      <title>Go Iterators (range-over-func): the yield contract and the traps</title>
      <dc:creator>Odilon HUGONNOT</dc:creator>
      <pubDate>Wed, 15 Jul 2026 09:00:03 +0000</pubDate>
      <link>https://dev.to/ohugonnot/go-iterators-range-over-func-the-yield-contract-and-the-traps-270c</link>
      <guid>https://dev.to/ohugonnot/go-iterators-range-over-func-the-yield-contract-and-the-traps-270c</guid>
      <description>&lt;p&gt;I wanted to turn a pagination helper into a "clean" iterator with Go 1.23's &lt;code&gt;range&lt;/code&gt; syntax. Thirty seconds later: &lt;code&gt;panic: range function continued iteration after function call returned false&lt;/code&gt;. The kind of message that says nothing until you understand the underlying contract. range-over-func iterators are elegant, but they shift part of the responsibility from the compiler to &lt;em&gt;you&lt;/em&gt; — and that's where it breaks.&lt;/p&gt;

&lt;p&gt;Here's how they actually work, and the three traps that turn a "clean" iterator into a production bug: the yield contract, cleanup on break, and the &lt;code&gt;iter.Pull&lt;/code&gt; leak.&lt;/p&gt;

&lt;h2&gt;
  
  
  An iterator is a function that takes a yield
&lt;/h2&gt;

&lt;p&gt;Since Go 1.23, you can &lt;code&gt;range&lt;/code&gt; over a function. The &lt;code&gt;iter&lt;/code&gt; package standardizes two signatures: &lt;code&gt;iter.Seq[V]&lt;/code&gt; (one value) and &lt;code&gt;iter.Seq2[K, V]&lt;/code&gt; (two, like key/value). An iterator is a function that receives a &lt;code&gt;yield&lt;/code&gt; and calls it for each element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// iter.Seq[int] = func(yield func(int) bool)&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c"&gt;// the consumer stopped → we stop&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// caller side: the range syntax hides the whole mechanism&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// 0, 1, 2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you write &lt;code&gt;for i := range Count(3)&lt;/code&gt;, the compiler turns your loop body into a &lt;code&gt;yield&lt;/code&gt; closure that it passes to the iterator. Each &lt;code&gt;yield(i)&lt;/code&gt; runs one iteration of your loop. The return value of &lt;code&gt;yield&lt;/code&gt; is the key to everything: &lt;code&gt;true&lt;/code&gt; = keep going, &lt;code&gt;false&lt;/code&gt; = the consumer did a &lt;code&gt;break&lt;/code&gt; or a &lt;code&gt;return&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The yield contract: honor the false, always
&lt;/h2&gt;

&lt;p&gt;This is &lt;em&gt;the&lt;/em&gt; trap, the one behind my panic. When &lt;code&gt;yield&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt;, your iterator &lt;strong&gt;must&lt;/strong&gt; stop immediately and never call &lt;code&gt;yield&lt;/code&gt; again. If you keep going, the runtime panics to stop you from producing into a consumer that's no longer listening.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ❌ ignores yield's return → panic on the consumer's first break&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Bad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// we NEVER look at the returned bool&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// ✅ honors the contract&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Good&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule is mechanical: &lt;strong&gt;every call to &lt;code&gt;yield&lt;/code&gt; must be guarded by an &lt;code&gt;if !yield(...) { return }&lt;/code&gt;&lt;/strong&gt;. If you produce in several branches, each one must honor the contract. That's the price of the sugar syntax: the compiler can't check it for you, so it checks it at runtime — brutally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup when the consumer breaks
&lt;/h2&gt;

&lt;p&gt;An iterator that holds a resource — a file, a connection, a DB cursor — must release it &lt;em&gt;even if the consumer stops halfway&lt;/em&gt;. And since a &lt;code&gt;break&lt;/code&gt; on the caller side makes &lt;code&gt;yield&lt;/code&gt; return &lt;code&gt;false&lt;/code&gt; then exits your function, a simple &lt;code&gt;defer&lt;/code&gt; inside the iterator is enough — as long as you write it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// ✅ runs even if the consumer breaks&lt;/span&gt;

        &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bufio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewScanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c"&gt;// defer f.Close() fires here too&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// the defer closes the file whether the loop finishes or stops at line 2&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;Lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"big.log"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"FATAL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The classic mistake is opening the resource &lt;em&gt;outside&lt;/em&gt; the iterator function (when building the &lt;code&gt;iter.Seq&lt;/code&gt;), where the &lt;code&gt;defer&lt;/code&gt; no longer covers the break case. Open the resource &lt;strong&gt;inside&lt;/strong&gt; the closure, close it with &lt;code&gt;defer&lt;/code&gt; right after. Same cancellation discipline as &lt;a href="https://www.web-developpeur.com/en/blog/goroutine-leaks-golang" rel="noopener noreferrer"&gt;not leaking a goroutine&lt;/a&gt;: plan the early exit from the design.&lt;/p&gt;

&lt;h2&gt;
  
  
  iter.Pull: pulling on demand, and never forgetting stop()
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;range&lt;/code&gt; is a "push" model: the iterator pushes values. Sometimes you want to "pull": advance two sequences in parallel, or consume one value at a time on a decision. &lt;code&gt;iter.Pull&lt;/code&gt; converts a push iterator into two functions — &lt;code&gt;next()&lt;/code&gt; and &lt;code&gt;stop()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// ✅ MANDATORY — otherwise a leak&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="c"&gt;// early stop: stop() (via defer) releases the iterator&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;iter.Pull&lt;/code&gt; runs the iterator in a dedicated goroutine. If you don't consume to the end &lt;strong&gt;and&lt;/strong&gt; you don't call &lt;code&gt;stop()&lt;/code&gt;, that goroutine stays blocked forever: a pure &lt;a href="https://www.web-developpeur.com/en/blog/goroutine-leaks-golang" rel="noopener noreferrer"&gt;goroutine leak&lt;/a&gt;. The reflex is invariable: &lt;code&gt;next, stop := iter.Pull(...)&lt;/code&gt; immediately followed by &lt;code&gt;defer stop()&lt;/code&gt;. No exception.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to write an iterator
&lt;/h2&gt;

&lt;p&gt;The hype pushes you to turn everything into an &lt;code&gt;iter.Seq&lt;/code&gt;. That's a mistake. A range-over-func has a cost: indirect function calls, closures, inlining limits the compiler doesn't always cross. For a small collection already in memory, a slice is simpler &lt;em&gt;and&lt;/em&gt; faster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ❌ pointless: the data fits in memory, the iterator only adds overhead&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Names&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// ✅ return a slice: simpler to call, to test, to compose&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Names&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Iterators shine at what a slice does badly: &lt;strong&gt;lazy&lt;/strong&gt; sequences (computed on demand), &lt;strong&gt;infinite&lt;/strong&gt; ones (a counter, a stream), &lt;strong&gt;expensive&lt;/strong&gt; ones (API pagination, reading a big file line by line), or &lt;strong&gt;composed&lt;/strong&gt; ones (chained filters and maps without materializing the intermediates). If none of these apply, return a slice. It's the Go spirit of &lt;a href="https://www.web-developpeur.com/en/blog/interfaces-go-philosophie-accept-return" rel="noopener noreferrer"&gt;simplicity by default&lt;/a&gt;: the cleverest feature isn't always the right one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;range-over-func iterators are a real addition to the language, but they introduce a contract the compiler only enforces at runtime: honor &lt;code&gt;yield&lt;/code&gt;'s &lt;code&gt;false&lt;/code&gt;, close resources inside the closure, call &lt;code&gt;stop()&lt;/code&gt; on an &lt;code&gt;iter.Pull&lt;/code&gt;. Three rules, three traps. Once internalized, they become automatic.&lt;/p&gt;

&lt;p&gt;The question to ask before writing one isn't "can I?" but "is the sequence lazy, infinite, expensive or composed?". If yes, the iterator is elegant and justified. If not, you're adding complexity and overhead to return a slice — and a slice never panics.&lt;/p&gt;

</description>
      <category>go</category>
      <category>iterators</category>
      <category>rangeoverfunc</category>
    </item>
    <item>
      <title>Learning to Code With AI Without Your Brain Checking Out (2026)</title>
      <dc:creator>Odilon HUGONNOT</dc:creator>
      <pubDate>Tue, 14 Jul 2026 09:00:01 +0000</pubDate>
      <link>https://dev.to/ohugonnot/learning-to-code-with-ai-without-your-brain-checking-out-2026-30fn</link>
      <guid>https://dev.to/ohugonnot/learning-to-code-with-ai-without-your-brain-checking-out-2026-30fn</guid>
      <description>&lt;p&gt;In a 2024 study, a student finishes their programming exercise. They succeeded: 95% of the task done, the code runs. In the interview right after, they admit: "Honestly, I really don't understand code at all." They had just produced it. With AI, they finished everything, and learned nothing.&lt;/p&gt;

&lt;p&gt;That sentence captures the most insidious trap of our era. AI makes writing code so frictionless that you can sail through an entire learning experience without your brain encoding a single thing. You feel like you're learning, you produce, and nothing sticks. I built an entire course section (&lt;a href="https://www.web-developpeur.com/en/learning/" rel="noopener noreferrer"&gt;Learn with AI&lt;/a&gt;) around this problem, and the deeper I dig into the literature, the more I see how underestimated it is. Here is what the neuroscience actually says, and how to learn to code in the age of AI without getting fooled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cognitive debt, or why you remember nothing
&lt;/h2&gt;

&lt;p&gt;In 2025, Nataliya Kosmyna's team at the MIT Media Lab wired electrodes to the skulls of 54 students while they wrote essays. Three groups: brain only, search engine, and ChatGPT. The result is brutal. In the AI group, &lt;strong&gt;83% of participants could not quote a single sentence of what they had just written&lt;/strong&gt;. Not vaguely: nothing at all. The EEG showed collapsed brain connectivity, and the lowest sense of authorship over their own text.&lt;/p&gt;

&lt;p&gt;Researchers call this &lt;em&gt;cognitive debt&lt;/em&gt;, by analogy with technical debt. When you outsource your thinking to a machine, you save time now and pay later in the form of knowledge that never formed. The mechanism is simple and unforgiving: long-term memory is built through the effort of retrieval. If information arrives pre-chewed and no effort goes into producing it, the hippocampus consolidates nothing into the neocortex. The code passed before your eyes, never into your head.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The counterintuitive rule of learning: the smoother it feels in the moment, the less it stays afterward. Felt ease is not learning, it's just ease.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The illusion of competence
&lt;/h2&gt;

&lt;p&gt;The second danger is sneakier, because it disguises itself as success. In 2024, a study titled &lt;em&gt;The Widening Gap&lt;/em&gt; watched 21 novices solve a problem with GitHub Copilot. Completion rate: 95%, versus 65% in a comparable study without AI. On paper, a triumph. Except nine of the ten struggling students would never have finished without AI, and believed they understood the solution far more than they did.&lt;/p&gt;

&lt;p&gt;This is the &lt;em&gt;illusion of competence&lt;/em&gt;. Your brain confuses "I recognize this code, it looks right" with "I can produce this code." Recognizing is infinitely easier than generating, and AI constantly puts you in recognition mode. You read a solution, it seems obvious, you nod, you move on. The feeling of mastery is real. The mastery is not. And since you feel competent, you never feel the need to actually practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's worse for beginners
&lt;/h2&gt;

&lt;p&gt;The study's title, &lt;em&gt;the widening gap&lt;/em&gt;, is no accident. AI does not benefit everyone the same way. Already-solid developers use it to produce what they already knew they wanted, and crucially they &lt;strong&gt;reject&lt;/strong&gt; bad suggestions. Researchers call this &lt;em&gt;negative expertise&lt;/em&gt;: knowing what not to do, spotting a smelly suggestion at a glance.&lt;/p&gt;

&lt;p&gt;Beginners lack that filter. They accept Copilot's suggestions 34% of the time, versus 24% for the more comfortable ones, including when they are wrong. They dive into debugging dead ends, drifting further from the solution, convinced the AI must be right. The result: AI accelerates the strong and drowns the weak. The gap between those who already knew how to reason and the rest widens instead of closing.&lt;/p&gt;

&lt;p&gt;The lesson is harsh but clear: &lt;strong&gt;AI is an amplifier, not an equalizer&lt;/strong&gt;. It multiplies what you already know how to do. If you can judge code, it makes you formidable. If you can't yet, it gives you the illusion that learning is no longer worth the trouble.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 4 mechanisms to actually learn
&lt;/h2&gt;

&lt;p&gt;The good news is that the remedies are known, measured, and old. The science of learning calls them &lt;em&gt;desirable difficulties&lt;/em&gt; (Robert Bjork): deliberate frictions that make learning harder in the moment and far more durable afterward. Four matter most when learning to code with AI.&lt;/p&gt;

&lt;p&gt;Two learning paths: delegating to AI leads to fast forgetting, while active retrieval and effort lead to durable memory. Delegate to AI Actually learn AI writes the code you read, it looks clear You try first you retrieve from memory You copy, it works zero mental effort Effort, sometimes failure desirable difficulty Nothing encoded forgotten in 24h Memory consolidated it sticks&lt;/p&gt;

&lt;p&gt;Same time spent, opposite outcomes. The friction on the right is the price of memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Active retrieval
&lt;/h3&gt;

&lt;p&gt;Pulling a piece of information from your own memory strengthens it far more than rereading it. It's the best-established effect in all of cognitive psychology. Concretely: before asking the AI, try to answer yourself. Before running a snippet, predict what it will print. Close the tab and rewrite the function from memory. Every time you force your brain to produce instead of recognize, you carve it in.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Spaced repetition
&lt;/h3&gt;

&lt;p&gt;A concept reviewed once is forgotten. Reviewed three times at growing intervals, it settles for good. The counterintuitive part: you must &lt;em&gt;let forgetting begin&lt;/em&gt; before reviewing. Reviewing while it's all still fresh does nothing; reviewing as it fades is where consolidation happens. Revisiting a notion the next day, then three days later, then a week, beats any cramming.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The generation effect
&lt;/h3&gt;

&lt;p&gt;Attempting to solve a problem &lt;em&gt;before&lt;/em&gt; seeing the solution improves retention, even when the attempt fails. It's Manu Kapur's &lt;em&gt;productive failure&lt;/em&gt;. The failed effort primes the brain to encode the explanation that follows. This is the exact opposite of what AI does, serving you the solution before you've had time to struggle with it. Flip the reflex: struggle for five minutes, then ask.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Negative expertise
&lt;/h3&gt;

&lt;p&gt;Learn to say no to the AI's code. The core skill in the age of LLMs is no longer writing code, it's judging what you're handed. Read each suggestion asking "do I accept this, and why?". Hunt for the security flaw, the side effect, the forgotten edge case. Remember that a significant share of generated code contains vulnerabilities. The reviewer's reflex is what separates whoever drives the AI from whoever gets driven by it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I build my courses around this
&lt;/h2&gt;

&lt;p&gt;This is exactly the philosophy of &lt;a href="https://www.web-developpeur.com/en/learning/coder-avec-ia/" rel="noopener noreferrer"&gt;Coding with AI&lt;/a&gt; and the &lt;a href="https://www.web-developpeur.com/en/learning/projets/" rel="noopener noreferrer"&gt;applied projects&lt;/a&gt;: AI cranks out code fast, you read it slowly, you send back precise instructions. Each project shows the real prompts, the shaky first draft, then the review that hardens security, accessibility and contrast. We don't teach delegation, we teach critique. That's negative expertise turned into a habit.&lt;/p&gt;

&lt;p&gt;But rereading the research, I saw my own blind spots. My quizzes fire right after the lesson: that tests working memory, not durable memory. My examples often show the solution before the effort. I had no real spaced repetition. So I'm adding: a "predict before you run" in the editor, free-recall questions where you re-explain in your own words, the return of older notions across later lessons, and a teach-back after each exchange with the AI. In short, I'm deliberately injecting the frictions my own courses, too smooth, had sanded away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The irony of all this is that the tool meant to democratize learning to code can widen the gap between those who already know how to think and those still learning. AI won't turn you into a developer in your place, any more than a calculator makes you a mathematician. It amplifies a reasoning that must already exist.&lt;/p&gt;

&lt;p&gt;The real question was never "can AI write this code?". Of course it can. The question is: "after reading it, could I rewrite it alone?". As long as the answer is no, you haven't learned anything, you've only borrowed. And debt, one day, comes due.&lt;/p&gt;

</description>
      <category>learning</category>
      <category>ai</category>
      <category>neuroscience</category>
      <category>pedagogy</category>
    </item>
    <item>
      <title>I Rebuilt My Courses on the Science of Learning (2026)</title>
      <dc:creator>Odilon HUGONNOT</dc:creator>
      <pubDate>Mon, 13 Jul 2026 09:00:01 +0000</pubDate>
      <link>https://dev.to/ohugonnot/i-rebuilt-my-courses-on-the-science-of-learning-2026-2iic</link>
      <guid>https://dev.to/ohugonnot/i-rebuilt-my-courses-on-the-science-of-learning-2026-2iic</guid>
      <description>&lt;p&gt;I had already built the courses. Interactive, with a live code editor, quizzes, diagrams, bilingual. I was rather proud of them. Then I read an MIT EEG study on cognitive debt, and another on the widening gap between learners in the age of AI. They made me uneasy, because they described a trap my own courses might be falling into: &lt;strong&gt;giving the illusion of learning while anchoring nothing for the long term&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So I reworked everything. Here is what I changed, why, and how I rolled it out across 109 lessons without spending three months on it. It's a retrospective that's as pedagogical as it is technical. (For the science itself, I cover it in &lt;a href="https://www.web-developpeur.com/en/blog/apprendre-coder-ia-cerveau" rel="noopener noreferrer"&gt;a dedicated article&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem my courses couldn't see
&lt;/h2&gt;

&lt;p&gt;My lessons were &lt;em&gt;smooth&lt;/em&gt;. You read a clear explanation, watched a working example, ticked a multiple-choice quiz, moved on. Pleasant. And that's exactly the trap: the science of learning is clear, &lt;strong&gt;felt fluency is not learning&lt;/strong&gt;. The easier it feels in the moment, the less it stays.&lt;/p&gt;

&lt;p&gt;My quizzes came right after the theory: they tested working memory, not durable memory. My editors showed the solution before the learner had made the effort to find it. My multiple-choice was recognition (pick the right answer), not production (write it from memory). And nothing brought notions back over time. In short, I was sanding away every friction, when the &lt;em&gt;good&lt;/em&gt; frictions are precisely what carves things in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five mechanisms, five desirable difficulties
&lt;/h2&gt;

&lt;p&gt;Robert Bjork calls them &lt;em&gt;desirable difficulties&lt;/em&gt;: deliberate efforts that make learning harder in the moment and far more durable afterward. I implemented five, each wired into the right moment of a lesson.&lt;/p&gt;

&lt;p&gt;The five mechanisms added to lessons: predict before running, free recall, re-explain after the AI, accept or reject journal, and spaced review. 1. Predict before running 2. Free recall from memory 3. Re-explain after the AI 4. Accept / reject AI code 5. Review spaced repetition&lt;/p&gt;

&lt;p&gt;Each mechanism targets a precise moment: predict, produce, re-explain, judge, then come back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Predict before running.&lt;/strong&gt; Before running a snippet, the preview is blurred and the learner must write what they think it will print. That's the generation effect: attempting before seeing primes the brain to encode, even when you're wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Free recall.&lt;/strong&gt; At the end of a lesson, a question you answer &lt;em&gt;from memory&lt;/em&gt;, without looking, then self-assess honestly. Retrieving information carves it in far more than rereading it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Re-explaining after the AI.&lt;/strong&gt; After each box where the learner queries an AI, we ask them to re-explain in their own words. That closes the loop: checking you understood the answer, not just read it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The accept / reject journal.&lt;/strong&gt; We show a snippet produced by the AI, sometimes subtly flawed, sometimes fine. The learner decides and justifies, then compares with a senior dev's take. That's negative expertise: learning to say no to generated code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Spaced review.&lt;/strong&gt; Self-assessed notions return in a &lt;a href="https://www.web-developpeur.com/en/learning/reviser/" rel="noopener noreferrer"&gt;review hub&lt;/a&gt;, re-surfaced at growing intervals, right when you were about to forget them.&lt;/p&gt;

&lt;h2&gt;
  
  
  A spaced-repetition hub with no backend
&lt;/h2&gt;

&lt;p&gt;The biggest piece is the fifth. Real spaced repetition needs to remember each notion's state: when it was seen, its interval, its next due date. With no database and no user account, I put it all in the browser's &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The key was freezing a &lt;em&gt;data contract&lt;/em&gt;: each review card is &lt;strong&gt;self-contained&lt;/strong&gt;. When the learner self-assesses, we store not only the verdict, but the question and answer in both languages, the link to the lesson, and the scheduling state. The hub can then replay the card outside its original lesson, entirely client-side. The accepted trade-off: history stays on the device. For a free, no-signup path, that's the right compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Industrializing across 109 lessons with agents
&lt;/h2&gt;

&lt;p&gt;Adding these blocks by hand across 109 lessons, in two languages, writing content specific to each topic, was weeks of work. I first validated the components on &lt;strong&gt;one&lt;/strong&gt; pilot lesson, then orchestrated the rest with AI agents: one agent per lesson, each reading the pilot lesson as a reference, applying a relevance rubric, and writing content adapted to the topic.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The golden rule: don't slap all five mechanisms everywhere by reflex. Free recall almost always; a "predict" only if there's a runnable editor; a re-explanation only if there's an AI box; a journal only if a judgeable snippet exists within scope. Two excellent components beat four lukewarm ones.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The result surprised me with its contextual accuracy. On a Git lesson, the journal doesn't judge "code" but a &lt;em&gt;command&lt;/em&gt; or a &lt;em&gt;commit&lt;/em&gt;. On a SQL lesson, it flags a &lt;code&gt;DELETE&lt;/code&gt; without a &lt;code&gt;WHERE&lt;/code&gt;. On PHP, a concatenated query vulnerable to injection. Each agent adapted the snippet to the exact topic of its lesson. The irony wasn't lost on me: I used AI to industrialize courses that teach, precisely, not to delegate to it blindly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tests caught
&lt;/h2&gt;

&lt;p&gt;I verified everything in production with automated tests that really interact with the site: the prediction unlocking the preview, the answer revealing itself, the verdict persisting after a reload, French / English parity, zero console errors. In total, more than 230 green tests across all the courses.&lt;/p&gt;

&lt;p&gt;And they caught real traps, all of the same sneaky kind: a &lt;code&gt;hidden&lt;/code&gt; attribute overridden by a &lt;code&gt;display: flex&lt;/code&gt;, so a zone showing up too early. A detail invisible when reading the code, obvious in a test. An accessibility audit also revealed an unlabeled editor and contrasts that were too weak, fixed right away. The meta lesson: &lt;strong&gt;behavior tests say nothing about whether a color is readable&lt;/strong&gt;. You also have to look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The hardest part wasn't writing the components' code. It was accepting that my courses, which I thought were good, were too comfortable to actually teach. Perceived quality and real effectiveness are two different things, and only the second one counts.&lt;/p&gt;

&lt;p&gt;There remains the real question, the one no test settles: will people &lt;em&gt;come back&lt;/em&gt; to review? Spaced repetition is only worth it if you return. That's why the final brick isn't an algorithm, but a simple "to review" badge reminding you that a notion is waiting. The best learning engine is useless if you forget to start it.&lt;/p&gt;

</description>
      <category>learning</category>
      <category>pedagogy</category>
      <category>ai</category>
      <category>neuroscience</category>
    </item>
    <item>
      <title>Go Middleware Best Practices in Production (2026)</title>
      <dc:creator>Odilon HUGONNOT</dc:creator>
      <pubDate>Sun, 12 Jul 2026 09:00:02 +0000</pubDate>
      <link>https://dev.to/ohugonnot/go-middleware-best-practices-in-production-2026-1ekj</link>
      <guid>https://dev.to/ohugonnot/go-middleware-best-practices-in-production-2026-1ekj</guid>
      <description>&lt;p&gt;The service had run for six months without a hiccup. Then one morning: 2% of requests returning a 500 — no log, no trace, no panic surfaced. The kind of bug that never reproduces locally. Half a day later, the cause: a &lt;code&gt;panic&lt;/code&gt; in a handler, caught by a &lt;code&gt;recover&lt;/code&gt; middleware… placed &lt;em&gt;inside&lt;/em&gt; the logging middleware. The recover swallowed the panic &lt;strong&gt;after&lt;/strong&gt; the logger had already written its line — but &lt;strong&gt;before&lt;/strong&gt; the request ID was set. The result: a ghost 500, invisible in the dashboards.&lt;/p&gt;

&lt;p&gt;A Go middleware is ten trivial lines. A &lt;em&gt;chain&lt;/em&gt; of middleware in production is where the real problems hide: ordering, panic recovery, timeouts, &lt;code&gt;ResponseWriter&lt;/code&gt; wrapping, context propagation. Here's what 14 years of running APIs taught me about the middleware that holds up — and the kind that falls over.&lt;/p&gt;

&lt;h2&gt;
  
  
  A middleware is just a function that wraps another
&lt;/h2&gt;

&lt;p&gt;No magic, no framework required. A Go middleware is a function that takes an &lt;code&gt;http.Handler&lt;/code&gt; and returns another. The canonical pattern, the one everything else follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Middleware&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"dur"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Since&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything &lt;strong&gt;before&lt;/strong&gt; &lt;code&gt;next.ServeHTTP&lt;/code&gt; runs on the way in (on the request). Everything &lt;strong&gt;after&lt;/strong&gt; runs on the way out (on the response). That's the onion model: the request travels through each layer toward the handler, and the response climbs back out in reverse.&lt;/p&gt;

&lt;p&gt;The request descends through each middleware to the handler; the response climbs back out in reverse request response Recover (panic) Request ID + Tracing Logger Timeout (context) Auth + Rate limit Handler&lt;/p&gt;

&lt;p&gt;The onion model: declaration order = traversal order.&lt;/p&gt;

&lt;p&gt;To chain them without nesting by hand (&lt;code&gt;A(B(C(handler)))&lt;/code&gt; gets unreadable fast), a small helper does the job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mw&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Middleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// apply in reverse so mw[0] becomes the outermost layer&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// usage: Recover is the outermost layer, it wraps everything else&lt;/span&gt;
&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Recover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RequestID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the loop direction: we apply &lt;code&gt;mw&lt;/code&gt; from last to first so that &lt;code&gt;mw[0]&lt;/code&gt; stays the &lt;strong&gt;outermost&lt;/strong&gt; layer. Get the direction wrong and the whole order inverts — which is exactly the first trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Order isn't cosmetic, it's functional
&lt;/h2&gt;

&lt;p&gt;The most-asked question about Go middleware in production: &lt;em&gt;what order do I declare them in?&lt;/em&gt; The answer isn't a style convention, it's a matter of correctness. Three non-negotiable rules:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;Recover&lt;/code&gt; must be the outermost layer.&lt;/strong&gt; If it sits inside the logger, a panic in the logger itself isn't caught. If it sits inside request ID, the emergency 500 won't carry the correlation ID. Recover wraps &lt;em&gt;everything&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Request ID before the logger.&lt;/strong&gt; Otherwise your log line has no ID to correlate with the rest of the trace. Obvious when written down — yet it's the most common inversion I see in review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Auth and rate-limit closest to the handler, but after observability.&lt;/strong&gt; You want to log and trace even rejected requests (a spike of 401s or 429s is a signal). If auth sits above the logger, your rejections are invisible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ❌ BAD: recover inside, rejections invisible, ID missing from the 500&lt;/span&gt;
&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Recover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RequestID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// ✅ GOOD: recover outside, ID then log, auth near the handler&lt;/span&gt;
&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Recover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RequestID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RateLimit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule of thumb: &lt;strong&gt;from most defensive to most domain-specific&lt;/strong&gt;. What protects the server (recover) on top, what identifies and observes (ID, log, trace) next, what filters (timeout, auth, rate-limit) just before the business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recover: the middleware that keeps the whole process alive
&lt;/h2&gt;

&lt;p&gt;In Go, an unrecovered panic in a handler goroutine doesn't just kill the request — depending on the version and HTTP runtime, it can take down the whole server. The standard &lt;code&gt;net/http&lt;/code&gt; server recovers per-request panics by default, but it turns them into a silent dropped connection with no usable application log. An explicit &lt;code&gt;Recover&lt;/code&gt; is therefore essential:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Recover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;recover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"panic recovered"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"err"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"stack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{"error":"internal"}`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}()&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two real traps here. &lt;strong&gt;First&lt;/strong&gt;: never re-panic inside the &lt;code&gt;defer&lt;/code&gt;, or you end up with an unrecovered panic during recovery. &lt;strong&gt;Second&lt;/strong&gt;, subtler: if a downstream middleware has &lt;em&gt;already written&lt;/em&gt; a status code before the panic, your &lt;code&gt;w.WriteHeader(500)&lt;/code&gt; will emit a &lt;code&gt;superfluous response.WriteHeader call&lt;/code&gt; warning and be ignored. The client receives a truncated response. Handling that case is exactly why you need to know whether the response has already started — which brings us to wrapping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping http.ResponseWriter without breaking Flush or Hijack
&lt;/h2&gt;

&lt;p&gt;To log the status code or the response size, you have to intercept calls to &lt;code&gt;WriteHeader&lt;/code&gt; and &lt;code&gt;Write&lt;/code&gt;. The naive solution: a wrapper that remembers the code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;statusRecorder&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;  &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;written&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;statusRecorder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;statusRecorder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt; &lt;span class="c"&gt;// implicit Write = 200&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works… until the day you serve Server-Sent Events or streaming, and everything breaks. &lt;strong&gt;The classic wrapping trap&lt;/strong&gt;: by embedding &lt;code&gt;http.ResponseWriter&lt;/code&gt; in a struct, you &lt;em&gt;lose&lt;/em&gt; the optional interfaces the original writer implemented — &lt;code&gt;http.Flusher&lt;/code&gt;, &lt;code&gt;http.Hijacker&lt;/code&gt;, &lt;code&gt;http.Pusher&lt;/code&gt;. Your SSE handler type-asserts to &lt;code&gt;http.Flusher&lt;/code&gt;, fails, and streaming never flushes again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ✅ Re-expose Flush so you don't break streaming behind the wrapper&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;statusRecorder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flusher&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// same for Hijack if you serve WebSocket behind this middleware&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;statusRecorder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Hijack&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;bufio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hijacker&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hijack&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hijack not supported"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since Go 1.20, &lt;code&gt;http.ResponseController&lt;/code&gt; exists precisely to call &lt;code&gt;Flush&lt;/code&gt;/&lt;code&gt;SetWriteDeadline&lt;/code&gt; through nested wrappers without re-exposing each interface by hand. On a fresh codebase, use it. If you're maintaining an existing wrapper, re-expose at least &lt;code&gt;Flush&lt;/code&gt; — it's the case that breaks most often in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeouts: cut the request before it overflows
&lt;/h2&gt;

&lt;p&gt;A handler that calls a slow database or a third-party API with no timeout is a time bomb: under load, goroutines pile up, memory climbs, and the service ends up OOM-killed. The right reflex: a middleware that sets a deadline on the request &lt;code&gt;context&lt;/code&gt;, propagated to every downstream call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Middleware&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Careful: this middleware doesn't &lt;em&gt;kill&lt;/em&gt; the handler, it signals cancellation via &lt;code&gt;ctx.Done()&lt;/code&gt;. Your downstream code still has to honor the context — a &lt;code&gt;db.QueryContext(ctx, ...)&lt;/code&gt;, not a &lt;code&gt;db.Query(...)&lt;/code&gt;. A timeout on a context no one listens to is useless. It's the same discipline as &lt;a href="https://www.web-developpeur.com/en/blog/goroutine-leaks-golang" rel="noopener noreferrer"&gt;avoiding goroutine leaks&lt;/a&gt;: if nothing listens to &lt;code&gt;ctx.Done()&lt;/code&gt;, the goroutine stays blocked.&lt;/p&gt;

&lt;p&gt;Avoid the stdlib &lt;code&gt;http.TimeoutHandler&lt;/code&gt; for this specific need: it writes its own 503 response when the deadline expires, which conflicts with your status wrapper and your recover. A context timeout keeps you in control of the response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability: request ID, structured logs, and the context trap
&lt;/h2&gt;

&lt;p&gt;The request ID is the thread that ties a log line, a distributed trace, and a support ticket together. The middleware generates it (or reads it from the incoming header) and puts it in the context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ctxKey&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;requestIDKey&lt;/span&gt; &lt;span class="n"&gt;ctxKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"request_id"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RequestID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Request-ID"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;requestIDKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Request-ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two best practices many people miss. &lt;strong&gt;One&lt;/strong&gt;: the context key must be a &lt;em&gt;private&lt;/em&gt; type (&lt;code&gt;type ctxKey string&lt;/code&gt;), never a bare &lt;code&gt;string&lt;/code&gt; — otherwise two packages both using &lt;code&gt;"request_id"&lt;/code&gt; clobber each other. That's exactly the spirit of &lt;a href="https://www.web-developpeur.com/en/blog/interfaces-go-philosophie-accept-return" rel="noopener noreferrer"&gt;type rigor in Go&lt;/a&gt;: make the mistake impossible to compile. &lt;strong&gt;Two&lt;/strong&gt;: enrich the logger with the ID once, via a &lt;code&gt;slog.Logger&lt;/code&gt; stored in the context, rather than threading the ID through every log call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// in the Logger middleware, once you have the ID:&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;With&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"request_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RequestIDFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;loggerKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c"&gt;// everywhere downstream:&lt;/span&gt;
&lt;span class="n"&gt;LoggerFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rate-limiting follows the same middleware mechanics; if you're building a serious per-IP one with &lt;code&gt;golang.org/x/time/rate&lt;/code&gt;, I covered the full implementation in &lt;a href="https://www.web-developpeur.com/en/blog/rate-limiter-go-token-bucket" rel="noopener noreferrer"&gt;this dedicated token-bucket article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A middleware in isolation is a textbook exercise. A &lt;em&gt;chain&lt;/em&gt; of middleware in production is a matter of order and contracts: who wraps whom, who sees the panic first, who sets the context the others will read. None of these decisions is aesthetic — each one changes how the service behaves under load or during an incident.&lt;/p&gt;

&lt;p&gt;The test that doesn't lie: trigger a panic in a handler, in a simulated production run, and watch what lands in your logs. If you get a clean 500, with the request ID, the stack, and the correlated log line — your chain is sound. If you get a ghost 500 with no trace, you now know exactly which order to revisit.&lt;/p&gt;

</description>
      <category>go</category>
      <category>middleware</category>
      <category>http</category>
    </item>
    <item>
      <title>Build an MCP Server in Go (for Claude Code)</title>
      <dc:creator>Odilon HUGONNOT</dc:creator>
      <pubDate>Sat, 11 Jul 2026 09:00:03 +0000</pubDate>
      <link>https://dev.to/ohugonnot/build-an-mcp-server-in-go-for-claude-code-2o2i</link>
      <guid>https://dev.to/ohugonnot/build-an-mcp-server-in-go-for-claude-code-2o2i</guid>
      <description>&lt;p&gt;Claude Code can read your repo, run your CLI, query your database — as long as you give it the entry point. That entry point is an &lt;strong&gt;MCP&lt;/strong&gt; server (Model Context Protocol). The day I wanted Claude to query our internal billing API without me copy-pasting JSON responses by hand, I wrote a small MCP server. In Go, not Python — and in hindsight that was the right call, for one specific reason we'll get to.&lt;/p&gt;

&lt;p&gt;The official Go SDK (&lt;code&gt;github.com/modelcontextprotocol/go-sdk&lt;/code&gt;) matured in 2025. It's perfectly usable in production now, but the docs still center on the "hello world." Here's what actually matters once the server leaves your machine: the transport choice, typed tools, auth, and the design traps I walked straight into.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP in 30 seconds: a server that exposes tools to an LLM
&lt;/h2&gt;

&lt;p&gt;MCP is a client-server protocol. The &lt;strong&gt;client&lt;/strong&gt; (Claude Code, Claude Desktop, your own agent) connects to one or more &lt;strong&gt;servers&lt;/strong&gt;, each exposing three things: &lt;em&gt;tools&lt;/em&gt; (functions the model can call), &lt;em&gt;resources&lt;/em&gt; (data it can read), and &lt;em&gt;prompts&lt;/em&gt; (reusable templates). 90% of real-world use is tools.&lt;/p&gt;

&lt;p&gt;Claude Code (MCP client) talks to your Go MCP server over stdio or HTTP; the server calls your API or database Claude Code MCP client MCP server in Go Your API DB, services… stdio / HTTP Go calls The model decides to call a "tool" → the server runs it → returns the result&lt;/p&gt;

&lt;p&gt;The MCP server is the adapter between the LLM and your system.&lt;/p&gt;

&lt;p&gt;The key point: &lt;strong&gt;you don't write a prompt&lt;/strong&gt;. You declare tools with a name, a description and an input schema. The model reads those descriptions and decides on its own when to call them. The quality of your descriptions &lt;em&gt;is&lt;/em&gt; your interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The minimal server with the official SDK
&lt;/h2&gt;

&lt;p&gt;Three steps: create the server, register a tool with its typed handler, run it on a transport. The SDK infers the tool's JSON schema directly from your Go struct.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/modelcontextprotocol/go-sdk/mcp"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// The tool input: the SDK derives the JSON schema exposed to the model from it.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;InvoiceArgs&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ClientID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"client_id" jsonschema:"the client account ID"`&lt;/span&gt;
    &lt;span class="n"&gt;Year&lt;/span&gt;     &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="s"&gt;`json:"year"      jsonschema:"fiscal year, e.g. 2026"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getInvoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallToolRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="n"&gt;InvoiceArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallToolResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;billing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// your real code&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallToolResult&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextContent&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;()}},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Implementation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"billing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"v1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;"get_invoices"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"List a client's invoices for a given fiscal year."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;getInvoices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// stdio transport: Claude Code launches this binary as a subprocess&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdioTransport&lt;/span&gt;&lt;span class="p"&gt;{});&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the Claude Code side, you declare this server in the MCP config (command + arguments). At startup, Claude launches the binary, negotiates the protocol, and discovers &lt;code&gt;get_invoices&lt;/code&gt;. No prompt to write: the description is enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  stdio or streamable HTTP: the choice that drives everything else
&lt;/h2&gt;

&lt;p&gt;The SDK supports two transports, and this is &lt;strong&gt;the&lt;/strong&gt; architecture decision. They don't serve the same use case:&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;stdio&lt;/p&gt;

&lt;p&gt;streamable HTTP&lt;/p&gt;

&lt;p&gt;Execution&lt;/p&gt;

&lt;p&gt;local subprocess launched by the client&lt;/p&gt;

&lt;p&gt;standalone network service&lt;/p&gt;

&lt;p&gt;Clients&lt;/p&gt;

&lt;p&gt;one, local&lt;/p&gt;

&lt;p&gt;many, remote&lt;/p&gt;

&lt;p&gt;Auth&lt;/p&gt;

&lt;p&gt;inherited from the machine&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;your responsibility&lt;/strong&gt; (token, mTLS…)&lt;/p&gt;

&lt;p&gt;Typical use&lt;/p&gt;

&lt;p&gt;personal tool, dev, CLI&lt;/p&gt;

&lt;p&gt;team server, SaaS, shared prod&lt;/p&gt;

&lt;p&gt;The simple rule: &lt;strong&gt;stdio for a tool only you use on your machine, HTTP as soon as it's shared&lt;/strong&gt;. The classic trap is to prototype in stdio then want to expose it to the team without realizing you're moving from a "zero auth" model to a "network attack surface." The HTTP transport plugs in almost like a regular &lt;a href="https://www.web-developpeur.com/en/blog/go-middleware-best-practices-production" rel="noopener noreferrer"&gt;HTTP handler&lt;/a&gt; — which means all the middleware best practices (recover, timeout, auth, logs) apply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewStreamableHTTPHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// wrap it with the same middleware as any API&lt;/span&gt;
&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Recover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Typed tools: let Go's types do the work
&lt;/h2&gt;

&lt;p&gt;This is where Go beats Python for this particular job. The JSON schema exposed to the model is derived from your input struct. No hand-written schema, no drift between validation and docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ❌ manual schema: drifts from the code, validates nothing&lt;/span&gt;
&lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"search"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;InputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rawJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{"type":"object","properties":{"q":{"type":"string"}}}`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// ✅ typed struct: schema inferred, validation for free, one source of truth&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;SearchArgs&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Query&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"query" jsonschema:"full-text search query"`&lt;/span&gt;
    &lt;span class="n"&gt;Limit&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="s"&gt;`json:"limit" jsonschema:"max results, default 10"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the model sends arguments, the SDK validates them against the schema &lt;em&gt;before&lt;/em&gt; calling your handler. A &lt;code&gt;year&lt;/code&gt; sent as a string? Rejected before your code runs. It's exactly the Go philosophy of &lt;a href="https://www.web-developpeur.com/en/blog/interfaces-go-philosophie-accept-return" rel="noopener noreferrer"&gt;making invalid state impossible&lt;/a&gt;, applied at the boundary with the LLM — the least reliable layer of your system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auth and security: an MCP server is an attack surface
&lt;/h2&gt;

&lt;p&gt;An MCP server runs code &lt;em&gt;on a model's request&lt;/em&gt;, and that model is driven by a potentially adversarial prompt (prompt injection). Three rules I set for myself after the fact:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Least privilege per tool.&lt;/strong&gt; Don't expose &lt;code&gt;run_sql&lt;/code&gt; with write access "just in case." Expose &lt;code&gt;get_invoices(client_id, year)&lt;/code&gt; — bounded, read-only. Each tool is a capability granted to the model — treat it like a public API route.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Over HTTP, auth is non-negotiable.&lt;/strong&gt; A bearer token checked in a middleware before reaching the MCP server, like any API. An MCP server open on the network with no auth is self-service RCE.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The context is your cancellation thread.&lt;/strong&gt; If the client disconnects, &lt;code&gt;ctx&lt;/code&gt; is cancelled — propagate it down to your DB calls. An MCP handler that ignores &lt;code&gt;ctx.Done()&lt;/code&gt; is a &lt;a href="https://www.web-developpeur.com/en/blog/goroutine-leaks-golang" rel="noopener noreferrer"&gt;goroutine leak&lt;/a&gt; waiting to happen under load.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design mistakes I made
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tools too granular.&lt;/strong&gt; My first version exposed &lt;code&gt;get_client&lt;/code&gt;, &lt;code&gt;get_invoice&lt;/code&gt;, &lt;code&gt;get_line_item&lt;/code&gt; separately. The model chained five calls where a single well-designed &lt;code&gt;get_client_summary&lt;/code&gt; would do — and every call costs a round-trip and context tokens. Design your tools for &lt;em&gt;the model's task&lt;/em&gt;, not for your database schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responses too big.&lt;/strong&gt; Returning 2,000 lines of raw JSON saturates the context window and makes the model (and the bill) pay for nothing. Summarize, paginate, return readable Markdown instead of a dump. A tool result isn't a REST response: it's &lt;em&gt;reasoning material&lt;/em&gt; for a human-model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vague descriptions.&lt;/strong&gt; "search" says nothing. "Full-text search across invoices, returns up to &lt;code&gt;limit&lt;/code&gt; matches sorted by date" tells the model when and how to call it. Your descriptions are literally your tool's system prompt — treat them like copywriting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Writing an MCP server in Go isn't writing AI — it's writing a clean, typed, secure API whose only client happens to be a model. Everything you already know about good Go services applies: strict types at the boundary, propagated context, middleware, least privilege. MCP just adds a new category of client, the most unpredictable of them all.&lt;/p&gt;

&lt;p&gt;And that's exactly why Go wins here: facing a non-deterministic caller, you want the maximum number of guarantees at compile time. Python lets you write the server faster; Go lets you sleep at night when the model calls it in a way you never anticipated.&lt;/p&gt;

</description>
      <category>go</category>
      <category>mcp</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>Apprendre SQL : la méthode par la pratique (2026)</title>
      <dc:creator>Odilon HUGONNOT</dc:creator>
      <pubDate>Fri, 10 Jul 2026 09:00:02 +0000</pubDate>
      <link>https://dev.to/ohugonnot/apprendre-sql-la-methode-par-la-pratique-2026-1m38</link>
      <guid>https://dev.to/ohugonnot/apprendre-sql-la-methode-par-la-pratique-2026-1m38</guid>
      <description>&lt;p&gt;Le scénario revient tout le temps. Quelqu'un veut « apprendre SQL », ouvre un tuto de 400 pages qui démarre sur la théorie des bases relationnelles, la troisième forme normale et un diagramme entité-association grand comme une nappe. Trois jours plus tard, il n'a toujours pas écrit une seule requête, et il a abandonné.&lt;/p&gt;

&lt;p&gt;SQL ne s'apprend pas comme ça. C'est un langage qu'on apprend les mains sur le clavier, une requête à la fois, dans un ordre précis où chaque notion s'appuie sur la précédente. Voici la méthode que je conseille, après des années à l'utiliser et à voir des débutants accrocher ou décrocher.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pourquoi la théorie d'abord ne marche pas
&lt;/h2&gt;

&lt;p&gt;La modélisation relationnelle, la normalisation, les index : tout ça compte. Mais ce sont des réponses à des problèmes que le débutant n'a pas encore rencontrés. Apprendre la troisième forme normale avant d'avoir écrit un &lt;code&gt;SELECT&lt;/code&gt;, c'est comme apprendre les règles du hors-jeu avant d'avoir touché un ballon.&lt;/p&gt;

&lt;p&gt;La bonne approche est inverse : tu écris des requêtes sur de vraies données dès la première minute, tu obtiens un résultat, tu te trompes, tu corriges. La théorie vient ensuite, quand tu as un problème concret qu'elle résout. C'est plus motivant, et surtout ça ancre les notions parce qu'elles sont liées à une expérience, pas à un paragraphe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le bon ordre d'apprentissage
&lt;/h2&gt;

&lt;p&gt;SQL a une progression naturelle. Chaque étape réutilise la précédente, donc l'ordre n'est pas négociable : sauter une marche, c'est se retrouver bloqué deux marches plus haut sans comprendre pourquoi.&lt;/p&gt;

&lt;p&gt;Le parcours d'apprentissage SQL en six étapes : lire avec SELECT, filtrer et trier, agréger, relier les tables avec les jointures, modifier les données, puis concevoir et sécuriser. 1. Lire SELECT, FROM 2. Filtrer, trier WHERE, ORDER BY 3. Agréger GROUP BY, COUNT 4. Relier JOIN, clés 5. Modifier INSERT, UPDATE 6. Concevoir tables, sécurité&lt;/p&gt;

&lt;p&gt;Six étapes, chacune appuyée sur la précédente. On ne saute pas une marche.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Lire les données (SELECT).&lt;/strong&gt; Avant tout, savoir interroger. &lt;code&gt;SELECT colonne FROM table&lt;/code&gt; : tu récupères ce qui existe déjà. C'est la commande que tu écriras 80 % du temps, autant la maîtriser à fond.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Filtrer et trier (WHERE, ORDER BY).&lt;/strong&gt; On veut rarement toutes les lignes. &lt;code&gt;WHERE&lt;/code&gt; garde celles qui t'intéressent, &lt;code&gt;ORDER BY&lt;/code&gt; les range. Avec &lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;OR&lt;/code&gt;, &lt;code&gt;LIKE&lt;/code&gt;, &lt;code&gt;IN&lt;/code&gt;, tu cibles précisément.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Agréger (GROUP BY).&lt;/strong&gt; Compter, totaliser, faire des moyennes par groupe. C'est le moment où SQL devient puissant : « combien de commandes par client », « le chiffre d'affaires par mois ».&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Relier les tables (JOIN).&lt;/strong&gt; Les vraies bases ont plusieurs tables liées par des clés. Les jointures les rassemblent. C'est l'étape qui fait peur, et c'est précisément pour ça qu'il ne faut l'aborder qu'une fois les trois premières solides.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Modifier (INSERT, UPDATE, DELETE).&lt;/strong&gt; Jusqu'ici tu ne faisais que lire. Maintenant tu écris, modifies, supprimes, sans vider une table par accident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Concevoir et sécuriser.&lt;/strong&gt; En dernier : créer tes propres tables, choisir les types et les clés, et te protéger des injections SQL avec les requêtes préparées. La théorie arrive ici, quand elle répond enfin à des questions que tu te poses vraiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pratiquer sans rien installer
&lt;/h2&gt;

&lt;p&gt;Le frein classique, c'est l'installation : un serveur de base de données, un client, une config. Le débutant abandonne avant la première requête. La parade, c'est de pratiquer dans un environnement qui tourne déjà, dans le navigateur, avec des données prêtes.&lt;/p&gt;

&lt;p&gt;C'est exactement ce que j'ai construit dans le &lt;a href="https://dev.to/apprendre/sql/"&gt;cours SQL gratuit et interactif&lt;/a&gt; de ce site : chaque notion ci-dessus a sa leçon, avec des requêtes que tu exécutes directement dans la page sur une vraie base, des exercices et des quiz. Tu écris du SQL dès la première leçon, dans l'ordre qui marche.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;La meilleure façon d'apprendre SQL n'est pas de lire sur SQL : c'est d'écrire des requêtes, dans le bon ordre, sur des données qui te parlent. La théorie n'est pas inutile, elle est juste mal placée quand on la met en premier. Mets-la en dernier, après la pratique, et elle devient évidente au lieu d'être un mur.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>apprentissage</category>
      <category>basededonnees</category>
      <category>debutant</category>
    </item>
    <item>
      <title>Apprendre la POO en PHP : de la théorie au code (2026)</title>
      <dc:creator>Odilon HUGONNOT</dc:creator>
      <pubDate>Thu, 09 Jul 2026 09:00:02 +0000</pubDate>
      <link>https://dev.to/ohugonnot/apprendre-la-poo-en-php-de-la-theorie-au-code-2026-39fi</link>
      <guid>https://dev.to/ohugonnot/apprendre-la-poo-en-php-de-la-theorie-au-code-2026-39fi</guid>
      <description>&lt;p&gt;Tu connais déjà PHP. Tu écris des fonctions, tu boucles sur des tableaux, tu sors des pages qui marchent. Puis un jour tu ouvres un projet Symfony, ou juste le code d'un collègue, et tout est en classes, en interfaces, avec des &lt;code&gt;$this&lt;/code&gt; partout et des &lt;code&gt;use App\Service\Truc;&lt;/code&gt; en haut de chaque fichier. Tu comprends vaguement, mais tu ne saurais pas l'écrire toi-même proprement.&lt;/p&gt;

&lt;p&gt;C'est le moment de passer à la POO. Et la bonne nouvelle, c'est que tu n'as pas à tout réapprendre : la POO en PHP, c'est surtout une syntaxe précise posée sur une poignée de concepts. Si tu attaques cette syntaxe dans le bon ordre, après avoir compris les concepts, la transition est rapide. Si tu l'attaques dans le désordre, tu vas recopier des classes sans savoir pourquoi elles sont faites comme ça.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comprendre les concepts d'abord (sinon la syntaxe ne sert à rien)
&lt;/h2&gt;

&lt;p&gt;Première erreur classique : apprendre la POO &lt;em&gt;par&lt;/em&gt; PHP. On lit la doc des classes, on note la syntaxe du mot-clé &lt;code&gt;class&lt;/code&gt;, des &lt;code&gt;extends&lt;/code&gt;, des &lt;code&gt;interface&lt;/code&gt;, et on croit avoir compris la POO. En réalité on a juste mémorisé du vocabulaire PHP.&lt;/p&gt;

&lt;p&gt;La POO est d'abord une façon de penser, indépendante du langage. Un objet regroupe des données et le comportement qui agit dessus. L'encapsulation cache l'intérieur pour qu'on ne dépende que de l'extérieur. L'héritage et le polymorphisme permettent de traiter différentes choses de la même façon. Ces idées sont les mêmes en PHP, en Java, en Python. Tant qu'elles ne sont pas claires, la syntaxe PHP n'est qu'une suite de mots-clés que tu copies.&lt;/p&gt;

&lt;p&gt;Avant de toucher à la syntaxe PHP, je conseille donc de poser les concepts au propre, langage à part. C'est exactement l'objet du &lt;a href="https://dev.to/apprendre/poo/"&gt;cours de POO agnostique&lt;/a&gt; de ce site : il explique le pourquoi des objets, de l'encapsulation, de l'héritage et des interfaces sans s'attacher à un langage. Une fois ça en tête, PHP devient simplement la mise en oeuvre concrète, et le reste de cet article prend tout son sens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le bon ordre en PHP
&lt;/h2&gt;

&lt;p&gt;La POO en PHP a une progression naturelle, où chaque brique s'appuie sur la précédente. Une interface n'a aucun sens tant qu'on n'a pas compris une classe ; un trait résout un problème qu'on ne voit qu'après avoir buté sur l'héritage. Voici l'ordre qui évite de tourner en rond.&lt;/p&gt;

&lt;p&gt;Le parcours d'apprentissage de la POO en PHP en six étapes : classes et objets, visibilité et encapsulation, héritage et classes abstraites, interfaces et polymorphisme, traits, puis namespaces et autoloading PSR-4 avec Composer. 1. Classes &amp;amp; objets $this, propriétés typées 2. Visibilité encapsulation 3. Héritage classes abstraites 4. Interfaces polymorphisme 5. Traits réutilisation 6. Namespaces autoloading PSR-4&lt;/p&gt;

&lt;p&gt;Six étapes, chacune appuyée sur la précédente. On ne saute pas une marche.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Classes et objets ($this, propriétés typées)
&lt;/h3&gt;

&lt;p&gt;Le point de départ. Une classe est un moule, un objet est ce qu'on coule dedans. En PHP moderne, on type les propriétés et on initialise dans le constructeur, souvent avec la promotion de propriétés qui évite la répétition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Compte&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$titulaire&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nv"&gt;$solde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;crediter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nv"&gt;$montant&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;solde&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$montant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;solde&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;solde&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Compte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Odilon'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;crediter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;50.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;solde&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 150&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;$this&lt;/code&gt; désigne l'objet courant, celui sur lequel la méthode a été appelée. Tant que ce mot-clé n'est pas limpide, rien d'autre ne le sera : c'est lui qui relie le comportement aux données de l'instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Visibilité et encapsulation
&lt;/h3&gt;

&lt;p&gt;Tu as remarqué le &lt;code&gt;private&lt;/code&gt; au-dessus. C'est la visibilité : &lt;code&gt;public&lt;/code&gt; est accessible de partout, &lt;code&gt;private&lt;/code&gt; uniquement depuis la classe, &lt;code&gt;protected&lt;/code&gt; depuis la classe et ses enfants. Ce n'est pas une décoration. C'est ce qui garantit qu'on ne triche pas avec le solde de l'extérieur.&lt;/p&gt;

&lt;p&gt;L'idée concrète : si &lt;code&gt;$solde&lt;/code&gt; est &lt;code&gt;private&lt;/code&gt;, personne ne peut écrire &lt;code&gt;$c-&amp;gt;solde = -1000;&lt;/code&gt;. On passe forcément par &lt;code&gt;crediter()&lt;/code&gt; ou un futur &lt;code&gt;debiter()&lt;/code&gt;, qui peuvent valider. C'est l'encapsulation en pratique : la classe protège ses propres règles. Apprends à mettre &lt;code&gt;private&lt;/code&gt; par défaut et à n'exposer que ce qui doit l'être.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Héritage et classes abstraites
&lt;/h3&gt;

&lt;p&gt;Quand deux classes partagent du comportement, l'héritage permet à l'une d'étendre l'autre avec &lt;code&gt;extends&lt;/code&gt;. Une classe abstraite, elle, définit un squelette qu'on ne peut pas instancier seul : elle impose à ses enfants d'implémenter certaines méthodes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Notification&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$destinataire&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;envoyer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationEmail&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Notification&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;envoyer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// envoi réel de l'e-mail à $this-&amp;gt;destinataire&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attention au piège classique : on abuse vite de l'héritage pour partager du code. Si la relation n'est pas un vrai « est un » (un email &lt;em&gt;est une&lt;/em&gt; notification), il vaut souvent mieux une interface ou un trait. L'héritage profond se paie cher en maintenabilité.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Interfaces et polymorphisme
&lt;/h3&gt;

&lt;p&gt;Une interface est un contrat : une liste de méthodes qu'une classe s'engage à fournir, sans dire comment. C'est le coeur du polymorphisme : du code qui dépend d'une interface fonctionne avec n'importe quelle classe qui la respecte, sans savoir laquelle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;MoyenPaiement&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;payer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nv"&gt;$montant&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Carte&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;MoyenPaiement&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;payer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nv"&gt;$montant&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Paypal&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;MoyenPaiement&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;payer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nv"&gt;$montant&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;encaisser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MoyenPaiement&lt;/span&gt; &lt;span class="nv"&gt;$moyen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nv"&gt;$montant&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$moyen&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;payer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$montant&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// peu importe la classe concrète&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C'est l'étape qui transforme ta façon d'écrire du code. Tu cesses de dépendre de classes précises pour dépendre de contrats. C'est ce qui rend le code testable et remplaçable, et c'est exactement ce que fait l'injection de dépendances de Symfony sous le capot.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Traits
&lt;/h3&gt;

&lt;p&gt;PHP ne permet pas l'héritage multiple : une classe ne peut &lt;code&gt;extends&lt;/code&gt; qu'une seule autre. Le trait résout ça. C'est un bloc de méthodes qu'on injecte dans plusieurs classes sans relation d'héritage, avec &lt;code&gt;use&lt;/code&gt; à l'intérieur de la classe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kd"&gt;trait&lt;/span&gt; &lt;span class="nc"&gt;Horodatable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nc"&gt;\DateTimeImmutable&lt;/span&gt; &lt;span class="nv"&gt;$creeLe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;horodater&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;creeLe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\DateTimeImmutable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Horodatable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Commentaire&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Horodatable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le trait arrive en cinquième parce qu'il ne fait sens qu'une fois l'héritage compris : c'est précisément la réponse à ses limites. Utilisé avec mesure, il évite la duplication. Utilisé à tort et à travers, il devient un fourre-tout qui cache d'où vient chaque méthode.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Namespaces et autoloading (PSR-4, Composer)
&lt;/h3&gt;

&lt;p&gt;Dernière marche, et celle qui sépare le code jouet du code de projet réel. Un namespace range les classes pour éviter les collisions de noms ; l'autoloading PSR-4 charge automatiquement le bon fichier quand tu utilises une classe, sans aucun &lt;code&gt;require&lt;/code&gt; manuel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// fichier : src/Paiement/Carte.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Paiement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Carte&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;MoyenPaiement&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avec un &lt;code&gt;composer.json&lt;/code&gt; qui mappe le namespace au dossier, Composer génère l'autoloader :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"autoload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"psr-4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"App\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La convention PSR-4 est simple : le namespace suit l'arborescence des dossiers, une classe par fichier portant le même nom. Une fois ça en place, tu écris &lt;code&gt;use App\Paiement\Carte;&lt;/code&gt; et la classe est là, sans te soucier du chemin du fichier. C'est ce mécanisme qui fait tourner Symfony, Laravel et tout l'écosystème Composer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pratiquer avec du code exécutable
&lt;/h2&gt;

&lt;p&gt;Lire des classes ne suffit pas, pas plus que lire du SQL n'apprend à écrire des requêtes. La POO se comprend en la faisant tourner : modifier une visibilité et voir l'erreur apparaître, implémenter une interface et constater que le polymorphisme marche, casser un héritage pour comprendre à quoi sert &lt;code&gt;parent::&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;C'est ce que j'ai construit dans le &lt;a href="https://dev.to/apprendre/php-poo/"&gt;cours PHP orienté objet, avec du code exécutable&lt;/a&gt; de ce site : chaque étape ci-dessus a sa leçon, avec des exemples que tu exécutes directement dans la page, des exercices et des quiz. Tu écris des classes dès la première leçon, dans l'ordre qui marche. Si les bases de PHP elles-mêmes sont encore fragiles, commence par le &lt;a href="https://dev.to/apprendre/php/"&gt;cours PHP de base&lt;/a&gt; avant d'attaquer l'objet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Passer du PHP procédural à la POO n'est pas un changement de langage, c'est un changement de regard. Le piège n'est pas la syntaxe, qui est courte et régulière : c'est de l'apprendre avant d'avoir compris ce qu'elle exprime. Pose les concepts d'abord, déroule la syntaxe PHP dans l'ordre, et chaque mot-clé deviendra l'évidence d'une idée que tu as déjà comprise.&lt;/p&gt;

&lt;p&gt;Le jour où tu rouvriras ce projet Symfony, les &lt;code&gt;$this&lt;/code&gt;, les interfaces et les &lt;code&gt;use&lt;/code&gt; ne seront plus du bruit. Ce sera juste de la POO, écrite en PHP.&lt;/p&gt;

</description>
      <category>php</category>
      <category>poo</category>
      <category>programmationobjet</category>
      <category>classes</category>
    </item>
    <item>
      <title>Apprendre la POO : les concepts avant la syntaxe (2026)</title>
      <dc:creator>Odilon HUGONNOT</dc:creator>
      <pubDate>Wed, 08 Jul 2026 09:00:03 +0000</pubDate>
      <link>https://dev.to/ohugonnot/apprendre-la-poo-les-concepts-avant-la-syntaxe-2026-47el</link>
      <guid>https://dev.to/ohugonnot/apprendre-la-poo-les-concepts-avant-la-syntaxe-2026-47el</guid>
      <description>&lt;p&gt;J'ai vu le même développeur cent fois. Il sait écrire &lt;code&gt;class&lt;/code&gt;, il colle des accolades au bon endroit, il met des &lt;code&gt;private&lt;/code&gt; partout parce qu'un tuto a dit que c'était bien. Et pourtant, devant un vrai problème, il ne sait pas s'il faut une classe, une fonction, deux objets ou aucun. Il connaît la syntaxe par cœur et la POO pas du tout.&lt;/p&gt;

&lt;p&gt;C'est le piège classique de la programmation orientée objet : on l'apprend comme un chapitre de grammaire d'un langage, alors que c'est une façon de penser qui existe avant et au-dessus de tout langage. Voici comment apprendre la POO pour de bon, dans l'ordre qui fait que ça finit par cliquer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pourquoi apprendre la syntaxe d'abord est un piège
&lt;/h2&gt;

&lt;p&gt;La plupart des cours de POO commencent par : « voici comment on déclare une classe en Java ». Trois pages plus loin, tu sais écrire &lt;code&gt;public class Chien extends Animal&lt;/code&gt;, mais personne ne t'a expliqué &lt;em&gt;pourquoi&lt;/em&gt; tu voudrais une classe &lt;code&gt;Animal&lt;/code&gt;, ni quand l'héritage est une bonne idée plutôt qu'une catastrophe.&lt;/p&gt;

&lt;p&gt;Le résultat est un développeur qui produit du code orienté objet syntaxiquement correct et conceptuellement bancal : des classes qui ne sont que des sacs de fonctions, de l'héritage à cinq niveaux qui s'effondre au premier changement, des getters et setters sur tout par réflexe. La syntaxe était acquise, le raisonnement non.&lt;/p&gt;

&lt;p&gt;La POO, ce sont quelques idées : regrouper des données avec le comportement qui les manipule, cacher les détails, faire collaborer des objets. Ces idées sont les mêmes en Java, en Python, en PHP ou en C#. La syntaxe change, le concept non. Apprends le concept une fois, et n'importe quel langage objet devient une simple traduction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Les concepts dans le bon ordre
&lt;/h2&gt;

&lt;p&gt;La POO a une progression naturelle. Chaque notion s'appuie sur la précédente : essayer de comprendre le polymorphisme sans avoir digéré ce qu'est un objet, c'est se condamner à réciter une définition sans jamais la saisir.&lt;/p&gt;

&lt;p&gt;Le parcours d'apprentissage de la POO en six étapes : comprendre pourquoi la POO existe, puis l'objet et la classe, l'encapsulation, l'héritage, la composition, et enfin le polymorphisme. 1. Pourquoi la POO le problème résolu 2. Objet &amp;amp; classe données + comportement 3. Encapsulation cacher les détails 4. Héritage réutiliser, spécialiser 5. Composition assembler des objets 6. Polymorphisme un appel, des formes&lt;/p&gt;

&lt;p&gt;Six concepts, chacun appuyé sur le précédent. On ne saute pas une marche.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Pourquoi la POO existe.&lt;/strong&gt; Avant tout, comprendre le problème qu'elle résout. Quand un programme grossit, les données et les fonctions qui les manipulent se dispersent partout, et tout changement devient un champ de mines. La POO range les deux ensemble. Sans cette motivation, le reste n'est qu'un rituel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. L'objet et la classe.&lt;/strong&gt; Un objet, c'est des données plus le comportement qui va avec, dans une même boîte. Une classe, c'est le moule qui fabrique ces boîtes. C'est la brique de base : tant qu'elle n'est pas limpide, rien au-dessus ne tiendra.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. L'encapsulation.&lt;/strong&gt; Cacher l'intérieur, n'exposer que ce qui est nécessaire. L'extérieur appelle des méthodes sans savoir comment c'est rangé dedans. C'est ce qui permet de changer l'implémentation sans casser le reste du code, et c'est le vrai intérêt de la POO, pas &lt;code&gt;private&lt;/code&gt; mis par superstition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. L'héritage.&lt;/strong&gt; Une classe peut en étendre une autre pour réutiliser et spécialiser. C'est la notion la plus enseignée et la plus mal utilisée. À aborder seulement une fois l'encapsulation solide, parce que l'héritage mal posé casse justement l'encapsulation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. La composition.&lt;/strong&gt; Plutôt que d'hériter, on assemble : un objet en contient d'autres et délègue. C'est souvent plus souple que l'héritage, et c'est le contrepoison à la sur-ingénierie. La comprendre après l'héritage permet de voir quand chacune est le bon choix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Le polymorphisme.&lt;/strong&gt; Un même appel, des comportements différents selon l'objet réel derrière. C'est le sommet : il s'appuie sur l'objet, l'encapsulation et l'héritage ou les interfaces. Quand ça clique, la POO entière prend enfin son sens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le piège du « modéliser le monde réel »
&lt;/h2&gt;

&lt;p&gt;On répète aux débutants que la POO sert à « modéliser le monde réel » : un &lt;code&gt;Chien&lt;/code&gt; hérite d'&lt;code&gt;Animal&lt;/code&gt;, une &lt;code&gt;Voiture&lt;/code&gt; a quatre &lt;code&gt;Roue&lt;/code&gt;. C'est une analogie sympathique pour démarrer, et un guide désastreux pour concevoir.&lt;/p&gt;

&lt;p&gt;Le monde réel est plein de hiérarchies qui s'effondrent dès qu'on les code. Le manchot est un oiseau qui ne vole pas, l'autruche court, la chauve-souris vole sans être un oiseau. Modéliser fidèlement le réel produit des arbres d'héritage tordus que la moindre exception fait exploser. Le code ne modélise pas le monde, il résout un problème : tu ne représentes que ce dont ton programme a besoin, rien de plus.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Préfère la composition à l'héritage. Demande-toi « X possède un Y » plutôt que « X est un Y ». Une voiture &lt;em&gt;a&lt;/em&gt; un moteur, elle n'&lt;em&gt;est&lt;/em&gt; pas un moteur, et c'est exactement pour ça que la composition tient là où l'héritage casse.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La sur-ingénierie naît presque toujours de cette envie de tout modéliser : des hiérarchies de classes pour des cas qui n'arriveront jamais, des abstractions « au cas où ». La POO bien faite est sobre. On ajoute une classe quand un besoin concret l'exige, pas parce que le monde réel a une catégorie de plus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pratiquer les concepts
&lt;/h2&gt;

&lt;p&gt;Lire sur la POO ne suffit pas, pas plus que lire sur le vélo n'apprend à pédaler. Il faut écrire des objets, se tromper dans une hiérarchie, sentir une classe devenir ingérable et la refactoriser en composition. C'est là que les concepts passent de la définition à l'intuition.&lt;/p&gt;

&lt;p&gt;C'est pour ça que j'ai construit un &lt;a href="https://dev.to/apprendre/poo/"&gt;cours POO gratuit et agnostique du langage&lt;/a&gt; sur ce site : il prend chaque concept ci-dessus dans cet ordre, avec des schémas, des exemples et des exercices, sans te noyer dans la syntaxe d'un langage en particulier. Une fois les idées en place, tu enchaînes avec le cours &lt;a href="https://dev.to/apprendre/php-poo/"&gt;PHP orienté objet&lt;/a&gt; pour les voir s'incarner dans un vrai langage, classes, interfaces et traits compris.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;La meilleure façon d'apprendre la POO n'est pas de mémoriser la syntaxe &lt;code&gt;class&lt;/code&gt; d'un langage : c'est de comprendre les concepts dans l'ordre, puis de les traduire dans le langage de ton choix. La syntaxe s'apprend en un après-midi ; le raisonnement objet, c'est lui qui distingue le développeur qui écrit des classes de celui qui conçoit avec.&lt;/p&gt;

&lt;p&gt;Et le jour où tu te surprends à choisir la composition plutôt que l'héritage sans même y penser, à hésiter avant d'ajouter une classe « au cas où », c'est gagné. Tu n'écris plus de la POO, tu penses en objets.&lt;/p&gt;

</description>
      <category>poo</category>
      <category>programmationobjet</category>
      <category>apprentissage</category>
      <category>concepts</category>
    </item>
  </channel>
</rss>
