<?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>Forem: Pavel Sanikovich</title>
    <description>The latest articles on Forem by Pavel Sanikovich (@devflex-pro).</description>
    <link>https://forem.com/devflex-pro</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%2F1182166%2Fe9c0491a-2b1d-4ec6-a6f2-ab6c1dd126cb.png</url>
      <title>Forem: Pavel Sanikovich</title>
      <link>https://forem.com/devflex-pro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/devflex-pro"/>
    <language>en</language>
    <item>
      <title>5 Go Bugs That Only Appear in Production</title>
      <dc:creator>Pavel Sanikovich</dc:creator>
      <pubDate>Mon, 26 Jan 2026 12:30:25 +0000</pubDate>
      <link>https://forem.com/devflex-pro/5-go-bugs-that-only-appear-in-production-4a7g</link>
      <guid>https://forem.com/devflex-pro/5-go-bugs-that-only-appear-in-production-4a7g</guid>
      <description>&lt;p&gt;Go has a reputation for being boring — in a good way.&lt;br&gt;
Strong typing, a simple concurrency model, a strict compiler. If something is wrong, it usually fails fast.&lt;/p&gt;

&lt;p&gt;And yet, many Go bugs don’t fail fast at all.&lt;/p&gt;

&lt;p&gt;They quietly pass tests, survive code review, behave perfectly on your laptop, and only show up in production — under real traffic, real data, and long-running processes.&lt;/p&gt;

&lt;p&gt;This article isn’t about exotic edge cases. It’s about bugs that look innocent, feel “Go-ish”, and still manage to hurt you in production. Especially if you’re a junior or mid-level Go developer.&lt;/p&gt;


&lt;h2&gt;
  
  
  Goroutines That Never Die
&lt;/h2&gt;

&lt;p&gt;One of the most common production issues in Go is not a crash, but slow degradation. Memory usage grows, CPU usage creeps up, and the number of goroutines keeps increasing.&lt;/p&gt;

&lt;p&gt;Often the root cause is a goroutine that was supposed to finish — but never did.&lt;/p&gt;

&lt;p&gt;Consider a worker reading from a channel:&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;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;job&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;ch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&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;This code looks clean and idiomatic. In tests, the channel is closed properly. Locally, everything works.&lt;/p&gt;

&lt;p&gt;In production, things are different. A producer might crash, a request might be canceled, or a code path that closes the channel might never execute. The goroutine stays alive forever, blocked on receive.&lt;/p&gt;

&lt;p&gt;Over time, these goroutines accumulate. The service is still “up”, but it’s slowly dying.&lt;/p&gt;

&lt;p&gt;Production-grade goroutines need an explicit lifetime. Usually that means context cancellation:&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;worker&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;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;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;case&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a goroutine doesn’t know when it should stop, it probably won’t.&lt;/p&gt;




&lt;h2&gt;
  
  
  Data Races That Only Exist Under Load
&lt;/h2&gt;

&lt;p&gt;Go’s race detector is excellent, but it’s not magic. Many race conditions simply don’t appear without real concurrency and real pressure.&lt;/p&gt;

&lt;p&gt;A classic example is shared configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Enabled&lt;/span&gt; &lt;span class="kt"&gt;bool&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;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handler&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;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;doSomething&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;At some point, someone adds hot reload:&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;reload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This might run fine for weeks. Tests pass. The race detector stays quiet.&lt;/p&gt;

&lt;p&gt;Then traffic grows. CPU cores are actually busy. Suddenly behavior becomes inconsistent, but nothing obviously crashes.&lt;/p&gt;

&lt;p&gt;The problem isn’t Go. The problem is mutating shared state without synchronization. In production, concurrency is not hypothetical — it’s constant.&lt;/p&gt;

&lt;p&gt;A safer approach is to treat configuration as immutable and swap it atomically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Load&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="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;doSomething&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;Production reveals races not because it’s special, but because it’s honest.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Interface That Is Nil (Except It Isn’t)
&lt;/h2&gt;

&lt;p&gt;This is one of the most confusing bugs for people new to Go, and it often hides until a rare code path is executed in production.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MyError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"something went wrong"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;do&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;var&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;MyError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the caller’s point of view:&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;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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;"error:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You expect nothing to happen. Instead, the error branch runs.&lt;/p&gt;

&lt;p&gt;The reason is subtle but fundamental. An interface value in Go contains both a type and a value. Here, the value is nil, but the type is not. That makes the interface itself non-nil.&lt;/p&gt;

&lt;p&gt;This kind of bug often appears only in production, when a rarely used error path finally executes.&lt;/p&gt;

&lt;p&gt;The fix is simple but strict: never return a typed nil as an interface. Return a real &lt;code&gt;nil&lt;/code&gt; or a real error — nothing in between.&lt;/p&gt;




&lt;h2&gt;
  
  
  Timeouts That Work Locally and Fail in Production
&lt;/h2&gt;

&lt;p&gt;Timeouts are another classic “it worked on my machine” trap.&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;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Locally, requests are fast. In staging, everything looks fine. In production, requests start timing out randomly.&lt;/p&gt;

&lt;p&gt;The difference is the network. DNS latency, TLS handshakes, slow upstreams, saturated connection pools — none of that exists on localhost.&lt;/p&gt;

&lt;p&gt;A single global timeout often hides where time is actually being spent. A more production-friendly approach is to put deadlines on requests themselves:&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;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;500&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;Millisecond&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;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="s"&gt;"GET"&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="n"&gt;resp&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;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production is not slow because Go is slow. It’s slow because networks are unreliable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Allocation Patterns That Break at Scale
&lt;/h2&gt;

&lt;p&gt;Many performance problems don’t come from algorithms, but from memory behavior that changes with scale.&lt;/p&gt;

&lt;p&gt;Code like this looks harmless:&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;buf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Maybe it runs once per request. Maybe it’s short-lived. Locally, no problem.&lt;/p&gt;

&lt;p&gt;In production, under sustained load, this creates constant pressure on the garbage collector. Large allocations must be zeroed, tracked, and scanned. Latency spikes appear, and p99 gets ugly.&lt;/p&gt;

&lt;p&gt;This is why production Go code often relies on reuse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bufPool&lt;/span&gt; &lt;span class="o"&gt;=&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;Pool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="m"&gt;10&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 GC in Go is very good, but it still obeys physics.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why These Bugs Feel “Production-Only”
&lt;/h2&gt;

&lt;p&gt;Because production is the first place where your code experiences:&lt;br&gt;
long uptimes, real concurrency, unreliable networks, large data, and sustained load.&lt;/p&gt;

&lt;p&gt;Go doesn’t hide these problems — it simply doesn’t simulate them for you.&lt;/p&gt;

&lt;p&gt;If you write Go as if production is calm and predictable, production will eventually disagree.&lt;/p&gt;




&lt;h3&gt;
  
  
  Want to go further?
&lt;/h3&gt;

&lt;p&gt;This series focuses on &lt;em&gt;understanding Go&lt;/em&gt;, not just using it.&lt;br&gt;&lt;br&gt;
If you want to continue in the same mindset, &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Educative&lt;/a&gt;&lt;/strong&gt; is a great next step.&lt;/p&gt;

&lt;p&gt;It’s a single subscription that gives you access to &lt;strong&gt;hundreds of in-depth, text-based courses&lt;/strong&gt; — from Go internals and concurrency to system design and distributed systems. No videos, no per-course purchases, just structured learning you can move through at your own pace.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Explore the full Educative library here&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Why allocs/op stopped being a good Go performance signal</title>
      <dc:creator>Pavel Sanikovich</dc:creator>
      <pubDate>Sun, 11 Jan 2026 14:56:08 +0000</pubDate>
      <link>https://forem.com/devflex-pro/why-allocsop-stopped-being-a-good-go-performance-signal-29ia</link>
      <guid>https://forem.com/devflex-pro/why-allocsop-stopped-being-a-good-go-performance-signal-29ia</guid>
      <description>&lt;p&gt;Most Go performance advice still revolves around one metric:&lt;br&gt;
&lt;strong&gt;reduce allocs/op&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Avoid heap allocations.&lt;br&gt;
Preallocate everything.&lt;br&gt;
Use &lt;code&gt;sync.Pool&lt;/code&gt;.&lt;br&gt;
Be careful with interfaces.&lt;/p&gt;

&lt;p&gt;All of that sounds reasonable — and used to be useful.&lt;/p&gt;

&lt;p&gt;But after benchmarking modern Go (1.25) in isolation, I realized that &lt;strong&gt;allocation count alone no longer predicts performance in a meaningful way&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One benchmark in particular completely changed my mental model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Retention vs Allocation
&lt;/h2&gt;

&lt;p&gt;Consider the following experiment.&lt;/p&gt;

&lt;p&gt;Both variants below perform &lt;strong&gt;the exact same number of allocations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The only difference is &lt;strong&gt;how much memory they retain&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bad retention
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;~1.5 ms/op&lt;/li&gt;
&lt;li&gt;~8 MB/op&lt;/li&gt;
&lt;li&gt;129 allocs/op&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Good retention
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;~90 µs/op&lt;/li&gt;
&lt;li&gt;~11 KB/op&lt;/li&gt;
&lt;li&gt;129 allocs/op&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s a &lt;strong&gt;~16× difference in runtime&lt;/strong&gt; with &lt;strong&gt;identical allocation counts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The GC doesn’t care how many objects you allocated.&lt;br&gt;
It cares how much memory stays reachable — and for how long.&lt;/p&gt;

&lt;p&gt;Once you see this, a lot of familiar advice starts to feel incomplete.&lt;/p&gt;




&lt;h2&gt;
  
  
  When allocs/op lies
&lt;/h2&gt;

&lt;p&gt;I also benchmarked a few other “classic” optimization targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;new(int)&lt;/code&gt; vs stack allocation → &lt;strong&gt;0 allocs/op in both cases&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;interface vs concrete calls → measurable overhead, &lt;strong&gt;no allocation or GC cost&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sync.Pool&lt;/code&gt; → often pure overhead when allocations don’t hit the heap&lt;/li&gt;
&lt;li&gt;slice over-preallocation → fewer allocations, &lt;em&gt;worse performance&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In isolation, many of these micro-optimizations either don’t matter anymore — or optimize the wrong layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  The real shift in modern Go performance
&lt;/h2&gt;

&lt;p&gt;What changed is not a single runtime tweak or compiler trick.&lt;/p&gt;

&lt;p&gt;The shift is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mechanical costs got cheaper.&lt;br&gt;
Architectural costs dominate.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Modern Go rewards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clear ownership&lt;/li&gt;
&lt;li&gt;short-lived data&lt;/li&gt;
&lt;li&gt;explicit lifetimes&lt;/li&gt;
&lt;li&gt;bounded concurrency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Old advice assumed the runtime was fragile.&lt;br&gt;
In modern Go, the runtime is usually fine — &lt;strong&gt;it’s accidental retention and unclear lifetimes that hurt&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Allocations still matter.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;allocation count alone is a poor proxy for performance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you reduce allocs/op without controlling object lifetime, you often optimize the wrong thing.&lt;/p&gt;




&lt;p&gt;I published the full deep dive with &lt;strong&gt;all benchmarks, code, and detailed explanations&lt;/strong&gt; here:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://blog.devflex.pro/why-most-go-performance-advice-is-outdated-go-125-edition" rel="noopener noreferrer"&gt;https://blog.devflex.pro/why-most-go-performance-advice-is-outdated-go-125-edition&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;interfaces vs generics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sync.Pool&lt;/code&gt; trade-offs&lt;/li&gt;
&lt;li&gt;slice growth strategies&lt;/li&gt;
&lt;li&gt;retention vs allocation&lt;/li&gt;
&lt;li&gt;and why modern Go performance problems often look like architecture bugs&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>performance</category>
      <category>programming</category>
      <category>backend</category>
    </item>
    <item>
      <title>Why context.Context Is Not a Cancel Button (And Why That Matters)</title>
      <dc:creator>Pavel Sanikovich</dc:creator>
      <pubDate>Fri, 26 Dec 2025 10:21:25 +0000</pubDate>
      <link>https://forem.com/devflex-pro/go-from-zero-to-depth-part-8-context-isnt-cancellation-its-a-protocol-4adp</link>
      <guid>https://forem.com/devflex-pro/go-from-zero-to-depth-part-8-context-isnt-cancellation-its-a-protocol-4adp</guid>
      <description>&lt;p&gt;context.Context is often treated as a cancellation mechanism.&lt;/p&gt;

&lt;p&gt;But cancellation is just one small part of what context is designed to do.&lt;/p&gt;

&lt;p&gt;Most misuse of context.Context comes from misunderstanding its role as a protocol for request-scoped data and lifecycle management.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is part of a practical guide on how Go works under the hood.&lt;br&gt;&lt;br&gt;
&lt;a href="https://dev.to/devflex-pro/go-from-zero-to-depth-part-1-the-hidden-simplicity-of-go-3326"&gt;Start here&lt;/a&gt; if you want the full picture &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most beginners meet &lt;code&gt;context.Context&lt;/code&gt; as a strange requirement. Functions suddenly demand an extra argument. You pass &lt;code&gt;context.Background()&lt;/code&gt; everywhere, add &lt;code&gt;WithCancel&lt;/code&gt; or &lt;code&gt;WithTimeout&lt;/code&gt; when a tutorial tells you to, and move on. The code compiles, tests pass, and the meaning of context remains vague.&lt;/p&gt;

&lt;p&gt;This is unfortunate, because &lt;code&gt;context&lt;/code&gt; is one of the clearest expressions of Go’s philosophy. It is not a helper. It is not just cancellation. It is a protocol for coordinating lifetimes across boundaries you don’t control.&lt;/p&gt;

&lt;p&gt;To understand context, you need to forget the idea that it is about stopping things. Cancellation is only a symptom. The real purpose of context is &lt;em&gt;scope&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let’s start with the simplest example:&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does nothing. It has no deadline, no cancellation signal, no values. And yet it is not useless. It establishes a root. Every real context grows from a root. Without that root, lifetimes become unstructured.&lt;/p&gt;

&lt;p&gt;Now consider a server handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="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="n"&gt;process&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;That single line carries a huge amount of information. It tells you that &lt;code&gt;process&lt;/code&gt; must respect the lifetime of the HTTP request. If the client disconnects, times out, or cancels the request, &lt;code&gt;process&lt;/code&gt; must stop. This is not optional. This is part of the contract.&lt;/p&gt;

&lt;p&gt;That contract is what makes context a protocol.&lt;/p&gt;

&lt;p&gt;Look at a typical mistake:&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;process&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;work&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 goroutine ignores the context entirely. It outlives the request. It may continue working long after the client is gone. This is not a bug you can catch with tests. It is a design error.&lt;/p&gt;

&lt;p&gt;The correct version looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;process&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;work&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;And inside:&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;work&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="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="n"&gt;doWork&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;Now the lifetime is explicit. The goroutine exists &lt;em&gt;because&lt;/em&gt; the context exists. When the context ends, so does the work. This is not cancellation as an afterthought; it is structured concurrency.&lt;/p&gt;

&lt;p&gt;This is why &lt;code&gt;context.Background()&lt;/code&gt; in deep layers is often a smell. It breaks the chain. It creates work with no owner.&lt;/p&gt;

&lt;p&gt;Contexts are meant to be passed, not created arbitrarily. A function should only create a new context when it is defining a new lifetime boundary.&lt;/p&gt;

&lt;p&gt;For example:&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;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a sub-scope. It says: “This operation must finish within one second, regardless of what the parent allows.” That is a design decision, not a convenience.&lt;/p&gt;

&lt;p&gt;Another common misunderstanding is using context to pass data.&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="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;"userID"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but it should make you uncomfortable. Context values are for request-scoped metadata that crosses API boundaries, not for normal parameters. If a function requires a value to operate correctly, it should take it explicitly. Context is for data that &lt;em&gt;must&lt;/em&gt; flow everywhere but &lt;em&gt;should not&lt;/em&gt; define behavior.&lt;/p&gt;

&lt;p&gt;Good examples include request IDs, authentication tokens, trace IDs. Bad examples include business logic flags, configuration, or domain objects.&lt;/p&gt;

&lt;p&gt;This distinction matters because context values are invisible. They bypass the type system. Overusing them turns APIs into riddles.&lt;/p&gt;

&lt;p&gt;Another important aspect of context is that it is read-only. You cannot cancel a context you didn’t create. This enforces ownership. If a function receives a context, it can observe cancellation but cannot control it. Only the creator owns the right to end the lifetime.&lt;/p&gt;

&lt;p&gt;This mirrors everything you’ve learned so far. Ownership. Lifetimes. Boundaries.&lt;/p&gt;

&lt;p&gt;Contexts also compose naturally. A deadline implies cancellation. Cancellation propagates downward. Values flow with the scope. All of this happens without explicit wiring between layers. The protocol is implicit, but the behavior is consistent.&lt;/p&gt;

&lt;p&gt;Consider 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="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="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="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;taskA&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="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;taskB&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both tasks share the same fate. When the context expires, both must stop. You didn’t need a channel. You didn’t need shared variables. The protocol handled it.&lt;/p&gt;

&lt;p&gt;This is why context works so well with goroutines and channels. It doesn’t replace them. It coordinates them.&lt;/p&gt;

&lt;p&gt;Beginners often treat context as boilerplate. Experienced Go developers treat it as a map of responsibility. If you follow the protocol, your programs shut down cleanly, release resources predictably, and behave well under failure. If you ignore it, you get goroutines that never die and bugs that appear only under load.&lt;/p&gt;

&lt;p&gt;The key idea is simple: &lt;strong&gt;every goroutine should have a reason to exist, and that reason should be tied to a context&lt;/strong&gt;. When the reason disappears, so should the goroutine.&lt;/p&gt;

&lt;p&gt;Once you internalize this, you stop scattering &lt;code&gt;context.Background()&lt;/code&gt; and start thinking in terms of lifetimes. That shift is subtle, but it’s where Go codebases become robust.&lt;/p&gt;

&lt;p&gt;By now, the pattern should be unmistakable. Go is not minimal because it lacks features. It is minimal because it gives you just enough structure to make responsibility visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Article Focuses On
&lt;/h2&gt;

&lt;p&gt;This article is not a style guide.&lt;/p&gt;

&lt;p&gt;It explains what context.Context is &lt;em&gt;meant&lt;/em&gt; to represent — and why treating it as a simple cancel switch leads to fragile code.&lt;/p&gt;




&lt;h3&gt;
  
  
  Want to go further?
&lt;/h3&gt;

&lt;p&gt;This series focuses on &lt;em&gt;understanding Go&lt;/em&gt;, not just using it.&lt;br&gt;&lt;br&gt;
If you want to continue in the same mindset, &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Educative&lt;/a&gt;&lt;/strong&gt; is a great next step.&lt;/p&gt;

&lt;p&gt;It’s a single subscription that gives you access to &lt;strong&gt;hundreds of in-depth, text-based courses&lt;/strong&gt; — from Go internals and concurrency to system design and distributed systems. No videos, no per-course purchases, just structured learning you can move through at your own pace.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Explore the full Educative library here&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;

</description>
      <category>go</category>
      <category>beginners</category>
      <category>programming</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>Stop Using Go Channels as Queues — Here’s the Correct Mental Model</title>
      <dc:creator>Pavel Sanikovich</dc:creator>
      <pubDate>Wed, 24 Dec 2025 10:50:54 +0000</pubDate>
      <link>https://forem.com/devflex-pro/go-from-zero-to-depth-part-7-channels-are-not-queues-a-correct-mental-model-2f1f</link>
      <guid>https://forem.com/devflex-pro/go-from-zero-to-depth-part-7-channels-are-not-queues-a-correct-mental-model-2f1f</guid>
      <description>&lt;p&gt;Go channels are often treated as queues.&lt;/p&gt;

&lt;p&gt;They look like queues.&lt;br&gt;
They behave like queues — at least at first.&lt;/p&gt;

&lt;p&gt;But designing systems around this assumption is one of the fastest ways to introduce deadlocks, backpressure issues, and subtle concurrency bugs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is part of a practical guide on how Go works under the hood.&lt;br&gt;&lt;br&gt;
&lt;a href="https://dev.to/devflex-pro/go-from-zero-to-depth-part-1-the-hidden-simplicity-of-go-3326"&gt;Start here&lt;/a&gt; if you want the full picture &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Channels are one of the most recognizable features of Go. Beginners often fall in love with them instantly. They look clean. They look safe. They feel like a built-in solution to concurrency. And then, slowly, problems appear. Deadlocks. Goroutines that never exit. Pipelines that stall under load. Systems that work perfectly in tests and fail in production.&lt;/p&gt;

&lt;p&gt;Almost always, the root cause is the same misunderstanding: &lt;strong&gt;channels are treated like queues&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They are not.&lt;/p&gt;

&lt;p&gt;A channel is not a data structure. It is a synchronization mechanism that happens to move values. If you treat it like a queue, you will eventually design yourself into a corner. If you treat it like a coordination tool, your concurrent code becomes simpler, safer, and easier to reason about.&lt;/p&gt;

&lt;p&gt;Let’s rebuild the mental model from scratch.&lt;/p&gt;

&lt;p&gt;When you write 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="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things happen, not one.&lt;br&gt;
A value moves from sender to receiver.&lt;br&gt;
Execution is synchronized.&lt;/p&gt;

&lt;p&gt;The send cannot complete until the receive is ready. The receive cannot complete until the send happens. This handshake is the essence of an unbuffered channel. The value transfer is almost secondary.&lt;/p&gt;

&lt;p&gt;This is why unbuffered channels are sometimes called &lt;em&gt;rendezvous points&lt;/em&gt;. They force goroutines to meet.&lt;/p&gt;

&lt;p&gt;Now let’s add a buffer:&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;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&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;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this looks like a queue. You put things in, you take things out. But the semantics are already different. The buffer only delays synchronization; it does not remove it. The third send will still block. The receive will still establish ordering. The channel is still about coordination, just with more slack.&lt;/p&gt;

&lt;p&gt;This distinction matters more than it seems.&lt;/p&gt;

&lt;p&gt;Beginners often write code 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="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then wonder: who closes the channel? What happens if the producer exits early? What happens if the consumer is slower? What happens if there are multiple consumers?&lt;/p&gt;

&lt;p&gt;These questions feel annoying at first. But they are not implementation details. They are design questions. Channels force you to answer them because channels encode &lt;em&gt;ownership&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Only the sender should close a channel.&lt;br&gt;
Closing a channel signals “no more values will ever arrive.”&lt;br&gt;
Closing is not cleanup. It is a broadcast event.&lt;/p&gt;

&lt;p&gt;This is why closing a channel from the receiver side is almost always a bug.&lt;/p&gt;

&lt;p&gt;Consider this pattern:&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;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This loop only exits when the channel is closed. That means the sender controls the lifetime of the receiver. This is not accidental. It is the channel expressing a protocol.&lt;/p&gt;

&lt;p&gt;Problems begin when channels are used without a protocol.&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;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="c"&gt;// Who closes ch?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This channel has no clear owner. No termination signal. No lifetime rule. It works for now — until it doesn’t.&lt;/p&gt;

&lt;p&gt;Now let’s talk about buffering mistakes.&lt;/p&gt;

&lt;p&gt;Beginners often add a buffer to “fix” a deadlock.&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;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deadlock disappears. The program runs. Everyone is happy. Until memory grows. Or latency spikes. Or goroutines pile up. Buffers do not fix coordination problems. They hide them.&lt;/p&gt;

&lt;p&gt;A buffer should exist because it models reality, not because it makes the program stop blocking.&lt;/p&gt;

&lt;p&gt;For example, a worker pool:&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;jobs&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&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 the buffer represents a queue of pending work. That is a valid use. But even here, the channel is still enforcing coordination. If producers outrun consumers, the buffer fills and applies backpressure. This is not an accident — it’s a feature.&lt;/p&gt;

&lt;p&gt;Another common beginner mistake is assuming channels imply fairness.&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;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}()&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="n"&gt;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="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no guarantee which value arrives first. Channels do not schedule goroutines. The scheduler does. Channels only synchronize &lt;em&gt;when&lt;/em&gt; communication happens, not &lt;em&gt;who&lt;/em&gt; gets to communicate first.&lt;/p&gt;

&lt;p&gt;This is why channels should not be used to encode priority or ordering unless you explicitly design for it.&lt;/p&gt;

&lt;p&gt;One of the most powerful realizations is this: &lt;strong&gt;channels are best used at the boundaries of ownership&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Inside a goroutine, use normal variables.&lt;br&gt;
Between goroutines, use channels to hand off responsibility.&lt;/p&gt;

&lt;p&gt;For example:&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;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goroutine owns &lt;code&gt;result&lt;/code&gt;. The moment it sends it, ownership transfers. After the send, the goroutine should not touch that value again. This mental rule eliminates an entire class of bugs.&lt;/p&gt;

&lt;p&gt;This also explains why sharing pointers through channels is dangerous unless you are careful. The channel does not protect you from shared mutable state. It only synchronizes the moment of transfer.&lt;/p&gt;

&lt;p&gt;Channels are not magical. They are explicit. And that explicitness is what makes them powerful.&lt;/p&gt;

&lt;p&gt;When beginners struggle with channels, it is usually not because channels are hard. It’s because the design is unclear. Who owns what? Who decides when things stop? What does closing mean? What does blocking mean?&lt;/p&gt;

&lt;p&gt;Once you answer those questions, channels stop feeling mysterious.&lt;/p&gt;

&lt;p&gt;By now, a pattern should be emerging.&lt;br&gt;
Memory model → lifetimes.&lt;br&gt;
Pointers → ownership.&lt;br&gt;
Goroutines → scheduling.&lt;br&gt;
Channels → coordination.&lt;/p&gt;

&lt;p&gt;Everything fits together.&lt;/p&gt;

&lt;p&gt;In the next part, we’ll talk about &lt;code&gt;context&lt;/code&gt;. Not as a cancellation trick, but as a protocol that ties together lifetimes, goroutines, and ownership across API boundaries. This is where many Go codebases quietly go wrong — and where understanding pays off immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Article Focuses On
&lt;/h2&gt;

&lt;p&gt;This article is not about channel syntax.&lt;/p&gt;

&lt;p&gt;It focuses on the mental model behind channels — what guarantees they provide, and what they explicitly do not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want to go further?
&lt;/h3&gt;

&lt;p&gt;This series focuses on &lt;em&gt;understanding Go&lt;/em&gt;, not just using it.&lt;br&gt;&lt;br&gt;
If you want to continue in the same mindset, &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Educative&lt;/a&gt;&lt;/strong&gt; is a great next step.&lt;/p&gt;

&lt;p&gt;It’s a single subscription that gives you access to &lt;strong&gt;hundreds of in-depth, text-based courses&lt;/strong&gt; — from Go internals and concurrency to system design and distributed systems. No videos, no per-course purchases, just structured learning you can move through at your own pace.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Explore the full Educative library here&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;

</description>
      <category>go</category>
      <category>beginners</category>
      <category>programming</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>What the Goroutine Scheduler Really Does (And Why It Matters)</title>
      <dc:creator>Pavel Sanikovich</dc:creator>
      <pubDate>Sat, 20 Dec 2025 10:12:15 +0000</pubDate>
      <link>https://forem.com/devflex-pro/go-from-zero-to-depth-part-6-the-goroutine-scheduler-the-part-nobody-tells-beginners-5f7i</link>
      <guid>https://forem.com/devflex-pro/go-from-zero-to-depth-part-6-the-goroutine-scheduler-the-part-nobody-tells-beginners-5f7i</guid>
      <description>&lt;p&gt;Goroutines are often described as “cheap threads”.&lt;/p&gt;

&lt;p&gt;That description is convenient — and misleading.&lt;/p&gt;

&lt;p&gt;To understand why certain concurrency issues appear only under load, you need a basic mental model of how the goroutine scheduler actually works.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is part of a practical guide on how Go works under the hood.&lt;br&gt;&lt;br&gt;
&lt;a href="https://dev.to/devflex-pro/go-from-zero-to-depth-part-1-the-hidden-simplicity-of-go-3326"&gt;Start here&lt;/a&gt; if you want the full picture &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At some point every Go developer asks the same question: &lt;em&gt;“How does Go actually run my goroutines?”&lt;/em&gt;&lt;br&gt;
Beginners are often told not to worry about it. “The runtime handles it.” “It just works.” “Goroutines are cheap.” All of that is true — and completely insufficient once your programs grow beyond toy examples.&lt;/p&gt;

&lt;p&gt;To write reliable concurrent Go code, you don’t need to know everything about the scheduler. But you &lt;em&gt;do&lt;/em&gt; need a mental model. Without it, performance issues feel random, deadlocks feel mysterious, and race conditions feel unfair. With it, many problems become predictable.&lt;/p&gt;

&lt;p&gt;Let’s build that model.&lt;/p&gt;

&lt;p&gt;When you write this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you are not creating a thread. You are creating a &lt;strong&gt;goroutine&lt;/strong&gt;, which is a lightweight task managed entirely by the Go runtime. Goroutines live in user space, not in the operating system. They are scheduled by Go, not by the OS.&lt;/p&gt;

&lt;p&gt;This distinction matters.&lt;/p&gt;

&lt;p&gt;The Go scheduler is based on a simple but powerful idea, often called the &lt;strong&gt;M–P–G model&lt;/strong&gt;. You don’t need to memorize the letters, but you need to understand what they represent.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;G&lt;/strong&gt; is a goroutine. It contains the function to execute, its stack, and some metadata.&lt;br&gt;
An &lt;strong&gt;M&lt;/strong&gt; is an OS thread. It executes machine instructions.&lt;br&gt;
A &lt;strong&gt;P&lt;/strong&gt; is a processor — a logical resource that connects goroutines to threads.&lt;/p&gt;

&lt;p&gt;A goroutine does not run unless it is assigned to a P, and a P does not run unless it is attached to an M. This indirection is what gives Go its flexibility.&lt;/p&gt;

&lt;p&gt;The number of Ps is controlled by &lt;code&gt;GOMAXPROCS&lt;/code&gt;. By default, it equals the number of CPU cores.&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;GOMAXPROCS&lt;/code&gt; is 4, Go can run up to four goroutines &lt;em&gt;in parallel&lt;/em&gt;. You can create a million goroutines, but only four will execute simultaneously. The rest are waiting in queues.&lt;/p&gt;

&lt;p&gt;This already explains something many beginners find confusing: &lt;strong&gt;creating many goroutines does not mean doing many things at once&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Consider this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;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;id&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="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;"goroutine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;i&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 order of output is unpredictable. This is not because Go is random, but because scheduling is opportunistic. Goroutines are placed in local run queues. Ps steal work from each other when idle. The runtime tries to be fair, but it does not guarantee order.&lt;/p&gt;

&lt;p&gt;Now let’s introduce blocking.&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;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;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;"done"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this goroutine sleeps, it does &lt;em&gt;not&lt;/em&gt; block the entire program. The runtime parks the goroutine and frees the P to run something else. This is why goroutines are cheap. Blocking calls like &lt;code&gt;time.Sleep&lt;/code&gt;, channel operations, and network I/O cooperate with the scheduler.&lt;/p&gt;

&lt;p&gt;But not all blocking is equal.&lt;/p&gt;

&lt;p&gt;Consider this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This goroutine never blocks. It spins forever. On a single-core system, this can starve other goroutines. The scheduler will eventually preempt it, but preemption is not instantaneous. This is why CPU-bound loops should yield naturally or be designed carefully.&lt;/p&gt;

&lt;p&gt;Now consider system calls.&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;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"large_file.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a goroutine enters a blocking syscall, the runtime detaches the M from the P and creates or reuses another M so that other goroutines can continue running. This is expensive compared to normal scheduling, but still far cheaper than managing threads manually.&lt;/p&gt;

&lt;p&gt;This explains why Go performs well for I/O-heavy workloads: blocking I/O does not block the scheduler.&lt;/p&gt;

&lt;p&gt;Now let’s look at a classic beginner pitfall involving loops and goroutines:&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;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This often prints &lt;code&gt;3 3 3&lt;/code&gt;. Not because the scheduler is broken, but because &lt;strong&gt;all goroutines capture the same variable&lt;/strong&gt;, and execution happens later. The scheduler delays execution long enough for the loop to finish.&lt;/p&gt;

&lt;p&gt;The fix is not about timing. It’s about ownership:&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;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;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;v&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here each goroutine owns its own copy. Scheduling no longer changes correctness.&lt;/p&gt;

&lt;p&gt;Let’s talk about fairness.&lt;/p&gt;

&lt;p&gt;The scheduler uses &lt;strong&gt;work stealing&lt;/strong&gt;. Each P has a local run queue. When it runs out of work, it steals from others. This improves cache locality and reduces contention. But it also means goroutines may not run in the order you expect.&lt;/p&gt;

&lt;p&gt;This matters when beginners assume round-robin execution:&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;go&lt;/span&gt; &lt;span class="n"&gt;printA&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;printB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no guarantee that &lt;code&gt;printA&lt;/code&gt; and &lt;code&gt;printB&lt;/code&gt; will interleave evenly. One might run to completion before the other even starts.&lt;/p&gt;

&lt;p&gt;Now combine this with channels:&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;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Which value prints first? You don’t know. The scheduler decides which goroutine runs first, and channels only guarantee synchronization — not fairness.&lt;/p&gt;

&lt;p&gt;Understanding this prevents subtle bugs.&lt;/p&gt;

&lt;p&gt;One more example that surprises beginners:&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;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GOMAXPROCS&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="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="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;"A"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="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;"B"&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;Even with one OS thread, both goroutines will run. Go uses cooperative scheduling with preemption. But the output will be uneven. One goroutine may dominate for a while before being interrupted.&lt;/p&gt;

&lt;p&gt;This is why relying on scheduling behavior for correctness is a mistake. Correct concurrent programs work under &lt;em&gt;any&lt;/em&gt; scheduling order.&lt;/p&gt;

&lt;p&gt;The Go scheduler is not something to fight. It is something to design for. When your goroutines have clear lifetimes, when blocking points are intentional, when shared state is explicit, the scheduler becomes invisible — and that’s the goal.&lt;/p&gt;

&lt;p&gt;In the next part, we’ll zoom in on channels themselves. We’ll explain why channels are not queues, why buffering changes semantics, and how channel patterns shape program structure. This is where many concurrency designs either become elegant — or collapse under their own complexity.&lt;/p&gt;

&lt;p&gt;By now, you should feel it: Go’s simplicity is not hiding chaos. It’s hiding a system that rewards clarity. The scheduler is not your enemy. It’s your silent partner.&lt;/p&gt;

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

&lt;p&gt;This article doesn’t dive into runtime source code.&lt;/p&gt;

&lt;p&gt;It explains the scheduler at a conceptual level — just enough to reason about performance, blocking, and unexpected behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want to go further?
&lt;/h3&gt;

&lt;p&gt;This series focuses on &lt;em&gt;understanding Go&lt;/em&gt;, not just using it.&lt;br&gt;&lt;br&gt;
If you want to continue in the same mindset, &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Educative&lt;/a&gt;&lt;/strong&gt; is a great next step.&lt;/p&gt;

&lt;p&gt;It’s a single subscription that gives you access to &lt;strong&gt;hundreds of in-depth, text-based courses&lt;/strong&gt; — from Go internals and concurrency to system design and distributed systems. No videos, no per-course purchases, just structured learning you can move through at your own pace.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Explore the full Educative library here&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;

</description>
      <category>go</category>
      <category>beginners</category>
      <category>programming</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>Concurrency in Go Is Easy — Until You Get It Wrong</title>
      <dc:creator>Pavel Sanikovich</dc:creator>
      <pubDate>Tue, 16 Dec 2025 14:35:44 +0000</pubDate>
      <link>https://forem.com/devflex-pro/go-from-zero-to-depth-part-5-concurrency-in-go-is-easy-until-you-get-it-wrong-jin</link>
      <guid>https://forem.com/devflex-pro/go-from-zero-to-depth-part-5-concurrency-in-go-is-easy-until-you-get-it-wrong-jin</guid>
      <description>&lt;p&gt;Go makes concurrency look simple.&lt;/p&gt;

&lt;p&gt;A few goroutines, a couple of channels — and everything seems to work.&lt;/p&gt;

&lt;p&gt;Until it doesn’t.&lt;/p&gt;

&lt;p&gt;Most production concurrency bugs in Go come not from complex patterns, but from a small set of wrong assumptions about how goroutines actually interact.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is part of a practical guide on how Go works under the hood.&lt;br&gt;&lt;br&gt;
&lt;a href="https://dev.to/devflex-pro/go-from-zero-to-depth-part-1-the-hidden-simplicity-of-go-3326"&gt;Start here&lt;/a&gt; if you want the full picture &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Concurrency is the reason many people choose Go. You write &lt;code&gt;go func() {}&lt;/code&gt; once, and suddenly your program feels modern, fast, and powerful. Goroutines are cheap. Channels feel intuitive. The syntax is friendly. Everything seems to work — until one day it doesn’t. And when it breaks, it breaks in ways that are deeply confusing for beginners.&lt;/p&gt;

&lt;p&gt;This is not a flaw in Go. It’s a consequence of how concurrency really works.&lt;/p&gt;

&lt;p&gt;Most beginner tutorials teach concurrency as a mechanical skill. Start a goroutine. Send data through a channel. Use a &lt;code&gt;WaitGroup&lt;/code&gt;. Avoid races. These rules are useful, but they don’t explain &lt;em&gt;why&lt;/em&gt; certain patterns are safe and others are dangerous. Without a mental model, concurrency becomes cargo cult programming: copy a pattern, hope it works, and pray nothing strange happens in production.&lt;/p&gt;

&lt;p&gt;To understand concurrency in Go, you need to stop thinking in terms of threads and start thinking in terms of &lt;em&gt;coordination and ownership&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let’s begin with the illusion.&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;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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;"Hello from goroutine"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks harmless. It usually works. But what actually happened? You did not “start a thread.” You asked the Go runtime to schedule a function for concurrent execution. You don’t control when it runs. You don’t control where it runs. You don’t even know if it runs at all before the program exits.&lt;/p&gt;

&lt;p&gt;This is the first important idea: &lt;strong&gt;goroutines are not threads, and concurrency is not parallelism&lt;/strong&gt;. Goroutines are tasks. The runtime decides how and when they execute. Sometimes they run in parallel. Sometimes they don’t. Sometimes thousands of them share a single OS thread.&lt;/p&gt;

&lt;p&gt;Once you accept that loss of control, things start to make sense.&lt;/p&gt;

&lt;p&gt;Now consider shared memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code compiles. It may print &lt;code&gt;42&lt;/code&gt;. It may print &lt;code&gt;0&lt;/code&gt;. It may work for years and then break after a minor refactor. This is not a timing issue. It’s a &lt;strong&gt;memory visibility issue&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Concurrency is not just about doing things at the same time. It’s about &lt;em&gt;when changes become visible to other parts of the program&lt;/em&gt;. Without synchronization, Go makes no promises. This is why data races are not “bugs you sometimes see.” They are undefined behavior.&lt;/p&gt;

&lt;p&gt;Beginners often try to fix this by adding sleep calls:&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;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not synchronization. This is gambling.&lt;/p&gt;

&lt;p&gt;The correct fix is not “wait longer.” The correct fix is to establish a happens-before relationship. In Go, this usually means communication.&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;done&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
    &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the program has a guarantee. The receive on &lt;code&gt;done&lt;/code&gt; happens after the write to &lt;code&gt;x&lt;/code&gt;. This is not a convention. It is a formal property of Go’s memory model.&lt;/p&gt;

&lt;p&gt;This leads to one of the most important ideas in Go concurrency: &lt;strong&gt;don’t share memory by communicating — communicate by sharing memory&lt;/strong&gt;. This sentence is often quoted, but rarely understood. It does not mean “never use shared variables.” It means that &lt;em&gt;coordination should happen through communication&lt;/em&gt;, not through ad-hoc shared state.&lt;/p&gt;

&lt;p&gt;Channels are not queues. They are synchronization points.&lt;/p&gt;

&lt;p&gt;Consider this example:&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;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The channel does two things at once. It transfers a value, and it synchronizes execution. The send cannot complete until the receive happens. This creates a clear ordering in time and memory. That ordering is the real value.&lt;/p&gt;

&lt;p&gt;Problems begin when beginners treat channels as generic data pipes without thinking about ownership.&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;go&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Who owns the channel? Who closes it? What happens if one consumer exits early? These are not stylistic questions. They are correctness questions. Every concurrent design must answer them explicitly.&lt;/p&gt;

&lt;p&gt;Another common beginner mistake is assuming goroutines are “fire and forget.”&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;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks elegant, but it hides several problems. How many goroutines are you spawning? What happens if &lt;code&gt;process&lt;/code&gt; blocks? What if it panics? What if it allocates memory faster than the GC can keep up? Concurrency magnifies every inefficiency.&lt;/p&gt;

&lt;p&gt;This is why experienced Go developers often prefer boring patterns: worker pools, bounded concurrency, explicit lifetimes. Not because they love ceremony, but because these patterns make ownership and limits visible.&lt;/p&gt;

&lt;p&gt;Here is a safer pattern:&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;sem&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="m"&gt;10&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;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
    &lt;span class="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;val&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="p"&gt;}()&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now concurrency is bounded. Memory usage is predictable. Failure modes are controlled. This is what “thinking concurrently” actually means.&lt;/p&gt;

&lt;p&gt;Concurrency also interacts deeply with everything you learned earlier. Closures extend lifetimes. Pointers introduce shared state. Escape analysis moves values to the heap. Goroutines delay execution. Combine these carelessly, and you get subtle bugs that are impossible to reason about without a solid mental model.&lt;/p&gt;

&lt;p&gt;This is why Go concurrency feels easy at first and hard later. The syntax removes friction, but the responsibility remains. Go does not protect you from bad design; it gives you tools that make good design explicit.&lt;/p&gt;

&lt;p&gt;The goal is not to avoid concurrency. The goal is to structure it so that ownership is clear, lifetimes are bounded, and communication defines order. When you do that, Go concurrency becomes not only safe, but elegant.&lt;/p&gt;

&lt;p&gt;In the next part, we’ll go one level deeper and look at what the runtime actually does when you start a goroutine. We’ll explore the scheduler, the M–P–G model, and why understanding it changes how you write concurrent code. This is where many developers finally stop guessing and start predicting behavior.&lt;/p&gt;

&lt;p&gt;Concurrency is not magic. It’s discipline, expressed through a language that rewards clarity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Article Focuses On
&lt;/h2&gt;

&lt;p&gt;This is not a concurrency tutorial.&lt;/p&gt;

&lt;p&gt;It explains why certain concurrency patterns &lt;em&gt;look correct&lt;/em&gt; but break under load, scheduling, or subtle timing differences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want to go further?
&lt;/h3&gt;

&lt;p&gt;This series focuses on &lt;em&gt;understanding Go&lt;/em&gt;, not just using it.&lt;br&gt;&lt;br&gt;
If you want to continue in the same mindset, &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Educative&lt;/a&gt;&lt;/strong&gt; is a great next step.&lt;/p&gt;

&lt;p&gt;It’s a single subscription that gives you access to &lt;strong&gt;hundreds of in-depth, text-based courses&lt;/strong&gt; — from Go internals and concurrency to system design and distributed systems. No videos, no per-course purchases, just structured learning you can move through at your own pace.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Explore the full Educative library here&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;

</description>
      <category>go</category>
      <category>beginners</category>
      <category>programming</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>Pointers in Go Are Simple — Until You Misunderstand What They Actually Point To</title>
      <dc:creator>Pavel Sanikovich</dc:creator>
      <pubDate>Sun, 14 Dec 2025 11:53:58 +0000</pubDate>
      <link>https://forem.com/devflex-pro/go-from-zero-to-depth-part-4-pointers-in-go-not-scary-just-misunderstood-4bbh</link>
      <guid>https://forem.com/devflex-pro/go-from-zero-to-depth-part-4-pointers-in-go-not-scary-just-misunderstood-4bbh</guid>
      <description>&lt;p&gt;Pointers in Go are often described as “simple”.&lt;/p&gt;

&lt;p&gt;And syntactically, they are.&lt;/p&gt;

&lt;p&gt;But most bugs involving pointers don’t come from syntax — they come from misunderstanding &lt;em&gt;what a pointer actually represents&lt;/em&gt; and how it relates to memory, variables, and values.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is part of a practical guide on how Go works under the hood.&lt;br&gt;&lt;br&gt;
&lt;a href="https://dev.to/devflex-pro/go-from-zero-to-depth-part-1-the-hidden-simplicity-of-go-3326"&gt;Start here&lt;/a&gt; if you want the full picture &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pointers are where many beginners quietly lose confidence. Even developers with years of experience in other languages often treat pointers in Go with suspicion. They either avoid them entirely or overuse them without understanding the consequences. Both approaches miss the point. In Go, pointers are neither dangerous nor advanced. They are simply the language’s way of expressing ownership and lifetime.&lt;/p&gt;

&lt;p&gt;The confusion usually comes from baggage. In C or C++, pointers are a source of power and catastrophe at the same time. They allow arithmetic, manual memory management, and undefined behavior. In Go, all of that is gone. You cannot move a pointer arbitrarily. You cannot free memory manually. You cannot corrupt the heap through pointer tricks. What remains is a safe, constrained tool with a very specific purpose.&lt;/p&gt;

&lt;p&gt;A pointer in Go does exactly one thing: it allows multiple parts of a program to refer to the same value. That’s it. No magic. No danger. Just shared access.&lt;/p&gt;

&lt;p&gt;Consider the most basic example:&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;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;incrementPtr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&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;Calling the first function changes nothing outside its scope. Calling the second modifies the caller’s value. The difference is not about mutability; it’s about ownership. The value version receives a copy. The pointer version receives access to the original.&lt;/p&gt;

&lt;p&gt;Once you see pointers as a question of ownership, many rules that felt arbitrary suddenly make sense.&lt;/p&gt;

&lt;p&gt;This is why Go encourages passing small structs by value. When you pass a value, you make ownership explicit. The function receives its own copy and cannot accidentally modify shared state. This leads to code that is easier to reason about, easier to test, and easier to parallelize.&lt;/p&gt;

&lt;p&gt;Now compare these two functions:&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;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Alice"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&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;processPtr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Alice"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first function modifies only its local copy. The second modifies shared state. Neither is wrong. But the second carries more responsibility. Any function that accepts a pointer is making a promise: “I might change this.” In Go, that promise is visible in the function signature. That visibility is intentional.&lt;/p&gt;

&lt;p&gt;Pointers also interact directly with memory lifetimes, which you saw in Part 3. Returning a pointer means extending lifetime. Capturing a pointer in a closure means sharing state. Passing pointers between goroutines means introducing synchronization concerns. None of these are flaws; they are explicit tradeoffs.&lt;/p&gt;

&lt;p&gt;A common beginner question is whether pointers are faster. The honest answer is: sometimes, and often not in the way you expect. Copying a small struct is cheap. Copying a pointer may force a heap allocation. The real cost is not the pointer itself, but the lifetime extension it causes.&lt;/p&gt;

&lt;p&gt;This is why Go APIs often look “odd” to newcomers. For example, methods on slices and maps rarely use pointers to slices or maps. That’s because slices and maps already contain internal pointers. Passing them by value still shares underlying data. A pointer to a slice is usually unnecessary and often harmful.&lt;/p&gt;

&lt;p&gt;Consider this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though &lt;code&gt;nums&lt;/code&gt; is passed by value, the underlying array is shared. The pointer semantics are already there, hidden inside the slice header. Adding an extra pointer layer does not give you more power; it only complicates lifetimes.&lt;/p&gt;

&lt;p&gt;Another area where pointers confuse beginners is method receivers. Go allows both value and pointer receivers, and many people assume one is “more correct” than the other. In reality, the choice expresses intent.&lt;/p&gt;

&lt;p&gt;A value receiver means the method does not modify the receiver’s logical state. A pointer receiver means it might. The compiler will automatically take the address or dereference as needed, so this is not about convenience. It’s about communication.&lt;/p&gt;

&lt;p&gt;This becomes especially important in concurrent programs. Shared mutable state is the hardest problem in software. Go’s pointer rules make sharing explicit. When you pass a pointer into a goroutine, you are saying: “This value will be accessed concurrently.” That statement alone should make you think about synchronization, ownership, and boundaries.&lt;/p&gt;

&lt;p&gt;One of the reasons Go feels simple is that it avoids cleverness. Pointers are not overloaded with meaning. They do not unlock hidden capabilities. They simply make sharing visible. And visibility is the foundation of maintainable systems.&lt;/p&gt;

&lt;p&gt;Beginners often try to avoid pointers entirely, hoping to stay in a “safe zone.” Ironically, this leads to worse code. The goal is not to avoid pointers but to understand them well enough to use them intentionally. When you do, pointers stop being scary and start being descriptive.&lt;/p&gt;

&lt;p&gt;By now, you should see a clear chain forming. Go’s memory model is based on lifetimes. Escape analysis enforces those lifetimes. Pointers express shared ownership and extended lifetimes. Together, they form a coherent system. Nothing is accidental.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Article Is (and Isn’t)
&lt;/h2&gt;

&lt;p&gt;This is not a pointer tutorial.&lt;/p&gt;

&lt;p&gt;It focuses on the &lt;em&gt;mental model&lt;/em&gt; behind pointers in Go — why they behave the way they do, and why certain assumptions lead to subtle bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want to go further?
&lt;/h3&gt;

&lt;p&gt;This series focuses on &lt;em&gt;understanding Go&lt;/em&gt;, not just using it.&lt;br&gt;&lt;br&gt;
If you want to continue in the same mindset, &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Educative&lt;/a&gt;&lt;/strong&gt; is a great next step.&lt;/p&gt;

&lt;p&gt;It’s a single subscription that gives you access to &lt;strong&gt;hundreds of in-depth, text-based courses&lt;/strong&gt; — from Go internals and concurrency to system design and distributed systems. No videos, no per-course purchases, just structured learning you can move through at your own pace.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Explore the full Educative library here&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;

</description>
      <category>go</category>
      <category>beginners</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Stack vs Heap in Go: How Escape Analysis Actually Works</title>
      <dc:creator>Pavel Sanikovich</dc:creator>
      <pubDate>Fri, 12 Dec 2025 10:28:31 +0000</pubDate>
      <link>https://forem.com/devflex-pro/go-from-zero-to-depth-part-3-stack-vs-heap-how-escape-analysis-actually-works-48dc</link>
      <guid>https://forem.com/devflex-pro/go-from-zero-to-depth-part-3-stack-vs-heap-how-escape-analysis-actually-works-48dc</guid>
      <description>&lt;p&gt;One of the most common questions Go developers ask is whether a value is allocated on the stack or on the heap.&lt;/p&gt;

&lt;p&gt;And just as often, the question itself is slightly wrong.&lt;/p&gt;

&lt;p&gt;In Go, stack vs heap is not a manual decision — it’s a result of escape analysis, and misunderstanding this leads to performance myths and unnecessary micro-optimizations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is part of a practical guide on how Go works under the hood.&lt;br&gt;&lt;br&gt;
&lt;a href="https://dev.to/devflex-pro/go-from-zero-to-depth-part-1-the-hidden-simplicity-of-go-3326"&gt;Start here&lt;/a&gt; if you want the full picture &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’ve ever profiled a Go program and wondered why a simple function allocates memory, or why a tiny struct suddenly ends up on the heap, you’ve seen the effects of escape analysis. Beginners often learn stack and heap as if they were fixed rules — small things go on the stack, large things go on the heap — but Go doesn’t work like that at all. Size is irrelevant. What matters is lifetime.&lt;/p&gt;

&lt;p&gt;A value stays on the stack only if the compiler can prove it never outlives the function that created it. The moment the lifetime becomes ambiguous, that value “escapes,” and Go places it on the heap. This is not a heuristic and not guesswork; it is a strict safety rule.&lt;/p&gt;

&lt;p&gt;Understanding this rule gives you x-ray vision into your Go programs. You start predicting allocations before they happen. You see how small changes in code shape memory behavior. And most importantly, you learn to write Go the way the compiler expects — which results in faster, cleaner, more predictable programs.&lt;/p&gt;

&lt;p&gt;Let's walk through this from the ground up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Article Focuses On
&lt;/h2&gt;

&lt;p&gt;This article is not about forcing allocations onto the stack or the heap.&lt;/p&gt;

&lt;p&gt;It explains how Go’s escape analysis works, why certain values escape, and why trying to “outsmart” the compiler usually backfires.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Stack: Fast, Local, Temporary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A stack frame exists only during the execution of a function. When the function returns, the frame disappears. If a value can be proven to stay within that frame, it is stack allocated.&lt;/p&gt;

&lt;p&gt;A simple example:&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;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we ask the compiler to show escape analysis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ go build -gcflags="-m"
&amp;lt;no escape&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing escapes. Everything is on the stack. The compiler is even free to inline the function, meaning the variables may never exist as “variables” at all — they become registers or constants.&lt;/p&gt;

&lt;p&gt;This is the ideal path: pure stack behavior, no GC pressure, no heap work.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Heap: For Values with Extended Lifetime&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A value must live on the heap if something outside the current stack frame needs to reference it. Returning a pointer is the most common example:&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;makePtr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compiler output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./main.go:4:9: &amp;amp;x escapes to heap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has nothing to do with the size of &lt;code&gt;x&lt;/code&gt;. The compiler simply sees that the caller needs a reference to &lt;code&gt;x&lt;/code&gt; after the function returns. The stack frame cannot hold it anymore.&lt;/p&gt;

&lt;p&gt;A beginner often doesn’t realize that the &lt;em&gt;pointer itself&lt;/em&gt; is not expensive — it’s the lifetime extension that forces the escape.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Closures: When Variables Quietly Escape&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Closures are a classic place where beginners accidentally create heap allocations.&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;counter&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="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&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;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;x&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;x&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;Compiler output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./main.go:6:13: func literal escapes to heap
./main.go:5:5: moved to heap: x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why?&lt;br&gt;
Because the returned function continues to exist after &lt;code&gt;counter&lt;/code&gt; finishes. It needs access to &lt;code&gt;x&lt;/code&gt;.&lt;br&gt;
Therefore, &lt;code&gt;x&lt;/code&gt; must move to the heap, where its lifetime is no longer tied to the stack frame.&lt;/p&gt;

&lt;p&gt;Many beginners write closure-based code without realizing they are allocating memory every time.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Two Constructors, Two Lifetimes, Two Allocation Patterns&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is one of the clearest ways to see escape analysis in action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version 1 — returning a value:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&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;func&lt;/span&gt; &lt;span class="n"&gt;newUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;User&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;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now version 2 — returning a pointer:&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;newUserPtr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the first version, the compiler often places &lt;code&gt;User&lt;/code&gt; directly into the caller’s stack frame.&lt;br&gt;
For the pointer version, the allocation must occur on the heap.&lt;/p&gt;

&lt;p&gt;Same data. Same fields. Same size.&lt;br&gt;
Different lifetime = different memory behavior.&lt;/p&gt;

&lt;p&gt;This is why experienced Go developers say: &lt;strong&gt;prefer returning values unless you need shared mutable state.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Escape Analysis Loves Clear Ownership&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s a subtle example where a tiny rewrite prevents a heap escape:&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;sumSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;total&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;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./main.go:7:12: &amp;amp;total escapes to heap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if we write it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;sumSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="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;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;no escape&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exact same logic. Different lifetime semantics.&lt;/p&gt;

&lt;p&gt;This is the power of understanding escape analysis: your intuition becomes aligned with the compiler.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;A Surprising Case: Heap Allocations Without Pointers&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Sometimes a heap escape happens even though you don’t return a pointer. A classic example:&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;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="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;Compiler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./main.go:5:10: func literal escapes to heap
./main.go:4:6: moved to heap: i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why?&lt;br&gt;
Because the goroutine runs &lt;em&gt;after the loop iteration completes&lt;/em&gt;.&lt;br&gt;
It needs access to &lt;code&gt;i&lt;/code&gt;.&lt;br&gt;
Therefore &lt;code&gt;i&lt;/code&gt; cannot live on the stack.&lt;/p&gt;

&lt;p&gt;This is the precise moment when a beginner realizes that concurrency also changes lifetimes.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;What Escape Analysis Is Actually Doing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It’s not trying to optimize your code.&lt;br&gt;
It’s proving safety.&lt;/p&gt;

&lt;p&gt;If the compiler can prove a value is local → stack.&lt;br&gt;
If it cannot prove → heap.&lt;/p&gt;

&lt;p&gt;It’s a conservative algorithm. A value will escape even when theoretically safe, simply because proving otherwise would require solving undecidable problems. Go plays it safe; that’s what keeps programs correct.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;How to See Escape Analysis in Your Code&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;You can observe everything the compiler decides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go build -gcflags="-m=2"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or for even more detail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go build -gcflags="-m -m"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This becomes addictive. You begin scanning your code and &lt;em&gt;predicting&lt;/em&gt; escapes before the compiler prints them.&lt;/p&gt;

&lt;p&gt;Once you reach that level, Go feels like a language that explains itself to you.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Why This Matters for Beginners&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Understanding escape analysis is not about premature optimization.&lt;br&gt;
It’s about forming the mental model that Go expects you to have.&lt;/p&gt;

&lt;p&gt;Once you understand lifetimes:&lt;/p&gt;

&lt;p&gt;– you choose value vs pointer intentionally&lt;br&gt;
– you design APIs that minimize hidden allocations&lt;br&gt;
– your code scales better under load&lt;br&gt;
– you avoid concurrency pitfalls&lt;br&gt;
– you become predictable to the compiler&lt;/p&gt;

&lt;p&gt;This is exactly where beginners stop being beginners.&lt;/p&gt;




&lt;h3&gt;
  
  
  Want to go further?
&lt;/h3&gt;

&lt;p&gt;This series focuses on &lt;em&gt;understanding Go&lt;/em&gt;, not just using it.&lt;br&gt;&lt;br&gt;
If you want to continue in the same mindset, &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Educative&lt;/a&gt;&lt;/strong&gt; is a great next step.&lt;/p&gt;

&lt;p&gt;It’s a single subscription that gives you access to &lt;strong&gt;hundreds of in-depth, text-based courses&lt;/strong&gt; — from Go internals and concurrency to system design and distributed systems. No videos, no per-course purchases, just structured learning you can move through at your own pace.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Explore the full Educative library here&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;

</description>
      <category>go</category>
      <category>beginners</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Go Memory Model Explained Simply (But Correctly)</title>
      <dc:creator>Pavel Sanikovich</dc:creator>
      <pubDate>Wed, 10 Dec 2025 13:49:46 +0000</pubDate>
      <link>https://forem.com/devflex-pro/go-from-zero-to-depth-part-2-go-memory-model-explained-simply-but-correctly-1n63</link>
      <guid>https://forem.com/devflex-pro/go-from-zero-to-depth-part-2-go-memory-model-explained-simply-but-correctly-1n63</guid>
      <description>&lt;p&gt;Most developers assume that if code “looks sequential”, it behaves sequentially.&lt;/p&gt;

&lt;p&gt;In Go, this assumption is often wrong.&lt;/p&gt;

&lt;p&gt;The Go memory model is small, precise, and intentionally restrictive — but many concurrency bugs come from misunderstanding what it &lt;em&gt;does not&lt;/em&gt; guarantee.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is part of a practical guide on how Go works under the hood.&lt;br&gt;&lt;br&gt;
&lt;a href="https://dev.to/devflex-pro/go-from-zero-to-depth-part-1-the-hidden-simplicity-of-go-3326"&gt;Start here&lt;/a&gt; if you want the full picture &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Beginners usually hear a reassuring story about memory in Go: small things live on the stack, large things live on the heap, the garbage collector handles cleanup, and escape analysis is a mysterious optimization pass that decides where everything goes. This story is simple, but it hides the actual forces that shape how Go works. The reality is both subtler and more interesting, and once you see it, the way you write Go code changes forever.&lt;/p&gt;

&lt;p&gt;The right way to understand Go’s memory model is to forget about stack and heap for a moment and focus instead on the nature of values. Go is a value-oriented language. Everything starts with a value: an integer, a struct, a slice header, a map handle. The crucial question is not “where” the value lives but “how long” it needs to live. Lifetime, not size, governs Go’s memory behavior.&lt;/p&gt;

&lt;p&gt;A stack frame lives only as long as a function call. If a value can stay within that frame safely, it will. That decision is the essence of escape analysis. Nothing mystical is happening: the compiler simply looks at each value and decides whether it can remain tied to the call stack or whether something forces it to survive beyond the function that created it.&lt;/p&gt;

&lt;p&gt;To see how straightforward this is, consider the simplest possible example:&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;makeNumber&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&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 ask Go to print escape analysis results, you will see nothing dramatic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ go build -gcflags="-m"
&amp;lt;no escape&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler sees that &lt;code&gt;x&lt;/code&gt; never leaves this function except as a copy. It can happily live on the stack.&lt;/p&gt;

&lt;p&gt;But now make a tiny change:&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;makeNumberPtr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the same command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./main.go:4:9: &amp;amp;x escapes to heap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value &lt;code&gt;x&lt;/code&gt; must now outlive the stack frame of &lt;code&gt;makeNumberPtr&lt;/code&gt;, because the caller receives a pointer to it. Go cannot keep &lt;code&gt;x&lt;/code&gt; on the stack anymore. Nothing about the size or complexity changed. Only the lifetime changed. The moment you return a pointer, your value escapes.&lt;/p&gt;

&lt;p&gt;This is where beginners usually pause. Returning a pointer feels harmless, even idiomatic, yet it changes how memory is managed. This is why thinking in terms of ownership is more important than memorizing stack vs heap rules. A pointer implies shared ownership. A value implies independent ownership. The compiler simply enforces this distinction.&lt;/p&gt;

&lt;p&gt;Closures reveal this idea even more clearly. Consider this common pattern:&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;counter&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="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&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;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;x&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;x&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 &lt;code&gt;x&lt;/code&gt; is referenced by a function literal that survives after &lt;code&gt;counter&lt;/code&gt; returns. Go performs the only correct action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./main.go:6:13: func literal escapes to heap
./main.go:5:5: moved to heap: x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run the program, every call increments the same &lt;code&gt;x&lt;/code&gt;, and that would be impossible if &lt;code&gt;x&lt;/code&gt; had stayed on the stack. The heap is not a performance penalty here; it is the correct expression of the variable’s lifetime.&lt;/p&gt;

&lt;p&gt;Not all escapes are required. Some are accidental. A lot of performance issues in Go come from patterns that unknowingly extend lifetimes.&lt;/p&gt;

&lt;p&gt;Compare two versions of a constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&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;func&lt;/span&gt; &lt;span class="n"&gt;newUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;User&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;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the pointer version:&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;newUserPtr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the first case, &lt;code&gt;User&lt;/code&gt; is returned by value. The compiler can often avoid heap allocation entirely by placing the struct directly in the caller’s stack frame. In the second case, returning a pointer forces escape. A beginner may prefer pointer semantics because they “feel cleaner,” but the cost appears only when you look deeper.&lt;/p&gt;

&lt;p&gt;The same applies to slices and maps. A slice header is a small struct containing a pointer to underlying data. Returning a slice does not necessarily cause a heap escape; returning a pointer to part of a slice almost always does. Ownership and lifetime drive everything.&lt;/p&gt;

&lt;p&gt;The garbage collector fits neatly into this picture. It is not sweeping memory randomly; it is following chains of references established by your program. If nothing points to a value, the value dies. If something still points to it, the value lives. This makes clarity of ownership a performance feature. The simpler your reference graph, the less work the GC performs.&lt;/p&gt;

&lt;p&gt;Understanding all of this does not require deep systems knowledge. It requires only the willingness to look under the surface and recognize that Go’s model is not magical. It is logical. And once you see the logic, your intuition sharpens. You start predicting when a value will escape. You notice unnecessary pointers. You understand how closures extend lifetimes. You become aware that Go optimizes for the code you write, not the code you intended.&lt;/p&gt;

&lt;p&gt;The Go memory model is approachable because the language removes distractions. There is no manual memory management, no pointer arithmetic, no undefined behavior. What remains is a direct connection between the structure of your program and the shape of its memory. For a beginner, discovering this is often the moment Go stops looking “too simple” and starts looking deeply intentional.&lt;/p&gt;

&lt;p&gt;The value of Go lies not in hidden complexity but in transparent mechanisms that reward clear thinking. The memory model is the first place where this becomes unmistakable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want to go further?
&lt;/h3&gt;

&lt;p&gt;This series focuses on &lt;em&gt;understanding Go&lt;/em&gt;, not just using it.&lt;br&gt;&lt;br&gt;
If you want to continue in the same mindset, &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Educative&lt;/a&gt;&lt;/strong&gt; is a great next step.&lt;/p&gt;

&lt;p&gt;It’s a single subscription that gives you access to &lt;strong&gt;hundreds of in-depth, text-based courses&lt;/strong&gt; — from Go internals and concurrency to system design and distributed systems. No videos, no per-course purchases, just structured learning you can move through at your own pace.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Explore the full Educative library here&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;

</description>
      <category>go</category>
      <category>beginners</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Go Under the Hood: Memory, Concurrency, and Mental Models Most Developers Get Wrong</title>
      <dc:creator>Pavel Sanikovich</dc:creator>
      <pubDate>Wed, 10 Dec 2025 13:33:55 +0000</pubDate>
      <link>https://forem.com/devflex-pro/go-from-zero-to-depth-part-1-the-hidden-simplicity-of-go-3326</link>
      <guid>https://forem.com/devflex-pro/go-from-zero-to-depth-part-1-the-hidden-simplicity-of-go-3326</guid>
      <description>&lt;p&gt;Go is often described as a “simple” language.&lt;/p&gt;

&lt;p&gt;And syntactically, it is.&lt;/p&gt;

&lt;p&gt;But many production bugs in Go don’t come from complex code — they come from &lt;em&gt;incorrect mental models&lt;/em&gt; about how Go actually works under the hood.&lt;/p&gt;

&lt;p&gt;This guide is a collection of practical explanations about Go’s execution, memory, and concurrency model — not as a tutorial, but as a way to fix the most common wrong assumptions developers make.&lt;/p&gt;

&lt;h2&gt;
  
  
  Articles in This Guide
&lt;/h2&gt;

&lt;p&gt;Each article below is standalone — you can read them in any order.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Go Memory Model Explained Simply (But Correctly) &lt;a href="https://dev.to/devflex-pro/go-from-zero-to-depth-part-2-go-memory-model-explained-simply-but-correctly-1n63"&gt;LINK&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stack vs Heap in Go &amp;amp; How Escape Analysis Actually Works &lt;a href="https://dev.to/devflex-pro/go-from-zero-to-depth-part-3-stack-vs-heap-how-escape-analysis-actually-works-48dc"&gt;LINK&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pointers in Go Are Simple — Until You Misunderstand What They Actually Point To &lt;a href="https://dev.to/devflex-pro/go-from-zero-to-depth-part-4-pointers-in-go-not-scary-just-misunderstood-4bbh"&gt;LINK&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Read This Guide
&lt;/h2&gt;

&lt;p&gt;These articles are not tutorials.&lt;/p&gt;

&lt;p&gt;They focus on &lt;em&gt;why things behave the way they do&lt;/em&gt; in Go — especially in situations where intuition often fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want to go further?
&lt;/h3&gt;

&lt;p&gt;This series focuses on &lt;em&gt;understanding Go&lt;/em&gt;, not just using it.&lt;br&gt;&lt;br&gt;
If you want to continue in the same mindset, &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Educative&lt;/a&gt;&lt;/strong&gt; is a great next step.&lt;/p&gt;

&lt;p&gt;It’s a single subscription that gives you access to &lt;strong&gt;hundreds of in-depth, text-based courses&lt;/strong&gt; — from Go internals and concurrency to system design and distributed systems. No videos, no per-course purchases, just structured learning you can move through at your own pace.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.educative.io/unlimited?aff=BXPO&amp;amp;utm_source=devto&amp;amp;utm_medium=go-series&amp;amp;utm_campaign=affiliate" rel="noopener noreferrer"&gt;Explore the full Educative library here&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;

</description>
      <category>go</category>
      <category>beginners</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How Vue Reactivity Actually Works Under the Hood (A Simple Explanation With Internals)</title>
      <dc:creator>Pavel Sanikovich</dc:creator>
      <pubDate>Mon, 01 Dec 2025 15:00:55 +0000</pubDate>
      <link>https://forem.com/devflex-pro/how-vue-reactivity-actually-works-under-the-hood-a-simple-explanation-with-internals-3ope</link>
      <guid>https://forem.com/devflex-pro/how-vue-reactivity-actually-works-under-the-hood-a-simple-explanation-with-internals-3ope</guid>
      <description>&lt;p&gt;If you’ve been working with Vue for a while, you’ve probably learned the rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ref()&lt;/code&gt; holds a value&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reactive()&lt;/code&gt; makes an object reactive&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;computed()&lt;/code&gt; derives state&lt;/li&gt;
&lt;li&gt;changing state triggers a re-render&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But none of that explains &lt;em&gt;why&lt;/em&gt; it works.&lt;/p&gt;

&lt;p&gt;Why does changing &lt;code&gt;count.value&lt;/code&gt; make the component update?&lt;br&gt;
Why does Vue know which component depends on which variable?&lt;br&gt;
What exactly happens during &lt;code&gt;render()&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;To really understand Vue — and to write scalable, predictable code — you need to understand what’s happening &lt;em&gt;under the hood&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let’s open that black box.&lt;/p&gt;

&lt;p&gt;And don’t worry — I’m going to explain this without computer-science jargon.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;The simple idea behind all of Vue: “track” and “trigger”&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The entire Vue reactivity system — every ref, reactive object, computed, watcher — is built on two operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;track()   // remember that some code used this value
trigger() // notify all code that depends on this value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;Every time you &lt;em&gt;read&lt;/em&gt; a reactive value → Vue calls &lt;code&gt;track()&lt;/code&gt;.&lt;br&gt;
Every time you &lt;em&gt;write&lt;/em&gt; to a reactive value → Vue calls &lt;code&gt;trigger()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Vue keeps a mapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reactiveValue → list of functions that should re-run when it changes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those “functions” are typically the component’s render function, or a computed getter, or a watcher.&lt;/p&gt;

&lt;p&gt;This is the entire reactivity system in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Vue re-runs functions that used a reactive value when that value changes.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rest is implementation detail.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Let’s build a tiny Vue-like reactivity system from scratch&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Vue 3 uses &lt;code&gt;Proxy&lt;/code&gt; under the hood to intercept reads and writes.&lt;/p&gt;

&lt;p&gt;Here’s a tiny toy example that mirrors how Vue works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;activeEffect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;effects&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;Map&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;activeEffect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeEffect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;
      &lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now watch the magic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;activeEffect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;
  &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;activeEffect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="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="s2"&gt;count changed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="c1"&gt;// → logs: "count changed: 1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations — this is the &lt;em&gt;core idea&lt;/em&gt; of Vue reactivity.&lt;/p&gt;

&lt;p&gt;Vue’s real system is more advanced — optimized dependency tracking, cleanup, effect scopes, watcher flushing — but the foundations are exactly this.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;How &lt;code&gt;ref()&lt;/code&gt; works internally&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;ref&lt;/code&gt; is just a reactive object with a &lt;code&gt;.value&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;Inside Vue, the implementation is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RefImpl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;trackRefValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_value&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt;
    &lt;span class="nf"&gt;triggerRefValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why you must use &lt;code&gt;.value&lt;/code&gt; — it’s where access tracking happens.&lt;/p&gt;

&lt;p&gt;When you write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vue performs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;track:&lt;/strong&gt; remember that some component/computed uses &lt;code&gt;count.value&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;trigger:&lt;/strong&gt; re-run those functions when the value changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;How &lt;code&gt;reactive()&lt;/code&gt; works internally&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Vue wraps your object in a &lt;code&gt;Proxy&lt;/code&gt;. The proxy intercepts all &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;set&lt;/code&gt; operations.&lt;/p&gt;

&lt;p&gt;When you read a property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vue calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;track(state.user, "name")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you change one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sarah&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vue calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trigger(state.user, "name")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets Vue know exactly &lt;em&gt;which fields of which objects&lt;/em&gt; your UI depends on.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The real magic: dependency tracking&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s a key insight junior developers often miss:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Vue tracks dependencies at runtime, not at compile time.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means Vue knows exactly &lt;em&gt;which&lt;/em&gt; reactive variables your component used &lt;em&gt;while rendering&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Consider this component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;double&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;double&lt;/code&gt; is evaluated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it reads &lt;code&gt;count.value&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;so Vue records: “double depends on count”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus, when &lt;code&gt;count.value&lt;/code&gt; changes → &lt;code&gt;double&lt;/code&gt; re-runs automatically.&lt;/p&gt;

&lt;p&gt;Vue builds a dependency graph dynamically, every time reactive values are used.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;computed values update automatically&lt;/li&gt;
&lt;li&gt;watchers run when the reactive value they use changes&lt;/li&gt;
&lt;li&gt;components re-render when data they accessed changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Why components re-render&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Inside a component’s render function, Vue does this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;evaluates the template → which reads reactive values&lt;/li&gt;
&lt;li&gt;tracks each read → maps reactive values → render function&lt;/li&gt;
&lt;li&gt;stores these dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Later, if a variable changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vue sees which render functions depend on it&lt;/li&gt;
&lt;li&gt;schedules those components to update&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So updating &lt;code&gt;state.count&lt;/code&gt; will &lt;em&gt;only&lt;/em&gt; re-render components that used &lt;code&gt;state.count&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is why Vue apps can stay performant even with lots of components.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Why some values don't trigger updates&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Vue tracks &lt;strong&gt;accesses&lt;/strong&gt;, not assignments.&lt;/p&gt;

&lt;p&gt;Consider this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;items&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push mutates an array &lt;em&gt;without calling set on the array property&lt;/em&gt; (because the reference didn’t change).&lt;/p&gt;

&lt;p&gt;But Vue patches array methods like &lt;code&gt;push&lt;/code&gt; to call &lt;code&gt;trigger()&lt;/code&gt; internally — otherwise arrays wouldn’t be reactive.&lt;/p&gt;

&lt;p&gt;If you create a custom object with methods, Vue &lt;strong&gt;will not&lt;/strong&gt; track them unless they use reactive data.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Why destructuring breaks reactivity&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This confuses a lot of beginners:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;   &lt;span class="c1"&gt;// ❌ reactivity lost&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because you copy &lt;code&gt;state.count&lt;/code&gt; into a standalone variable.&lt;br&gt;
It no longer goes through the reactive proxy.&lt;/p&gt;

&lt;p&gt;This explains why Pinia recommends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ breaks reactivity:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;

&lt;span class="c1"&gt;// ✔ keeps reactivity:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;storeToRefs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Why watchers run too often&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Because a watcher reacts to &lt;strong&gt;any change of any reactive variable used inside it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="s2"&gt;User changed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This watcher triggers whenever &lt;em&gt;any property&lt;/em&gt; of &lt;code&gt;state.user&lt;/code&gt; changes.&lt;/p&gt;

&lt;p&gt;Vue isn't being "weird".&lt;br&gt;
It’s doing exactly what you told it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Tell me whenever this entire object changes.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want precision:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="s2"&gt;Name changed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Why your app re-renders too much&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Every unintended use of reactive data inside a render or computed function adds a dependency.&lt;/p&gt;

&lt;p&gt;Vue will track it.&lt;/p&gt;

&lt;p&gt;Vue will re-run it.&lt;/p&gt;

&lt;p&gt;Vue will re-render.&lt;/p&gt;

&lt;p&gt;Even if it was an accident.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Final mental model (save this forever)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you truly understand this sentence, you understand Vue:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Vue re-runs anything that used a reactive value when that value changes.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s the whole system.&lt;/p&gt;

&lt;p&gt;Everything else — computed, watchers, refs, reactive objects — is just different UI around the same engine.&lt;/p&gt;

&lt;p&gt;Once this clicks, Vue stops feeling magical and starts feeling predictable.&lt;/p&gt;




</description>
      <category>vue</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Why Your Vue Code Becomes Messy (And How to Keep It Clean From the Start)</title>
      <dc:creator>Pavel Sanikovich</dc:creator>
      <pubDate>Mon, 01 Dec 2025 14:47:20 +0000</pubDate>
      <link>https://forem.com/devflex-pro/why-your-vue-code-becomes-messy-and-how-to-keep-it-clean-from-the-start-5fie</link>
      <guid>https://forem.com/devflex-pro/why-your-vue-code-becomes-messy-and-how-to-keep-it-clean-from-the-start-5fie</guid>
      <description>&lt;p&gt;Most Vue developers don’t notice when their codebase starts falling apart.&lt;br&gt;
At first everything feels simple: a clean component, a small API call, a few reactive variables, a couple of watchers. Vue makes early progress feel effortless.&lt;/p&gt;

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

&lt;p&gt;A new screen.&lt;br&gt;
A new flow.&lt;br&gt;
Another API call.&lt;br&gt;
A bit of duplicated state “for now.”&lt;br&gt;
A watcher added as a quick fix.&lt;br&gt;
A temporary boolean to fix an edge case.&lt;br&gt;
A composable created without a clear purpose.&lt;br&gt;
A Pinia store that now holds &lt;em&gt;everything&lt;/em&gt; because “it’s global anyway.”&lt;/p&gt;

&lt;p&gt;Before you realize it, the app becomes unpredictable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;components re-render for no reason,&lt;/li&gt;
&lt;li&gt;data changes in places you didn’t expect,&lt;/li&gt;
&lt;li&gt;bugs appear when two features accidentally depend on the same state,&lt;/li&gt;
&lt;li&gt;and removing old code feels terrifying because you’re not sure what it breaks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article explains &lt;strong&gt;why this happens&lt;/strong&gt;, and more importantly, &lt;strong&gt;how to prevent it from happening again&lt;/strong&gt; — even if you're still early in your Vue journey.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;The real reason Vue apps get messy&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Vue doesn’t force you into architecture.&lt;br&gt;
It gives you freedom — sometimes too much freedom.&lt;/p&gt;

&lt;p&gt;You can put state anywhere: inside components, inside composables, inside Pinia, inside random refs declared in the global scope. You can fetch data inside &lt;code&gt;mounted()&lt;/code&gt;, inside composables, inside watchers, inside actions, or inside event handlers.&lt;/p&gt;

&lt;p&gt;There are no strict rules, and that means junior developers often do what feels natural:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I need data → I’ll put it here.”&lt;br&gt;
“I need this flag → I’ll add it here.”&lt;br&gt;
“I need to update something → let me add a watcher.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This works perfectly… until features start interacting with each other.&lt;/p&gt;

&lt;p&gt;Let’s look at an innocent example.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;A small component that slowly becomes a monster&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;You start with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onMounted&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;

&lt;span class="nf"&gt;onMounted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProducts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing wrong here.&lt;/p&gt;

&lt;p&gt;Then someone adds filtering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;

&lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProducts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still fine.&lt;/p&gt;

&lt;p&gt;Then another team member adds a “selected product”.&lt;br&gt;
Then “draft product”.&lt;br&gt;
Then pagination.&lt;br&gt;
Then loading states.&lt;br&gt;
Then error states.&lt;/p&gt;

&lt;p&gt;Suddenly the component looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProducts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you feel it:&lt;br&gt;
&lt;strong&gt;you already lost control.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Why this happens: components are NOT meant to handle logic&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A Vue component should be mostly &lt;em&gt;declarative&lt;/em&gt;:&lt;br&gt;
“What should this UI show based on the state?”&lt;/p&gt;

&lt;p&gt;But junior developers often write components &lt;em&gt;imperatively&lt;/em&gt;:&lt;br&gt;
“What steps must I perform to make this work?”&lt;/p&gt;

&lt;p&gt;When you put API calls, derived data, transformation logic, and UI state all in the same file — components become untestable, unreadable, and impossible to refactor.&lt;/p&gt;

&lt;p&gt;The first step toward clean architecture is understanding this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Components should display state, not manage it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s extract logic.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Moving logic into a composable (the right way)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Instead of stuffing all logic inside the component, we move it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// useProducts.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;watch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useProducts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProducts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the component becomes clean again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useProducts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/composables/useProducts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useProducts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI becomes declarative.&lt;br&gt;
The logic becomes reusable.&lt;br&gt;
And the complexity stops growing &lt;strong&gt;inside the component&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is already a huge improvement — but it’s not enough.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;When state becomes shared between screens&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s the next problem junior developers hit:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two different components need the same data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product list page&lt;/li&gt;
&lt;li&gt;Product details page&lt;/li&gt;
&lt;li&gt;Admin product editor&lt;/li&gt;
&lt;li&gt;Recommendations sidebar&lt;/li&gt;
&lt;li&gt;Cart preview&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of them need “products,” but in slightly different ways.&lt;/p&gt;

&lt;p&gt;If each component creates its own instance of &lt;code&gt;useProducts()&lt;/code&gt;, you end up duplicating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API calls&lt;/li&gt;
&lt;li&gt;transformations&lt;/li&gt;
&lt;li&gt;caching&lt;/li&gt;
&lt;li&gt;error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where people jump to Pinia — but often do so incorrectly.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Pinia is not a storage box — it’s a state owner&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Many beginners treat Pinia as a place to dump everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useProductStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product&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;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// more and more...&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This leads to a store that knows too much, does too much, and depends on too many features.&lt;/p&gt;

&lt;p&gt;The correct approach is simpler:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pinia should own state — and only the state that truly belongs to the domain.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, API filters are UI state → they belong in a composable or component.&lt;/p&gt;

&lt;p&gt;But the &lt;em&gt;data itself&lt;/em&gt;?&lt;br&gt;
That belongs to the domain → it belongs in a store.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;A clean, beginner-friendly store structure&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s how a clean store might look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// product.store.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useProductStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product&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;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;isLoaded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;

  &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProducts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;getters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;byId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is simple, clean, scalable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The store owns the data.&lt;/li&gt;
&lt;li&gt;The composable owns the behavior.&lt;/li&gt;
&lt;li&gt;The component owns the UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once this separation clicks, Vue suddenly becomes effortless to scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The moment things start to feel “clean” again&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A junior developer becomes mid-level the moment they realize this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;not all state belongs in a component&lt;/li&gt;
&lt;li&gt;not all state belongs in Pinia&lt;/li&gt;
&lt;li&gt;not all logic belongs in a composable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A clean Vue architecture emerges when &lt;strong&gt;every layer has a single responsibility&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Components&lt;/strong&gt; show data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composables&lt;/strong&gt; coordinate behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stores&lt;/strong&gt; own global domain state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API modules&lt;/strong&gt; own network calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Types&lt;/strong&gt; describe structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reactivity&lt;/strong&gt; stays predictable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clean code is not magic.&lt;br&gt;
It’s the result of putting things where they belong.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;A short message for juniors reading this&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If your Vue code feels messy right now — that’s normal.&lt;/p&gt;

&lt;p&gt;It doesn’t mean you're bad at writing components.&lt;br&gt;
It means you're ready to start learning architecture.&lt;/p&gt;

&lt;p&gt;Your goal isn’t to memorize APIs.&lt;br&gt;
Your goal is to understand how data should move through an app.&lt;/p&gt;

&lt;p&gt;Once you learn that, scaling will feel natural.&lt;/p&gt;




</description>
      <category>vue</category>
      <category>frontend</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
