<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Escape-Analysis on Tachera W Sasi, Backend &amp; Infra Engineer (Go, Self-Hosted, Observability)</title><link>https://tacherasasi.github.io/tags/escape-analysis/</link><description>Recent content in Escape-Analysis on Tachera W Sasi, Backend &amp; Infra Engineer (Go, Self-Hosted, Observability)</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Fri, 20 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tacherasasi.github.io/tags/escape-analysis/index.xml" rel="self" type="application/rss+xml"/><item><title>Escape Analysis in Go: Where Does Your Data Actually Live?</title><link>https://tacherasasi.github.io/posts/escape-analysis-in-go-where-does-your-data-actually-live-d1a0430003d8/</link><pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate><guid>https://tacherasasi.github.io/posts/escape-analysis-in-go-where-does-your-data-actually-live-d1a0430003d8/</guid><description>&lt;h2 id="2"&gt;&amp;mdash;2&amp;quot;&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://cdn-images-1.medium.com/max/300/1*S0qxt-hT88hXkOFCj-nSBw.jpeg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If you’ve been writing Go for a while, you’ve probably heard the terms &lt;strong&gt;stack&lt;/strong&gt; and &lt;strong&gt;heap&lt;/strong&gt; tossed around. But have you ever wondered how Go decides where to put your variables? That’s exactly what &lt;strong&gt;escape analysis&lt;/strong&gt; is about and understanding it can help you write faster, more efficient Go code.&lt;/p&gt;
&lt;h3 id="stack-vs-heap-a-quick-refresher"&gt;Stack vs Heap: A Quick Refresher&lt;/h3&gt;
&lt;p&gt;Think of memory in two buckets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stack&lt;/strong&gt; Fast, automatically managed. When a function is called, variables are pushed onto the stack. When the function returns, they’re gone. No cleanup needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Heap&lt;/strong&gt; Slower, but longer-lived. Variables here are managed by Go’s garbage collector (GC). They stick around as long as something is referencing them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The stack is cheap. The heap costs more allocating on the heap puts pressure on the GC, which can slow your program down.&lt;/p&gt;
&lt;h3 id="so-what-is-escape-analysis"&gt;So What Is Escape Analysis?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Escape analysis&lt;/strong&gt; is what the Go compiler does at compile time to figure out: &lt;em&gt;“Does this variable need to live on the heap, or can it stay on the stack?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If a variable is only used inside the function it was created in, it can safely live on the stack. But if a variable “escapes” meaning something outside the function needs to access it it has to be moved to the heap.&lt;/p&gt;
&lt;p&gt;The compiler does this automatically. You don’t have to think about it most of the time. But understanding when things escape can help you avoid unnecessary heap allocations.&lt;/p&gt;
&lt;h3 id="a-simple-example"&gt;A Simple Example&lt;/h3&gt;
&lt;p&gt;go&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, x is created and used inside main. It doesn&amp;rsquo;t escape anywhere. The compiler keeps it on the stack.&lt;/p&gt;
&lt;p&gt;Now consider this:&lt;/p&gt;
&lt;p&gt;go&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newNumber&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// we&amp;#39;re returning a pointer to x &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, we’re returning a &lt;strong&gt;pointer&lt;/strong&gt; to x. That means after newNumber returns, something outside the function is still holding a reference to x. The stack for newNumber is gone at that point so Go moves x to the heap. It has &lt;em&gt;escaped&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="how-to-see-whats-escaping"&gt;How to See What’s Escaping&lt;/h3&gt;
&lt;p&gt;Go gives you a handy compiler flag to inspect escape analysis decisions:&lt;/p&gt;
&lt;p&gt;bash&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go build -gcflags&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-m&amp;#34;&lt;/span&gt; ./...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You’ll see output like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./main.go:6:2: moved to heap: x
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./main.go:10:13: ... argument does not escape
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This tells you exactly which variables the compiler decided to allocate on the heap. It’s a great debugging tool when you’re trying to optimize performance.&lt;/p&gt;
&lt;h3 id="common-reasons-a-variable-escapes"&gt;Common Reasons a Variable Escapes&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Returning a pointer to a local variable&lt;/strong&gt; As shown above if you return &amp;amp;x, x has to live on the heap.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Storing a value in an interface&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;go&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you assign a concrete value to an interface, Go often needs to heap-allocate it because the interface value needs to carry around type information.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Sending to a channel or storing in a slice/map&lt;/strong&gt; If the runtime can’t prove the value won’t outlive the current scope, it plays it safe and puts it on the heap.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Closures capturing variables&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;go&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;makeCounter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, count is captured by the closure returned from makeCounter. Since the closure can be called long after makeCounter returns, count escapes to the heap.&lt;/p&gt;
&lt;h3 id="does-this-matter-in-practice"&gt;Does This Matter in Practice?&lt;/h3&gt;
&lt;p&gt;For most applications, not really Go’s garbage collector is fast and well-tuned. But in performance-sensitive code (think: game loops, high-frequency trading, large-scale data pipelines), reducing heap allocations can make a meaningful difference.&lt;/p&gt;
&lt;p&gt;Here’s a rough guide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Don’t return pointers to small, short-lived values&lt;/strong&gt; if you don’t need to. Return by value instead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pre-allocate slices&lt;/strong&gt; when you know the size upfront (make([]int, 0, 100)) to avoid repeated heap allocations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use&lt;/strong&gt;**-gcflags= &amp;ldquo;-m&amp;rdquo; or benchmarks** to identify hot paths with lots of allocations before optimizing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="key-takeaways"&gt;Key Takeaways&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Go automatically decides whether variables live on the &lt;strong&gt;stack&lt;/strong&gt; (fast, temporary) or the &lt;strong&gt;heap&lt;/strong&gt; (slower, GC-managed).&lt;/li&gt;
&lt;li&gt;This decision is made by the compiler using &lt;strong&gt;escape analysis&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A variable “escapes” to the heap when it needs to outlive the function it was created in.&lt;/li&gt;
&lt;li&gt;You can inspect escape decisions with go build -gcflags=&amp;quot;-m&amp;quot;.&lt;/li&gt;
&lt;li&gt;Understanding escape analysis helps you write more allocation-efficient Go code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The beauty of Go is that you don’t &lt;em&gt;have&lt;/em&gt; to think about this most of the time. But when performance matters, escape analysis is one of those things that separates good Go code from great Go code. Happy coding! 🚀&lt;/p&gt;
&lt;p&gt;&lt;img src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=d1a0430003d8" alt=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@tacherasasi/escape-analysis-in-go-where-does-your-data-actually-live-d1a0430003d8?source=rss-9a41d7ec29fb------2"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>