<?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: Supriya</title>
    <description>The latest articles on DEV Community by Supriya (@supriya-kotturu).</description>
    <link>https://dev.to/supriya-kotturu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F188124%2F5a8da0db-edcd-462f-ad0a-922321f5bb85.png</url>
      <title>DEV Community: Supriya</title>
      <link>https://dev.to/supriya-kotturu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/supriya-kotturu"/>
    <language>en</language>
    <item>
      <title>Go's context: one goroutine cancels, all goroutines stop</title>
      <dc:creator>Supriya</dc:creator>
      <pubDate>Mon, 13 Apr 2026 23:10:00 +0000</pubDate>
      <link>https://dev.to/supriya-kotturu/gos-context-one-goroutine-cancels-all-goroutines-stop-1gok</link>
      <guid>https://dev.to/supriya-kotturu/gos-context-one-goroutine-cancels-all-goroutines-stop-1gok</guid>
      <description>&lt;p&gt;One goroutine calls &lt;code&gt;cancel()&lt;/code&gt;. Every goroutine stops. Without any of them talking to each other.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/supriya-kotturu/gos-context-isnt-reacts-context-think-abortsignal-1h2e?ref=dailydev"&gt;previous post&lt;/a&gt;, we used a single goroutine checking for cancellation before each store run. One goroutine, one &lt;code&gt;ctx&lt;/code&gt;, simple.&lt;/p&gt;

&lt;p&gt;Now imagine multiple goroutines running concurrently, each doing a different piece of work, each derived from the same parent context.&lt;/p&gt;

&lt;p&gt;Think of it like a family group chat. When you post "party's off," everyone in the group sees it: dad, siblings, whoever dad looped in without telling you, even the siblings who are always online but never respond. You don't need to know who's in there. If they're in the group, they get the message.&lt;/p&gt;

&lt;p&gt;That's the Go context tree. One &lt;code&gt;cancel()&lt;/code&gt; call, everyone stops, however deep the tree goes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before we go further: what does &lt;code&gt;cancel()&lt;/code&gt; actually do?
&lt;/h2&gt;

&lt;p&gt;When you call &lt;code&gt;context.WithCancel&lt;/code&gt;, Go gives you two things: a context and a cancel function.&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;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;WithCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parentCtx&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;ctx&lt;/code&gt; is what you pass around. It carries a &lt;code&gt;ctx.Done()&lt;/code&gt; method that returns a channel goroutines can listen on. &lt;code&gt;cancel&lt;/code&gt; is the function that closes that channel. You call it when you want everything to stop.&lt;/p&gt;

&lt;p&gt;Reading from &lt;code&gt;ctx.Done()&lt;/code&gt; blocks until &lt;code&gt;cancel()&lt;/code&gt; is called. At that point the channel closes and all readers unblock simultaneously. Not a broadcast message, not a loop over goroutines. Just a channel closing.&lt;/p&gt;

&lt;p&gt;Always pair &lt;code&gt;context.WithCancel&lt;/code&gt; with a &lt;code&gt;defer cancel()&lt;/code&gt; on the very next line. Even if nothing goes wrong, forgetting it means the context and its internal resources are never released until the parent is cancelled — a silent resource leak.&lt;/p&gt;

&lt;p&gt;If you need to communicate why cancellation happened, Go 1.20+ offers &lt;code&gt;context.WithCancelCause&lt;/code&gt; — it lets you attach an error to the cancellation, which consumers can retrieve with &lt;code&gt;context.Cause(ctx)&lt;/code&gt;. For most cases, &lt;code&gt;WithCancel&lt;/code&gt; is enough, but it's worth knowing the option exists.&lt;/p&gt;

&lt;p&gt;With cancellation covered, here's how goroutines actually listen for it. Goroutines opt in by listening on &lt;code&gt;ctx.Done()&lt;/code&gt; in a &lt;code&gt;select&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="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// channel closed — clean up and stop&lt;/span&gt;
    &lt;span class="k"&gt;return&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;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// context.Canceled&lt;/span&gt;
&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// channel still open — keep going&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;New to Go channels and select? &lt;a href="https://go.dev/tour/concurrency/2" rel="noopener noreferrer"&gt;A Tour of Go covers them here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Child contexts work the same way. When you call &lt;code&gt;context.WithCancel(parentCtx)&lt;/code&gt;, the child's &lt;code&gt;Done()&lt;/code&gt; channel is linked to the parent's. Cancel the parent, and the child's channel closes too, automatically, without any extra wiring.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;context.Background()&lt;/code&gt; and &lt;code&gt;context.TODO()&lt;/code&gt; create brand new root contexts. They can't be cancelled and &lt;code&gt;ctx.Done()&lt;/code&gt; returns &lt;code&gt;nil&lt;/code&gt; on both. They're meant to be the starting point of a context tree, not a node in one. If you need cancellation, always derive from an existing context using context.WithCancel or context.WithTimeout.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This pattern shows up in Go's standard library. When an HTTP client disconnects, &lt;code&gt;net/http&lt;/code&gt; cancels &lt;code&gt;r.Context()&lt;/code&gt; automatically. Any goroutine you spawned with that context stops without you doing anything.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A word of caution:&lt;/em&gt; passing &lt;code&gt;cancel&lt;/code&gt; into goroutines is powerful but should be done deliberately. In production code, it's good practice to keep &lt;code&gt;cancel&lt;/code&gt; scoped as tightly as possible — ideally only the goroutine or function that &lt;em&gt;owns&lt;/em&gt; the work should be able to stop it. If many goroutines all hold &lt;code&gt;cancel&lt;/code&gt;, it becomes harder to reason about what triggered a cancellation and when.&lt;/p&gt;

&lt;p&gt;That's the mechanism the context tree runs on. Let's see it with the party supplies story — four functions, one shared 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="c"&gt;// main — hosts the party, owns the root context&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="c"&gt;// watchRSVPs — checks if enough people are coming, calls cancel() if not&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;watchRSVPs&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;cancel&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;CancelFunc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;guests&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// coordinatePickup — sends goroutines out to each store&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;coordinatePickup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parentCtx&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="c"&gt;// pickup — heads to one store, checks phone before each trip&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;pickup&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;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's walk through each one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;main()&lt;/code&gt;&lt;/strong&gt; — the host. Creates the root context (the family group), spawns &lt;code&gt;watchRSVPs&lt;/code&gt; to watch RSVPs concurrently, then blocks on &lt;code&gt;coordinatePickup&lt;/code&gt;. When &lt;code&gt;coordinatePickup&lt;/code&gt; returns, &lt;code&gt;defer cancel()&lt;/code&gt; fires automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;guestList&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Carol"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Dave"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Eve"&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;WithCancel&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="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="c"&gt;// check RSVPs concurrently — calls cancel() if not enough yes&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;watchRSVPs&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;guestList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// send goroutines to each store — blocks until all finish&lt;/span&gt;
    &lt;span class="n"&gt;coordinatePickup&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;watchRSVPs&lt;/code&gt;&lt;/strong&gt; — the RSVP watcher. It holds &lt;code&gt;cancel()&lt;/code&gt; and checks whether enough people are coming. It also receives &lt;code&gt;ctx&lt;/code&gt; and listens on &lt;code&gt;ctx.Done()&lt;/code&gt; inside the loop — because even the goroutine that owns &lt;code&gt;cancel()&lt;/code&gt; should be stoppable. If &lt;code&gt;coordinatePickup&lt;/code&gt; finishes before all RSVPs come in, the context closes and &lt;code&gt;watchRSVPs&lt;/code&gt; returns cleanly instead of blocking forever.&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;watchRSVPs&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;cancel&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;CancelFunc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;guests&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="n"&gt;yesCount&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;guest&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;guests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// check phone before calling the next guest&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c"&gt;// supplies already handled — no need to keep checking&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;getResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"yes"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;yesCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;yesCount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guests&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="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// party's off — ctx.Done() closes for all goroutines&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;In the previous post, the condition check happened in &lt;code&gt;main&lt;/code&gt; before spawning the goroutine. Here both run concurrently — the pickup is already in motion while RSVPs are still coming in. Any goroutine holding &lt;code&gt;cancel()&lt;/code&gt; can pull the plug, at any point, from anywhere in the tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;coordinatePickup&lt;/code&gt;&lt;/strong&gt; — the delegator. Receives the parent context (the family group) and adds a child context for each item. Each goroutine joins the same family group. When the group gets the cancellation, they all see it. &lt;code&gt;wg.Wait()&lt;/code&gt; blocks until all pickups finish.&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;coordinatePickup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parentCtx&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="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;supplies&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"cake"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"https://api.bakery.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"plates"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"https://api.costco.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"balloons"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://api.partycity.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&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;supplies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;childCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;childCancel&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;WithCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parentCtx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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;cancel&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;CancelFunc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&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;defer&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="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="c"&gt;// clean up this goroutine's context when done&lt;/span&gt;

            &lt;span class="n"&gt;pickup&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;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;childCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;childCancel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&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;em&gt;New to sync.WaitGroup? &lt;a href="https://pkg.go.dev/sync#WaitGroup" rel="noopener noreferrer"&gt;Check out the official docs here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You might wonder: if cancelling the parent already stops all goroutines, why create a child context per goroutine at all? The parent handles fan-out cancellation — one cancel, everyone stops. But the child context lets you cancel &lt;em&gt;one&lt;/em&gt; goroutine individually without affecting its siblings. If the cake pickup fails and you want to abort just that one without cancelling the plates and balloons, you call that goroutine's own &lt;code&gt;childCancel()&lt;/code&gt;. The parent context is the group chat. The child context is a private message to one person.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;pickup&lt;/code&gt;&lt;/strong&gt; — one sibling, one store. Checks the family group (&lt;code&gt;ctx.Done()&lt;/code&gt;) before heading out. If the party's already been cancelled, the channel is closed and it stops immediately.&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;pickup&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;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cancelled — skipping %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&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;fetchFromStore&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;store&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;Here's what they look like together:&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;// main — hosts the party, owns the root context&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;guestList&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Carol"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Dave"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Eve"&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;WithCancel&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="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="c"&gt;// check RSVPs concurrently — calls cancel() if not enough yes&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;watchRSVPs&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;guestList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// send goroutines to each store — blocks until all finish&lt;/span&gt;
    &lt;span class="n"&gt;coordinatePickup&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="c"&gt;// watchRSVPs — checks if enough people are coming, calls cancel() if not&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;watchRSVPs&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;cancel&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;CancelFunc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;guests&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="n"&gt;yesCount&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;guest&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;guests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;getResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"yes"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;yesCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;yesCount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guests&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="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// closes ctx.Done() for all goroutines&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// coordinatePickup — sends goroutines out to each store&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;coordinatePickup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parentCtx&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="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;supplies&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"cake"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"https://api.bakery.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"plates"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"https://api.costco.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"balloons"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://api.partycity.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&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;supplies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;childCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;childCancel&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;WithCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parentCtx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// derived context&lt;/span&gt;

        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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;cancel&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;CancelFunc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&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;defer&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="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;pickup&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;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;childCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;childCancel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// pickup — one sibling, one store, checks the group before heading out&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;pickup&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;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// cancelled — stop before the next store&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cancelled — skipping %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&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;fetchFromStore&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;store&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;No goroutine was cancelled individually. One &lt;code&gt;cancel()&lt;/code&gt; call on the parent, everything stops. That's the context tree.&lt;/p&gt;

&lt;p&gt;One thing omitted here for clarity: error handling. &lt;code&gt;coordinatePickup&lt;/code&gt; spawns goroutines and discards their return values. In real code, you'd want to capture errors — the idiomatic tool for this is &lt;a href="https://pkg.go.dev/golang.org/x/sync/errgroup" rel="noopener noreferrer"&gt;&lt;code&gt;errgroup&lt;/code&gt;&lt;/a&gt; from &lt;code&gt;golang.org/x/sync&lt;/code&gt;. It wraps &lt;code&gt;sync.WaitGroup&lt;/code&gt;, collects the first non-nil error, and cancels the shared context automatically. Worth reaching for once this pattern feels familiar.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake that breaks the tree: goroutine leaks
&lt;/h2&gt;

&lt;p&gt;I made this mistake myself. I created new contexts mid-flow instead of deriving from the parent, and the first few runs looked fine. But each server restart... the memory climbed a little higher. By the time I opened Task Manager it was already tanking.&lt;/p&gt;

&lt;p&gt;There's a version of &lt;code&gt;coordinatePickup&lt;/code&gt; that looks almost identical but breaks everything:&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;// ❌ Wrong — creating a brand new context instead of deriving from parent&lt;/span&gt;
&lt;span class="n"&gt;childCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;childCancel&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;WithCancel&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="c"&gt;// 🚨&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;vs.&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;// ✅ Correct — deriving from the parent context&lt;/span&gt;
&lt;span class="n"&gt;childCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;childCancel&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;WithCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parentCtx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One character difference in the argument. Completely different behavior.&lt;/p&gt;

&lt;p&gt;Imagine dad decided not to delegate to your siblings. Instead he called your cousins. He created a separate "chore group" with just them. Your cousins aren't in the family group. They don't have the family group context.&lt;/p&gt;

&lt;p&gt;You text the family group "party's off." Everyone in the family group stops. But the cousins' chore group never gets the message. It's completely disconnected. Cousins keep going. Buying supplies for a party that isn't happening.&lt;/p&gt;

&lt;p&gt;That's a goroutine leak. Work continuing that nobody asked for anymore, consuming resources with no way to stop it from the outside.&lt;/p&gt;

&lt;p&gt;When you pass &lt;code&gt;context.Background()&lt;/code&gt;, you're creating a brand new root, a separate group with no connection to the parent. Your &lt;code&gt;cancel()&lt;/code&gt; call never reaches it. The channel it would close is in a completely different context tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule: always pass context down. Never create a new one mid-flow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you ever see a wild goroutine running and eating up resources on your machine with no clear reason why, look for a &lt;code&gt;context.Background()&lt;/code&gt; or &lt;code&gt;context.TODO()&lt;/code&gt; buried inside a function that should have been receiving and passing a parent context. That's almost always the culprit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same context, different roles
&lt;/h2&gt;

&lt;p&gt;The key insight across both posts: &lt;code&gt;watchRSVPs&lt;/code&gt; holds &lt;code&gt;cancel()&lt;/code&gt; and monitors a condition, pulling the plug when it's met. &lt;code&gt;coordinatePickup&lt;/code&gt; holds the parent &lt;code&gt;ctx&lt;/code&gt; and passes it down to child goroutines. The &lt;code&gt;pickup&lt;/code&gt; goroutines hold child &lt;code&gt;ctx&lt;/code&gt;, derived from the parent, cancelled automatically when the parent is cancelled. None of them need to know about each other. &lt;code&gt;ctx&lt;/code&gt; and &lt;code&gt;cancel&lt;/code&gt; are the only wire between them.&lt;/p&gt;

&lt;p&gt;That separation of concerns is what makes Go's context composable. The worker doesn't need to know who cancelled it or why. It just listens on &lt;code&gt;ctx.Done()&lt;/code&gt; and cleans up when the channel closes.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;New to Go's context? Read &lt;a href="https://dev.to/supriya-kotturu/gos-context-isnt-reacts-context-think-abortsignal-1h2e?ref=dailydev"&gt;Go's context isn't React's Context&lt;/a&gt; first — that's where we build the foundation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What Go misconceptions have cost you the most time? Drop them in the comments — I'd genuinely love to hear them.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Keep Up With My Go Journey
&lt;/h3&gt;

&lt;p&gt;I'm documenting my transition from frontend to backend in public. If you're on a similar path, or just enjoy watching someone learn and fail publicly, follow along:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linkedin.com/in/supriya-kotturu" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
      <category>backend</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>Go's context isn't React's Context. Think AbortSignal.</title>
      <dc:creator>Supriya</dc:creator>
      <pubDate>Mon, 06 Apr 2026 23:00:00 +0000</pubDate>
      <link>https://dev.to/supriya-kotturu/gos-context-isnt-reacts-context-think-abortsignal-1h2e</link>
      <guid>https://dev.to/supriya-kotturu/gos-context-isnt-reacts-context-think-abortsignal-1h2e</guid>
      <description>&lt;p&gt;It was my first time building a server with go and I was figuring out stuff. I've heard about context, but never spent too much time on it. I was too eager to start using...and that's what I did.&lt;/p&gt;

&lt;p&gt;I stored all the variables I wanted in context as if it's a HUGE object and kept passing it around the functions. It worked! But it was too late for me to realize that it wasn't how context needs to be used.&lt;/p&gt;

&lt;p&gt;It looked something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ❌ What I tried — treating ctx like a React context store&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;Background&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;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dbConn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c"&gt;// storing a DB connection&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;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// storing app config&lt;/span&gt;

&lt;span class="c"&gt;// Then passing ctx everywhere and extracting values like this:&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&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;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dbConn"&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;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;We all do have bad code days.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;This compiles. It runs. And it's wrong: extracting bunnies from a magic hat. The values are in there, but you have no idea what's inside until you reach in.&lt;/p&gt;

&lt;p&gt;And unlike React context, which is typed, Go's context values resolve to &lt;code&gt;any&lt;/code&gt; (Go's empty interface). No compiler help. The type assertion happens at runtime:&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;// ❌ This compiles fine — but panics at runtime if the value is missing,&lt;/span&gt;
&lt;span class="c"&gt;// the wrong type, or stored under a slightly different key&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&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;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dbConn"&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;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're coming from TypeScript, this should set off alarm bells. You've just moved a compile-time guarantee into a runtime panic. Compare that to an explicit parameter. The compiler knows exactly what it is, and can't be passed the wrong thing:&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;// ✅ Type is in the signature — compiler enforces it, no surprises&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;GetUser&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;db&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userID&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="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;So if context isn't for sharing state, what is it actually for?&lt;/p&gt;

&lt;p&gt;Two things. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Carrying request-scoped values
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;requestID&lt;/code&gt;, a JWT token, the authenticated &lt;code&gt;userID&lt;/code&gt;: values whose lifespan matches the request's lifespan. When the request ends, they're gone.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;userID&lt;/code&gt; is the one that divides devs. If you're only logging it, "request from user abc123", context is fine. Its lifespan matches the request. But if your handler is calling &lt;code&gt;ctx.Value("userID")&lt;/code&gt; to fetch user data or make authorization decisions, that's business logic hiding in context. Pass it explicitly instead: visible in the function signature, enforced by the compiler, testable without a context setup.&lt;/p&gt;

&lt;p&gt;The rule: context is for observability. Not for driving logic.&lt;/p&gt;

&lt;p&gt;One thing to get right: don't use raw string keys. Using &lt;code&gt;"requestID"&lt;/code&gt; as a key means any other package could accidentally collide with it. In Go's context, both the key &lt;em&gt;and&lt;/em&gt; the value are typed as &lt;code&gt;any&lt;/code&gt; (the empty interface). That's why a raw string like &lt;code&gt;"requestID"&lt;/code&gt; as a key is risky. Any other package using the same string collides silently. &lt;/p&gt;

&lt;p&gt;Define a private type instead:&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;// private key type — unexported, so no other package can collide with it&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;contextKey&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;contextKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"requestID"&lt;/span&gt;

&lt;span class="c"&gt;// middleware sets it&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;withRequestID&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;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="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;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;The private type wraps the string in a new type the compiler treats as distinct, making collisions impossible across package boundaries.&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;// handler reads it&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handleRequest&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="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;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;Value&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="o"&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;if&lt;/span&gt; &lt;span class="o"&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;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;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"warning: requestID missing from context"&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="s"&gt;"unknown"&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;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handling request %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test: does this value die when the request dies? If not, it's a function parameter, not a context value.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Cancellation — and why AbortController is the better comparison
&lt;/h2&gt;

&lt;p&gt;Here's a scenario. Your server receives a request and kicks off a fetch to a third-party API. While that fetch is in-flight, waiting on the promise to resolve, your server goes down. The third-party API eventually responds. But there's nobody home. The response arrives, resolves into a void, and the work was wasted.&lt;/p&gt;

&lt;p&gt;Or a softer version: the fetch is just taking too long, and you want to bail.&lt;/p&gt;

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

&lt;p&gt;You ask your dad to pick up supplies for a party. He grabs his keys, heads out the door. You can picture it. He's already at the store, navigating the aisles, loading the cart.&lt;/p&gt;

&lt;p&gt;And then you check the RSVPs. Not enough people are coming. Majority said no.&lt;/p&gt;

&lt;p&gt;You need to stop him. But here's the thing. You can only reach him if he has his phone with him. The phone is what makes cancellation even possible. Without it, there's no way to get through. The chore just... continues.&lt;/p&gt;

&lt;p&gt;That's &lt;code&gt;ctx&lt;/code&gt;. You pass it into the goroutine the same way dad takes his phone with him. It's the thing that keeps the line open between you two.&lt;/p&gt;

&lt;p&gt;When you decide to cancel, you call him. That's &lt;code&gt;cancel()&lt;/code&gt;. His phone rings. That's &lt;code&gt;ctx.Done()&lt;/code&gt;. He checks if he missed a call before heading to the next store. That's &lt;code&gt;select { case &amp;lt;-ctx.Done(): }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But if he left his phone at home? No &lt;code&gt;ctx&lt;/code&gt;. No cancellation. He comes back with a car full of streamers, balloons, and enough snacks for thirty people.&lt;/p&gt;

&lt;p&gt;For a party that isn't happening.&lt;/p&gt;

&lt;p&gt;You'd get scolded for making him step out for nothing. But nothing compared to explaining why he just spent $300 on a party nobody's coming to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note: in Go, "calling dad" isn't literally a phone call. It's &lt;code&gt;cancel()&lt;/code&gt; closing a channel. Every goroutine listening on &lt;code&gt;ctx.Done()&lt;/code&gt; gets unblocked simultaneously, not one by one. The call analogy is just a way to picture "signalling that work should stop."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Go, this looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="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;WithCancel&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="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="c"&gt;// Dad heads out first — he's already in-flight&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;dadGetPartySupplies&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"cake"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"plates"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"balloons"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c"&gt;// simplified — in real code you'd watch RSVPs concurrently (more on that in post #4)&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;majorityRSVPed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guestList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// dad is already out — this stops him mid-chore&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;defer cancel()&lt;/code&gt; is the safety net. Whether &lt;code&gt;cancel()&lt;/code&gt; is called explicitly or the function returns normally, it always runs. No goroutine leak. Calling &lt;code&gt;cancel()&lt;/code&gt; twice is safe. It's idempotent.&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;// Dad checks his phone before heading to each store&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;dadGetPartySupplies&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;supplies&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;stores&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"cake"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"https://api.bakery.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"plates"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"https://api.costco.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"balloons"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://api.partycity.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;supplies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// check phone before heading to the next store&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"party's cancelled, heading home"&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;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// context.Canceled&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fetchFromStore&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;stores&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="n"&gt;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="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="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;New to Go channels and select? &lt;a href="https://go.dev/tour/concurrency/2" rel="noopener noreferrer"&gt;A Tour of Go covers them here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you've used &lt;code&gt;AbortController&lt;/code&gt; in JS, this is the same idea. You create a controller, pass its signal to &lt;code&gt;fetch&lt;/code&gt;, and call &lt;code&gt;abort()&lt;/code&gt; when you need to cancel: a timeout, a user navigating away, a component unmounting.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&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;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com/data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch was cancelled — no supplies needed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;AbortController&lt;/code&gt;, once &lt;code&gt;fetch&lt;/code&gt; starts, you can't stop it. Same problem Go has without &lt;code&gt;ctx&lt;/code&gt;. The goroutine just keeps running.&lt;/p&gt;

&lt;p&gt;In real code, you often don't cancel manually. You set a deadline instead. &lt;code&gt;context.WithTimeout&lt;/code&gt; cancels automatically when time runs out:&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;// caller sets the deadline — 3 seconds max&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;getUserDetails&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;userID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// fetchFromEndpoint passes ctx to http.NewRequestWithContext,&lt;/span&gt;
&lt;span class="c"&gt;// so if the deadline fires mid-request, the HTTP call is cut off there too —&lt;/span&gt;
&lt;span class="c"&gt;// not just between iterations.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;fetchFromEndpoint&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;url&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;req&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;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequestWithContext&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;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodGet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// function just receives ctx and listens — doesn't need to know about the timeout&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getUserDetails&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;userID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"https://api.profiles.com/user/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"https://api.billing.com/user/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"https://api.activity.com/user/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&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;endpoints&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&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;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// context.DeadlineExceeded if timeout, context.Canceled if manual&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fetchFromEndpoint&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;url&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="k"&gt;return&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="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;select&lt;/code&gt; catches cancellation between API calls. &lt;code&gt;fetchFromEndpoint&lt;/code&gt; passes &lt;code&gt;ctx&lt;/code&gt; to the underlying HTTP client. So if the deadline fires mid-request, the HTTP call gets cut off there too. You don't need to check a timer yourself. The context does it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;default&lt;/code&gt; case runs immediately if the channel isn't closed yet, so the goroutine keeps going without blocking. Only when &lt;code&gt;cancel()&lt;/code&gt; is called does the &lt;code&gt;case &amp;lt;-ctx.Done()&lt;/code&gt; branch runs.&lt;/p&gt;

&lt;p&gt;It's &lt;code&gt;ctx&lt;/code&gt; that maps to &lt;code&gt;AbortSignal&lt;/code&gt;, not &lt;code&gt;AbortController&lt;/code&gt;. You pass &lt;code&gt;signal&lt;/code&gt; into &lt;code&gt;fetch&lt;/code&gt; the same way you pass &lt;code&gt;ctx&lt;/code&gt; into a goroutine. Neither initiates the cancellation. They just listen for it. &lt;code&gt;cancel()&lt;/code&gt; is the AbortController equivalent. The thing you call to trigger the stop.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JS&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controller.signal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;signal&lt;/code&gt; passed to &lt;code&gt;fetch&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ctx&lt;/code&gt; passed to goroutine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controller.abort()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cancel()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;addEventListener('abort', ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;-ctx.Done()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;ctx.Done()&lt;/code&gt; is a function that returns a channel which closes when the context is cancelled. The goroutine's equivalent of an abort event listener.&lt;/p&gt;

&lt;p&gt;AbortController says: here's a signal you can pass to fetch, call abort() when you're done with it.&lt;br&gt;
&lt;code&gt;context.WithCancel&lt;/code&gt; says: here's a context you can pass to goroutines, call cancel() when you're done with it.&lt;br&gt;
Same word. Completely different problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same word. Completely different job.
&lt;/h2&gt;

&lt;p&gt;React's context shares state down a component tree. Go's context does two things: carries request-scoped values that need to travel with a request, and propagates cancellation signals across goroutines.&lt;/p&gt;

&lt;p&gt;The confusion is understandable. The word is the same. The mental model is completely different.&lt;/p&gt;

&lt;p&gt;Next time you reach for &lt;code&gt;context.WithValue&lt;/code&gt; in Go, ask yourself: does this value die when the request dies? If not, it's a function parameter, not a context value.&lt;/p&gt;

&lt;p&gt;Next time you spawn a goroutine, ask yourself: does it have a &lt;code&gt;ctx&lt;/code&gt;? If not, you have no way to stop it. &lt;/p&gt;

&lt;p&gt;I learnt this the hard way. A friend who'd been writing Go for years looked at my code and said: &lt;em&gt;'Did you even read about context?'&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Curious what happens when multiple goroutines share the same context — and one of them gets to decide when everyone stops? I'm publishing a follow-up post next week. We'll build on the party supplies story to get there.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What Go misconceptions have cost you the most time? Drop them in the comments — I'd genuinely love to hear them.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Keep Up With My Go Journey
&lt;/h3&gt;

&lt;p&gt;I'm documenting my transition from frontend to backend in public. If you're on a similar path, or just enjoy watching someone learn and fail publicly, follow along:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linkedin.com/in/supriya-kotturu" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>architecture</category>
      <category>react</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Product Mindset Is the Skill Vibe Coding Can't Replace</title>
      <dc:creator>Supriya</dc:creator>
      <pubDate>Tue, 31 Mar 2026 05:05:00 +0000</pubDate>
      <link>https://dev.to/supriya-kotturu/product-mindset-is-the-skill-vibe-coding-cant-replace-26jk</link>
      <guid>https://dev.to/supriya-kotturu/product-mindset-is-the-skill-vibe-coding-cant-replace-26jk</guid>
      <description>&lt;h2&gt;
  
  
  Everyone Can Ship Now. That's the Problem.
&lt;/h2&gt;

&lt;p&gt;A few weeks ago, I built a full &lt;a href="https://dev.to/supriya-kotturu/building-go-mastery-socratic-learning-claude-artifacts-and-the-art-of-productive-struggle-2cpo"&gt;quiz app&lt;/a&gt; in three hours. Peaky Blinders on one window, Claude on the other. I described what I wanted, and it appeared. I didn't write a single component. I didn't set up a build tool. I didn't think about routing.&lt;/p&gt;

&lt;p&gt;I just thought.&lt;/p&gt;

&lt;p&gt;That's what vibe coding does. It collapses the distance between idea and implementation so dramatically that the bottleneck is no longer "can you build it" but "do you know what to build."&lt;/p&gt;

&lt;p&gt;And most engineers, myself included, are quietly underprepared for that shift.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cost of Building Used to Do the Thinking For You
&lt;/h2&gt;

&lt;p&gt;For a long time, the cost of building was a forcing function for clarity. If you were going to spend two weeks on something, you'd better understand it well enough to defend your decisions. The effort of implementation forced you to ask the uncomfortable questions early, because asking them late was painful and expensive.&lt;/p&gt;

&lt;p&gt;Vibe coding removes that friction. Which is mostly great. Genuinely. You can go from idea to working thing in an afternoon, and that's kind of wild.&lt;/p&gt;

&lt;p&gt;But it also means you can now ship something confidently without ever asking the questions that used to be forced on you by circumstance.&lt;/p&gt;

&lt;p&gt;What problem does this actually solve? For whom? What does success look like? What are the tradeoffs? What happens when it scales? What's the failure mode?&lt;/p&gt;

&lt;p&gt;These aren't engineering questions. They're product questions. And they've always mattered. They just used to have a built-in enforcer. That enforcer is gone now.&lt;/p&gt;




&lt;h2&gt;
  
  
  I Felt This Firsthand Building Go Mastery
&lt;/h2&gt;

&lt;p&gt;When I sat down to build Go Mastery, my first instinct was the engineering instinct: what components do I need, how does state flow, what's the data model.&lt;/p&gt;

&lt;p&gt;Then I caught myself. This was supposed to be about learning Go, not building a React app to learn Go. Those are very different projects. If I'd gone down that path I'd have spent three hours on architecture and zero hours thinking about whether the quiz would actually teach anything. I'd have shipped a beautiful, thoroughly useless piece of software.&lt;/p&gt;

&lt;p&gt;So I forced myself to stay in the product questions first.&lt;/p&gt;

&lt;p&gt;Why do most quizzes fail to produce real learning? Because they're built to be completed, not understood. One obviously right answer surrounded by obvious decoys. You don't need to understand anything. You just need to not be distracted. It's less of a test and more of a vibe check.&lt;/p&gt;

&lt;p&gt;What would a quiz look like if it made that strategy impossible? One wrong answer surrounded by options that all sound plausible. Suddenly you can't pattern-match your way through. You have to actually think.&lt;/p&gt;

&lt;p&gt;What would make it even harder to shortcut? A pushback mechanic. 60% of the time, after you answer, the quiz asks "are you sure about that?" You never know if you're wrong or just being challenged. So you pause, reread, and, if you're like me, briefly question every choice you've made.&lt;/p&gt;

&lt;p&gt;None of that thinking required code. It required understanding the problem deeply enough to have opinions about the solution. The implementation took three hours because the thinking happened first.&lt;/p&gt;

&lt;p&gt;That's the product mindset. And vibe coding doesn't give it to you. It just makes it more visible when you don't have it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hallucinations Are the Same Problem, Wearing Different Clothes
&lt;/h2&gt;

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

&lt;p&gt;AI hallucinations and learning misconceptions are structurally the same problem. In both cases, something that sounds correct isn't. In both cases, the danger isn't obvious wrongness. It's plausible wrongness. The output that's 90% right and 10% subtly broken is far more dangerous than the output that's just clearly wrong, because at least the clearly wrong answer is easy to catch.&lt;/p&gt;

&lt;p&gt;Spotting the obviously wrong answer is table stakes. Spotting the wrong answer when everything around it sounds reasonable, that's the actual skill.&lt;/p&gt;

&lt;p&gt;This is why I think misconception-spotting is becoming one of the most underrated skills in engineering. Not skepticism for its own sake, not compulsive fact-checking on every sentence. The specific ability to read something that sounds authoritative and plausible, hold it up against what you actually know, and find exactly where it goes sideways.&lt;/p&gt;

&lt;p&gt;That skill is directly trained by building with AI. Every time you prompt Claude or GPT and get back something that's almost right, you're exercising it. Every time you catch a hallucination before it ships, you're getting sharper. It's like going to the gym, except the gym occasionally tries to convince you that goroutines work like JavaScript promises.&lt;/p&gt;

&lt;p&gt;The engineers who will thrive in the vibe coding era aren't the ones who can prompt best. They're the ones who can evaluate output best. And evaluating output requires the same thing that evaluating quiz answers requires: understanding the concept deeply enough to know why something is wrong, not just that it is.&lt;/p&gt;




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

&lt;p&gt;A few things I've changed about how I work since building Go Mastery:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before writing a prompt, write the brief.&lt;/strong&gt; What problem am I solving? Who has this problem? What does good look like? What are the constraints? Five minutes of this saves thirty minutes of iteration and one very confusing diff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat AI output like a quiz answer.&lt;/strong&gt; Not "is this right?" but "where is this wrong?" The assumption of correctness is the trap. The default should be "this is probably mostly right and I need to find the part that isn't." It usually isn't hard to find.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Study the concepts, not just the syntax.&lt;/strong&gt; Vibe coding makes it easy to ship Go without understanding Go. But when the hallucination shows up (and it will), you need the mental model to catch it. Surface-level familiarity doesn't give you that. Understanding does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think in problems, not features.&lt;/strong&gt; The question "what should I build?" is less useful than "what problem am I solving and is this the right solution?" Features are easy to generate now. Problems worth solving are still hard to find, and no amount of prompting helps with that part.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Skill Stack Is Shifting
&lt;/h2&gt;

&lt;p&gt;We're not heading toward a world where engineering skills don't matter. We're heading toward a world where the engineering skills that matter most are shifting up the stack.&lt;/p&gt;

&lt;p&gt;Implementation is getting cheaper. Understanding is getting more valuable. Product thinking, knowing what to build, for whom, and why, is becoming the differentiator.&lt;/p&gt;

&lt;p&gt;That's not a threat to engineers. It's honestly a pretty good development if you're willing to lean into it. The engineers who've always wanted to think at the product level but got stuck in implementation details now have the tools to do both. The ones who only ever thought about implementation are going to find that better tools make the work harder, not easier, because the tools expose what was always true: code was never the hard part.&lt;/p&gt;

&lt;p&gt;Vibe coding doesn't replace thinking. It just makes thinking the job.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write about Go, full-stack development, and shipping projects without overthinking them. Follow along on &lt;a href="https://www.linkedin.com/in/supriya-kotturu" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; if this resonated.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>vibecoding</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building Go Mastery: Socratic Learning, Claude Artifacts, and the Art of Productive Struggle</title>
      <dc:creator>Supriya</dc:creator>
      <pubDate>Wed, 11 Mar 2026 10:43:47 +0000</pubDate>
      <link>https://dev.to/supriya-kotturu/building-go-mastery-socratic-learning-claude-artifacts-and-the-art-of-productive-struggle-2cpo</link>
      <guid>https://dev.to/supriya-kotturu/building-go-mastery-socratic-learning-claude-artifacts-and-the-art-of-productive-struggle-2cpo</guid>
      <description>&lt;p&gt;I came to Go from the React world. Frontend was comfortable. I knew the mental model: components, props, state, async with promises, &lt;code&gt;then().catch()&lt;/code&gt;, &lt;code&gt;async/await&lt;/code&gt;. It felt like home.&lt;/p&gt;

&lt;p&gt;Go felt like moving into a new city where everything is slightly off. The streets look familiar but the traffic flows the wrong way.&lt;/p&gt;

&lt;p&gt;The typed, compiled mindset was an adjustment. JavaScript lets you be loose; Go does not. But the thing that genuinely baffled me was concurrency. In JS, async is a pattern layered on top of the language. Promises chain. &lt;code&gt;await&lt;/code&gt; suspends. The mental model, once you have it, is linear.&lt;/p&gt;

&lt;p&gt;Goroutines don't work like that. There's no &lt;code&gt;await&lt;/code&gt;. No &lt;code&gt;.then()&lt;/code&gt;. You launch a goroutine and it runs alongside everything else, communicating through channels. I kept reaching for the async mental model I already had and kept grabbing at air.&lt;/p&gt;

&lt;p&gt;That's when I realized the problem wasn't Go. It was that I was trying to learn it the same way I'd learned everything else: skim, pattern-match, move on. For React, that worked. For Go's concurrency model, it absolutely did not.&lt;/p&gt;




&lt;h2&gt;
  
  
  One Tuesday Afternoon and a Claude Subscription Going to Waste
&lt;/h2&gt;

&lt;p&gt;Around that time, I kept thinking my Claude subscription wasn't pulling its weight. Then one Tuesday after spring break, I finally had a breather. I opened up their Cowork app and started poking around. One thing led to another and I found a remixable hallucination detection game sitting there. It clicked immediately.&lt;/p&gt;

&lt;p&gt;We're living in a world flooded with AI-generated content. Finding the wrong pattern, the thing that sounds right but isn't, is becoming a genuine skill. That framing mapped perfectly onto what I was struggling with in Go. I decided to remix it.&lt;/p&gt;

&lt;p&gt;I spent three hours that afternoon prompting and thinking through the product on breaks, with Peaky Blinders running on the other window. What surprised me was how easy it felt to describe what the app should &lt;em&gt;do&lt;/em&gt; without worrying about the &lt;em&gt;how&lt;/em&gt;. That's usually not how I work. As a frontend engineer, I think in components, in implementation. But this was supposed to be about learning Go, not building a React app to learn Go. If I'd done it the other way, I'd have missed the whole point.&lt;/p&gt;

&lt;p&gt;So I stayed in the prompt. I brainstormed streaks to keep users coming back. I worried about the wait time between questions. Every question was a fresh API call and the pause felt wrong. The frontend engineer brain kicked in immediately: &lt;em&gt;just add a loader&lt;/em&gt;. So I did. But a loader alone felt lazy, so I asked Claude to show random quotes from Go talks and well-known Go engineers while users waited. That helped.&lt;/p&gt;

&lt;p&gt;Then came the caching question. I'd recently learned what caching was, in that early curious way where you start seeing it everywhere. My new backend instincts whispered: &lt;em&gt;this is a perfect place to cache something&lt;/em&gt;. But I wasn't sure if a cache could be shared across different users. Turns out, with Claude artifact storage, it can.&lt;/p&gt;

&lt;p&gt;The design fell into place naturally: shared storage for generated questions, personal storage for each user's streak and progress. The first user to load the app takes the hit of generating questions. Everyone after that gets them instantly. And the more users play, the richer the question pool gets, and the faster it gets for the next person.&lt;/p&gt;

&lt;p&gt;That was the first working version. But building it forced me to confront something I'd been doing wrong for years.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Most Quizzes (and How I Broke the Format)
&lt;/h2&gt;

&lt;p&gt;I've been cheating myself for years.&lt;/p&gt;

&lt;p&gt;Not on exams. On learning. There's a difference, and building this app made me finally see it.&lt;/p&gt;

&lt;p&gt;The pattern showed up first in grad school, then again as I started learning Go. I'd sit down for a quiz, skim the options, and pick the answer that &lt;em&gt;felt&lt;/em&gt; right. Not because I understood the concept. Because I'd gotten good at finding the odd one out, the option that didn't match the vibe of the others.&lt;/p&gt;

&lt;p&gt;I was pattern-matching, not thinking. And it worked often enough that I never questioned it.&lt;/p&gt;

&lt;p&gt;That's the problem with most multiple-choice quizzes: they're built to be completed, not understood. One obviously right answer, three obvious decoys. You don't need to understand anything. You just need to not be distracted.&lt;/p&gt;

&lt;p&gt;So I flipped it. Instead of one right answer surrounded by wrong ones, Go Mastery has one &lt;em&gt;wrong&lt;/em&gt; answer surrounded by options that all sound plausible. Suddenly you can't skim for vibes. You have to read. You have to think. You have to know the &lt;em&gt;why&lt;/em&gt;, not just spot the outlier.&lt;/p&gt;

&lt;p&gt;And I made it harder still. 60% of the time, after you pick an answer, the app pushes back: &lt;em&gt;"Are you sure about that?"&lt;/em&gt; You never know if it's because you're wrong or just unlucky. So you pause, reread, rethink. If you do fail, you get three more tries and a mini lesson that explains what you missed. Failure stops being "you lost" and starts being "here's what you didn't understand."&lt;/p&gt;




&lt;h2&gt;
  
  
  The Deployment Question: Knowing When to Stop
&lt;/h2&gt;

&lt;p&gt;With the quiz working and the format redesigned, one question remained: &lt;em&gt;does this actually need to be deployed?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's not laziness. It's a real design decision. I'm in grad school. I don't have weekends. And "deploy it properly" means ongoing maintenance, billing alerts, and infrastructure decisions that compound over time. Before committing to any of that, I wanted to understand what I was actually signing up for.&lt;/p&gt;

&lt;p&gt;So I did the math.&lt;/p&gt;

&lt;p&gt;A typical question generation on Sonnet runs about 1,200 input tokens + 600 output tokens, roughly $0.012 per question. Add a doubt challenge, a hint, and a lesson for a user who struggles, and a full 10-question session costs around $0.24.&lt;/p&gt;

&lt;p&gt;That sounds fine until you think about scale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100 users/day → $540/month&lt;/li&gt;
&lt;li&gt;500 users/day (one Reddit post) → $2,700/month&lt;/li&gt;
&lt;li&gt;1,000 users/day (viral) → $5,400/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;(These estimates use Anthropic's Sonnet 4.5 pricing as of early 2026. Always verify current rates before building on this math.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's the deployed version. The artifact version costs me nothing to run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Deploy with my API key.&lt;/strong&gt; Polished, standalone, mine. Also, a billing liability I can't monitor from an assignment deadline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Stay in Claude artifacts.&lt;/strong&gt; Less control over uptime and updates. Every change means republishing. But users can fork it, remix it, make it their own. And the marginal cost to me is zero.&lt;/p&gt;

&lt;p&gt;I chose artifacts. Not because it was easier, but because it was the right tradeoff &lt;em&gt;for this stage&lt;/em&gt;. A freely remixable tool that reaches more learners beats a locked-down app I'd eventually neglect.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Caching Layer: How It Actually Works
&lt;/h2&gt;

&lt;p&gt;If you want to build something similar on top of Claude artifacts, here's the technical reality of the caching layer.&lt;/p&gt;

&lt;p&gt;Claude artifact storage gives you two modes: personal and shared. Personal storage is scoped to each user — that's where I store streaks and session progress. Shared storage is visible to everyone who loads the artifact — that's where the generated questions live.&lt;/p&gt;

&lt;p&gt;The virtuous cycle this creates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The first user loads the app and triggers question generation, which seeds one question per topic into shared storage&lt;/li&gt;
&lt;li&gt;Any user who clicks "Generate" adds more questions, also cached to shared storage&lt;/li&gt;
&lt;li&gt;Every user after that skips generation entirely and pulls from cache&lt;/li&gt;
&lt;li&gt;The pool grows organically over time, and API calls drop toward zero&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The cache caps at 5 questions per topic. Once full, new generations push out the oldest so the pool stays fresh without growing unbounded.&lt;/p&gt;

&lt;p&gt;A few constraints worth knowing if you build on this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;20 MB limit per artifact&lt;/strong&gt; — more than enough for text-based question data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text only&lt;/strong&gt; — no images or binary data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only works when published&lt;/strong&gt; — storage operations fail in development, so you have to publish to test the caching layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one real tradeoff: updating the question set requires republishing the artifact. That's fine for my use case. But if you need live updates, you'd want a different architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Is Really About
&lt;/h2&gt;

&lt;p&gt;Go Mastery started as a Tuesday afternoon side project. It ended up teaching me more about learning than Go did.&lt;/p&gt;

&lt;p&gt;The throughline across every decision: &lt;strong&gt;make the right things harder, not easier.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hard to pattern-match answers. Hard to mindlessly click through. Hard to walk away without understanding why you got something wrong. That friction isn't a bug in the design. It's the whole point.&lt;/p&gt;

&lt;p&gt;The same principle applied to how I built it. The easy path was spinning up a React app. The better path was staying in the prompt, describing what I wanted, and letting the tool handle the how. That's a different kind of thinking, and it's one I needed to practice.&lt;/p&gt;

&lt;p&gt;If you're learning Go, or building learning tools, or just trying to ship something without overthinking it: &lt;a href="https://claude.ai/public/artifacts/f313c7fb-24cb-4d4b-8ed6-dae8f3d3af27" rel="noopener noreferrer"&gt;Go Mastery is here&lt;/a&gt;. Fork it, change the topic, make it yours. Try it. Struggle a little. That's the point.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I write about Go, full-stack development, and shipping projects without overthinking them. Follow along on &lt;a href="https://www.linkedin.com/in/supriya-kotturu" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; if that sounds useful.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>go</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to set up SSH keys for signed commits</title>
      <dc:creator>Supriya</dc:creator>
      <pubDate>Mon, 16 Jun 2025 18:54:46 +0000</pubDate>
      <link>https://dev.to/supriya-kotturu/how-to-set-up-ssh-keys-for-signed-commits-1l7l</link>
      <guid>https://dev.to/supriya-kotturu/how-to-set-up-ssh-keys-for-signed-commits-1l7l</guid>
      <description>&lt;p&gt;SSH keys are used to access and write data into your GitHub repository. Whenever you connect via SSH, you authenticate using the private key file on your local machine.&lt;/p&gt;

&lt;p&gt;Before creating a new key, check if you already have one:&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;# on linux&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; ~/.ssh

&lt;span class="c"&gt;# on Windows&lt;/span&gt;
&lt;span class="nb"&gt;ls &lt;/span&gt;c:/Users/&amp;lt;your name&amp;gt;/.ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for files like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id_rsa  id_rsa.pub  or  id_ed25519  id_ed25519.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. Create your SSH key
&lt;/h2&gt;

&lt;p&gt;You can generate your SSH key pair using &lt;code&gt;ssh-keygen&lt;/code&gt;. Many algorithms generate an SSH key pair. Here, we specify the &lt;strong&gt;Ed25519&lt;/strong&gt; algorithm using the &lt;code&gt;-t&lt;/code&gt; flag. The &lt;code&gt;-C&lt;/code&gt; flag allows us to add a comment to identify the key and is optional.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"your_email@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates an SSH key pair (public and private keys) in the &lt;code&gt;c:/Users/&amp;lt;yourName&amp;gt;/.ssh&lt;/code&gt; folder on Windows.&lt;/p&gt;

&lt;p&gt;Your key pair is saved as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;c:/Users/&amp;lt;yourName&amp;gt;/.ssh/id_ed25519&lt;/code&gt; (private)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;c:/Users/&amp;lt;yourName&amp;gt;/.ssh/id_ed25519.pub&lt;/code&gt; (public)&lt;/p&gt;

&lt;p&gt;On Linux, this is usually: &lt;code&gt;~/.ssh/&lt;/code&gt;. Listing the files in the folder shows two files.&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="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
id_ed25519  id_ed25519.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Add the SSH Key to the SSH Agent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;ssh-agent &lt;span class="nt"&gt;-s&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# on Linux&lt;/span&gt;
ssh-add ~/.ssh/id_ed25519

&lt;span class="c"&gt;# on Windows&lt;/span&gt;
ssh-add /c/Users/&amp;lt;yourName&amp;gt;/.ssh/id_ed25519
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Copy the public key
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;cat&lt;/code&gt; shows the file's content. The pipe (&lt;code&gt;|&lt;/code&gt;) operator feeds this content as input to the &lt;code&gt;clip&lt;/code&gt; command. This copies the file content to your clipboard.&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="nb"&gt;cat&lt;/span&gt; ~/.ssh/id_ed25519.pub | clip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Add the public key to your GitHub account
&lt;/h2&gt;

&lt;p&gt;Go to GitHub → Profile Icon → Settings.&lt;/p&gt;

&lt;p&gt;Navigate to SSH and GPG keys&lt;/p&gt;

&lt;p&gt;Click New SSH key&lt;/p&gt;

&lt;p&gt;Give it a title (e.g., “Laptop - June 2025”) and paste the key&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frselsq34ivwsbl7ps5h1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frselsq34ivwsbl7ps5h1.png" alt="Image showing the user setting on GitHub under profile section." width="448" height="1309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01eynve3u0zer3rr4e4a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01eynve3u0zer3rr4e4a.png" alt="Image showing the SSH and GPG keys in the User settings" width="571" height="1018"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1fjljpfzpqvgcwt7vks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1fjljpfzpqvgcwt7vks.png" alt="Image which shows a button to create a new SSH key on GitHub" width="800" height="65"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since we are using this key to sign our commits, make sure you change the "Key type" to &lt;strong&gt;&lt;em&gt;Signing key&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj439n64onqa69b330i2z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj439n64onqa69b330i2z.png" alt="Image that shows details to create a new SSH key in GitHub. Make sure you select this as Signing key" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Testing your SSH connection
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-T&lt;/span&gt; git@github.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see something like this after you enter your passphrase (if you configured it while generating the SSH key pair).&lt;/p&gt;

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

&lt;p&gt;If you also want to authenticate repositories with this SSH key, add the same public key again as an Authentication key under SSH and GPG keys → New SSH Key → Change "Key Type" to Authentication key.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Use SSH for Your Repo Remote (optional)
&lt;/h2&gt;

&lt;p&gt;If your repo was cloned using HTTPS, you'll need to switch to SSH to use this key to push changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote set-url origin git@github.com:yourusername/yourrepo.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Signing commits
&lt;/h2&gt;

&lt;p&gt;Enable commit signing and specify SSH key format&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config commit.gpgsign &lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# or enable it globally&lt;/span&gt;

git config &lt;span class="nt"&gt;--global&lt;/span&gt; commit.gpgsign &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure Git to use SSH to sign commits and tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; gpg.format ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To set your SSH signing key in Git, paste the text below, substituting &lt;code&gt;/PATH/TO/.SSH/KEY.PUB&lt;/code&gt; with the path to the public key you'd like to use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.signingkey /PATH/TO/.SSH/KEY.PUB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. Check for verification on your new commit
&lt;/h2&gt;

&lt;p&gt;When committing changes in your local branch, add the &lt;code&gt;-S&lt;/code&gt; flag to the git commit command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git commit -S -m "YOUR_COMMIT_MESSAGE"
# Creates a signed commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you configured a passphrase while setting up the SSH key, provide it. Otherwise, leave it blank.&lt;/p&gt;

&lt;p&gt;Now, visit the commits in your repository to check the verified status.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2j6omisjuwc67z10gh5h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2j6omisjuwc67z10gh5h.png" alt="Image showing the verified tag in the commit, after configuring SSH key" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh" rel="noopener noreferrer"&gt;GitHub - Connecting to SSH&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#ssh-commit-signature-verification" rel="noopener noreferrer"&gt;GitHub - Commit Signature Verification&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits" rel="noopener noreferrer"&gt;GitHub - Signing Commits&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>A brief overview of HTTP protocol</title>
      <dc:creator>Supriya</dc:creator>
      <pubDate>Fri, 18 Aug 2023 10:53:42 +0000</pubDate>
      <link>https://dev.to/supriya-kotturu/a-brief-overwief-of-http-protocol-5e67</link>
      <guid>https://dev.to/supriya-kotturu/a-brief-overwief-of-http-protocol-5e67</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Let's start with the basics. The web works on client and server model. Where the Client initiates the request. It passes through the internet and reaches the server. The server then processes it and sends it a response the same way it arrived.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Client&lt;/strong&gt; - usually the browser. But can also be a web crawler bot indexing the search results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internet&lt;/strong&gt; - many networks of connected computers &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server&lt;/strong&gt; - THE computer that responds to that particular request&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  HTTP
&lt;/h2&gt;

&lt;p&gt;HTTP stands for Hypertext Transfer Protocol. But what is &lt;em&gt;Hypertext&lt;/em&gt;? Hypertext is a way of linking text to other text or resources. It is the underlying concept of the World Wide Web, where web pages are interconnected by hyperlinks. It is a text which you see on a computer, and on clicking on it redirects to the resource. Furthermore, this resource is a document (usually HTML files) present on a computer (it can be any computer. Be it yours or someone else's on cloud). And now, what is a Protocol? To send this request to the server, we need to follow some standard rules/protocols.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In short, HTTP is the &lt;strong&gt;standard set of rules&lt;/strong&gt;, which &lt;em&gt;describe the way we can make a request&lt;/em&gt;, that allows us to transfer a text/file/media (hypertext/hypermedia) over the internet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Between the client and the server, there are many entities (routers, switches, load balancers) through with the requests get passed. But discussing them would be out of scope for this article. You can read more about this &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/How_does_the_Internet_work" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's Simple and Stateless
&lt;/h3&gt;

&lt;p&gt;All the HTTP messages are human-readable, which makes it easier for the developers to debug and reduces complexity. Whenever a request is made from the client, the server doesn't store any information; which makes it stateless. &lt;/p&gt;

&lt;p&gt;You might question, if the server doesn't have the data from previous requests, how will it be able to keep track of what response to send for an authenticated user or a user with different permissions? &lt;br&gt;
Well, though it is stateless; it can keep track of this data using cookies, whenever a new session is created. This is where we store the JWT tokens of an authenticated user in &lt;strong&gt;HTTP Header&lt;/strong&gt;, to let your server know that the user is logged in.&lt;/p&gt;
&lt;h3&gt;
  
  
  Building Blocks
&lt;/h3&gt;

&lt;p&gt;HTTP is built on top of TCP/IP protocols and consisted of 4 blocks&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;textual format&lt;/em&gt; to represent Hypertext files (HTML)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;protocol&lt;/em&gt; to exchange these files (HTTP)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;client&lt;/em&gt; to display these files (browser)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;server&lt;/em&gt; to give access to these files&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Evolution on HTTP
&lt;/h3&gt;
&lt;h4&gt;
  
  
  HTTP/0.9
&lt;/h4&gt;

&lt;p&gt;The initial implementation of HTTP was remarkably straightforward. It's also called &lt;strong&gt;one-line protocol&lt;/strong&gt;, which contained only &lt;code&gt;GET&lt;/code&gt; method followed by the path to the resource. &lt;/p&gt;

&lt;p&gt;REQUEST&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET /fancyWebPage/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RESPONSE&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  HTML page with only text. 
  No images or other resources are included in this file.
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;only GET method was provided to access the resources&lt;/li&gt;
&lt;li&gt;no HTTP Headers. The response was always a (HTML) file.&lt;/li&gt;
&lt;li&gt;no status or error codes. If there was an error, an HTML file with the generated error description was sent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  HTTP/1.0
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;versioning&lt;/strong&gt; was added at the end of the resource path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;status code&lt;/strong&gt; was also sent at the beginning of the response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Headers&lt;/strong&gt; were introduced for both request and responses, which allowed us to send metadata.&lt;/li&gt;
&lt;li&gt;This allowed us to &lt;strong&gt;transfer documents other than plain HTML&lt;/strong&gt; if its respective HTTP header value was included in &lt;code&gt;Content-Type&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The connection is closed after the response is received. We need to create a new connection for every request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;REQUEST&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET /fancyWebPage/index.html HTTP/1.0
User-Agent: BrowserName/3.0 &lt;span class="o"&gt;(&lt;/span&gt;OS-Name 2.0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RESPONSE&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;200 ok                               &lt;span class="c"&gt;# status code&lt;/span&gt;
Date: Fri, 11 Aug 2023 12:23:22 IST  &lt;span class="c"&gt;# metadata&lt;/span&gt;
Server: company/2.0 project/2.8      &lt;span class="c"&gt;# metadata&lt;/span&gt;
Content-Type: text/html              &lt;span class="c"&gt;# metadata&lt;/span&gt;
&amp;lt;html&amp;gt;                               &lt;span class="c"&gt;# html content&lt;/span&gt;
  HTML page with images and other file content. 
  &amp;lt;img &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/cat.png"&lt;/span&gt; /&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the response contained, other resources; subsequent calls are made to the server to fetch them. Here, in the next call, the browser makes a call to the server to fetch the &lt;code&gt;cat.png&lt;/code&gt; file.&lt;/p&gt;

&lt;h4&gt;
  
  
  HTTP/1.1
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;it introduced 'keep-alive' mechanism. Connections can be reused. There's no longer a need to open multiple connections for each request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipelining&lt;/strong&gt; was introduced, which allowed the client to make a new request without waiting for the response from the previous one. The responses were sent in the order they were requested.

&lt;ul&gt;
&lt;li&gt;But this couldn't be handled well by the proxy servers(entities) between the client and server.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Cache control&lt;/strong&gt; mechanisms and support for &lt;strong&gt;chunked responses&lt;/strong&gt; was added.&lt;/li&gt;

&lt;li&gt;It was easier to create new Headers and methods. It was stable for more than 15yrs.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Here's an image from MDN showing the request/response structure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbg1oe884td4h8bpigi1j.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbg1oe884td4h8bpigi1j.PNG" alt="Image showing the request and response structure from HTTP/1.1 protocol" width="619" height="818"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTPS&lt;/strong&gt;&lt;br&gt;
Netsape came up with additional layer SSL on top of HTTP to encrypt the transmission of messages between client and server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authoring before REST&lt;/strong&gt;&lt;br&gt;
When the World Wide Web(browser) was created by Tim Berners-Lee, he wanted to create a medium, where everyone can share, edit, create and share the documents over the internet. But eventually, the webpages became read-only for most users. The write access was limited to few folks, who can change these documents on the servers. &lt;/p&gt;

&lt;p&gt;In 1996, people of World Wide Web consortium addressed the problem of authoring on the web and HTTP was extended to allow authoring, creating &lt;strong&gt;WebDAV - Web Distributed Authoring and Versioning&lt;/strong&gt;. It is an extension to HTTP that lets clients edit remote content on the web. Any server that supports WebDAV can act as a file server. There are other extentions like CalDAV which allows the client to schedule events on the server and CardDAV, which allows you to share the contact informatiom on the remote server which work similarly.&lt;/p&gt;

&lt;p&gt;Popular clients of WebDAV are Microsoft Office, OpenOffice, DropBox, etc. Any fie sharing system you can think of uses WebDAV behind the scenes to create/ share/ modify/ delete your files or folders over the internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✨REST✨&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In 2000, Representational State Transfer (REST) was designed to use the HTTP protocol. It uses the basic methods defined in HTTP/1.1 and allowed any web application to modify the data, without the need to update its servers. One drawback of it was each website defined its own RESTful APIs and had complete control over them.&lt;/p&gt;

&lt;p&gt;Think of this as using an open API(MovieDB) on your website, to display the content, but also allowing your clients to modify the data(marking a movie as favourite). But any modification of the data wouldn't effect the data in server where the Open API's being hosted. But instead, these changes are persisted on your website server where you handle these changes on top of Open API data.&lt;/p&gt;

&lt;h4&gt;
  
  
  HTTP/2
&lt;/h4&gt;

&lt;p&gt;As webpages became complex, more scripts being added for interactivity of the webpage... more and more data was being transfered through the HTTP requests. This created more overhead for the HTTP/1.1 connections. In early 2010, Google created an experimental protocol &lt;strong&gt;SPDY&lt;/strong&gt;, which laid foundation for HTTP/2.&lt;/p&gt;

&lt;p&gt;Officially standardised in 2015 and &lt;a href="https://w3techs.com/technologies/details/ce-http2" rel="noopener noreferrer"&gt;35.7%&lt;/a&gt; of websites use HTTP/2&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Binary protocol, unlike HTTP/1.1 which is text based&lt;/li&gt;
&lt;li&gt;multiplexed protocol. Enables the client to make parallel requests.&lt;/li&gt;
&lt;li&gt;It compresses headers, which are common for similar set of requests. This removes duplication and overhead of data transmitted.&lt;/li&gt;
&lt;li&gt;Server push. This allows the server to populate the client cache&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  HTTP/3
&lt;/h4&gt;

&lt;p&gt;It uses QUIC instead of TCP in the transportation layer. &lt;a href="https://w3techs.com/technologies/details/ce-http3" rel="noopener noreferrer"&gt;26.5%&lt;/a&gt; of websites use HTTP/3. QUIC is a connection-oriented protocol that creates a stateful interaction between a client and server. QUIC authenticates the entirety of each packet and encrypts as much of each packet as is practical. QUIC packets are carried in UDP datagrams [UDP] to better facilitate deployment in existing systems and networks.&lt;/p&gt;

&lt;p&gt;Initially an acronym QUICK was described as Quick UDP Internet Connections, but &lt;a href="https://datatracker.ietf.org/doc/html/rfc9000" rel="noopener noreferrer"&gt;RFC 9000&lt;/a&gt; mentions it as name, not an acronym.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiplexed protocol, unlike HTTP/2 which runs on single TCP connection. QUIC runs on multiple UDP streams. &lt;/li&gt;
&lt;li&gt;Endpoints communicate in QUIC by exchanging &lt;strong&gt;QUIC packets&lt;/strong&gt;. Most packets contain frames, which carry information and application data between endpoints. &lt;/li&gt;
&lt;li&gt;Application protocols exchange information over a QUIC connection via &lt;strong&gt;streams&lt;/strong&gt;, which are ordered sequences of bytes.

&lt;ul&gt;
&lt;li&gt;bidirectional streams, which allow both endpoints to send data; &lt;/li&gt;
&lt;li&gt;unidirectional streams, which allow a single endpoint to send data. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;A credit-based scheme is used to limit stream creation and to bound the amount of data that can be sent.&lt;/li&gt;

&lt;li&gt;QUIC depends on congestion control to avoid network congestion using &lt;a href="https://datatracker.ietf.org/doc/html/rfc9002" rel="noopener noreferrer"&gt;QUICK-RECOVERY&lt;/a&gt; algorithm for detecting and recovering losses.&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to create a server with HTTP/2/3 protocol?
&lt;/h3&gt;

&lt;p&gt;Many programming lanaguages have in-built libraries, for us to create servers. Node.js has &lt;a href="https://nodejs.org/api/http.html" rel="noopener noreferrer"&gt;http&lt;/a&gt;, &lt;a href="https://nodejs.org/api/http2.html" rel="noopener noreferrer"&gt;http2&lt;/a&gt; libraries to build implement servers with HTTP protocols.&lt;/p&gt;

&lt;p&gt;Golang has similar libraries &lt;a href="https://pkg.go.dev/net/http" rel="noopener noreferrer"&gt;http&lt;/a&gt;, and &lt;a href="https://pkg.go.dev/golang.org/x/net/http2" rel="noopener noreferrer"&gt;http2&lt;/a&gt;, &lt;a href="https://pkg.go.dev/github.com/lucas-clemente/quic-go/http3" rel="noopener noreferrer"&gt;http3&lt;/a&gt;✨&lt;/p&gt;

&lt;p&gt;But the support for creating servers which use HTTP/3 is still a work in progress in Node.js community. As of today, 18 Aug 2023, there's no library available for us to get on HTTP3. &lt;/p&gt;

&lt;h3&gt;
  
  
  Keep track of HTTP/3 and QUIC
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Node.js - status :  currenly blocked on QUIC implementation &lt;a href="https://github.com/nodejs/node/issues/38478" rel="noopener noreferrer"&gt;#38478&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Reference&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview#http_flow" rel="noopener noreferrer"&gt;HTTP - MDN Docs&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.cloudwards.net/what-is-webdav/" rel="noopener noreferrer"&gt;What is WebDAV&lt;/a&gt;&lt;br&gt;
&lt;a href="https://datatracker.ietf.org/doc/html/rfc9000" rel="noopener noreferrer"&gt;RFC 9000&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.freepik.com/icon/world-grid_3579187#fromView=search&amp;amp;term=internet&amp;amp;page=2&amp;amp;position=21" rel="noopener noreferrer"&gt;Icon by Freepik&lt;/a&gt;&lt;/p&gt;




</description>
      <category>beginners</category>
      <category>network</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
