<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Memory on Tachera W Sasi, Backend &amp; Infra Engineer (Go, Self-Hosted, Observability)</title><link>https://tacherasasi.github.io/tags/memory/</link><description>Recent content in Memory on Tachera W Sasi, Backend &amp; Infra Engineer (Go, Self-Hosted, Observability)</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Fri, 13 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tacherasasi.github.io/tags/memory/index.xml" rel="self" type="application/rss+xml"/><item><title>What Building malloc Taught Me About Memory</title><link>https://tacherasasi.github.io/posts/what-building-malloc-taught-me-about-memory-57eae5b35166/</link><pubDate>Fri, 13 Mar 2026 00:00:00 +0000</pubDate><guid>https://tacherasasi.github.io/posts/what-building-malloc-taught-me-about-memory-57eae5b35166/</guid><description>&lt;h2 id="tags-clang"&gt;&amp;mdash;2&amp;quot;
tags: [&amp;ldquo;clang&amp;rdquo;]&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*2dOqSEchtIkDv2Fma09rOA.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Modern developers rarely think about memory.&lt;/p&gt;
&lt;p&gt;We call malloc, new, or let a garbage collector handle everything. Most of the time that’s perfectly fine. But recently I decided to dig deeper and implement a very small version of malloc myself.&lt;/p&gt;
&lt;p&gt;Not to replace real allocators. Just to understand what actually happens under the hood.&lt;/p&gt;
&lt;p&gt;The result was surprisingly simple and incredibly educational.&lt;/p&gt;
&lt;p&gt;This post walks through the core ideas.&lt;/p&gt;
&lt;h3 id="why-understanding-memory-still-matters"&gt;Why Understanding Memory Still Matters&lt;/h3&gt;
&lt;p&gt;Most software today runs on layers of abstraction.&lt;/p&gt;
&lt;p&gt;Frameworks → languages → runtimes → operating systems.&lt;/p&gt;
&lt;p&gt;That’s powerful, but it also means many developers never see the &lt;strong&gt;foundations&lt;/strong&gt; those layers are built on.&lt;/p&gt;
&lt;p&gt;Understanding memory allocation helps with:&lt;/p&gt;
&lt;p&gt;• writing faster software&lt;br&gt;
• debugging memory issues&lt;br&gt;
• understanding how runtimes work&lt;br&gt;
• building systems tools&lt;br&gt;
• designing better APIs&lt;/p&gt;
&lt;p&gt;Even if you work in higher level languages like &lt;strong&gt;Go&lt;/strong&gt; , these concepts still power everything underneath.&lt;/p&gt;
&lt;h3 id="step-1-where-malloc-gets-memory"&gt;Step 1: Where malloc Gets Memory&lt;/h3&gt;
&lt;p&gt;When a program runs, memory roughly looks like this:&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;Code
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Globals
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Heap ← grows upward
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Stack ← grows downward
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;malloc allocates from the &lt;strong&gt;heap&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Under the hood, the allocator asks the OS for more heap space using a system call like sbrk().&lt;/p&gt;
&lt;p&gt;Example:&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;void *ptr = sbrk(1024);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This moves the &lt;strong&gt;program break&lt;/strong&gt; forward by 1024 bytes and returns a pointer to the new memory.&lt;/p&gt;
&lt;p&gt;Think of it like extending the end of your program’s heap.&lt;/p&gt;
&lt;h3 id="step-2-the-naive-allocator"&gt;Step 2: The Naive Allocator&lt;/h3&gt;
&lt;p&gt;The simplest possible allocator would just call sbrk.&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;void *my_malloc(size_t size) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return sbrk(size);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But this immediately creates a problem.&lt;/p&gt;
&lt;p&gt;If you later call free, the allocator has &lt;strong&gt;no idea&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;• how big the allocation was&lt;br&gt;
• where the next block starts&lt;br&gt;
• whether a block can be reused&lt;/p&gt;
&lt;p&gt;So real allocators store &lt;strong&gt;metadata&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="step-3-the-hidden-header-trick"&gt;Step 3: The Hidden Header Trick&lt;/h3&gt;
&lt;p&gt;Every allocation secretly stores a small header before the memory returned to the user.&lt;/p&gt;
&lt;p&gt;Memory layout:&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;[ HEADER ][ USER MEMORY ]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The header might contain:&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;size
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is_free
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;next_block
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Example structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&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="n"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;block_header&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="n"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&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="n"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_free&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="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;block_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="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 class="n"&gt;block_header_t&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When the allocator returns memory, it actually returns a pointer &lt;strong&gt;after the header&lt;/strong&gt;.&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;return (void *)(block + 1);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which means the user sees:&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;ptr → usable memory
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While the allocator still has its metadata.&lt;/p&gt;
&lt;h3 id="step-4-tracking-allocations"&gt;Step 4: Tracking Allocations&lt;/h3&gt;
&lt;p&gt;Blocks are usually tracked using a linked list.&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;heap_start
&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;[block A] → [block B] → [block C]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When malloc runs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;search for a free block big enough&lt;/li&gt;
&lt;li&gt;reuse it if possible&lt;/li&gt;
&lt;li&gt;otherwise request more memory from the OS&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This keeps allocations fast and avoids unnecessary system calls.&lt;/p&gt;
&lt;h3 id="step-5-what-free-actually-does"&gt;Step 5: What free Actually Does&lt;/h3&gt;
&lt;p&gt;One surprising thing: free usually does &lt;strong&gt;not return memory to the OS&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Instead it just marks the block as reusable.&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;[HEADER free=1][memory]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Future allocations can reuse that block instead of requesting more memory.&lt;/p&gt;
&lt;h3 id="what-real-allocators-do-differently"&gt;What Real Allocators Do Differently&lt;/h3&gt;
&lt;p&gt;Real allocators (like the ones used by &lt;strong&gt;glibc&lt;/strong&gt;) add several optimizations:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Block splitting&lt;/strong&gt;&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;Free block: 100 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Need: 20
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Split into:&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;20 used
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;80 still free
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Block coalescing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Adjacent free blocks merge together to reduce fragmentation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Large allocation handling&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Large allocations often use mmap instead of the heap.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thread safety&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Modern allocators maintain thread local arenas for performance.&lt;/p&gt;
&lt;h3 id="what-i-learned-from-this"&gt;What I Learned From This&lt;/h3&gt;
&lt;p&gt;Building even a tiny allocator reinforced a few important lessons.&lt;/p&gt;
&lt;h3 id="abstractions-hide-complexity-but-the-fundamentals-still-matter"&gt;Abstractions hide complexity, but the fundamentals still matter&lt;/h3&gt;
&lt;p&gt;Languages like &lt;strong&gt;Go&lt;/strong&gt; make memory management easier, but the underlying principles are still there.&lt;/p&gt;
&lt;h3 id="performance-often-comes-from-understanding-the-layers-below-you"&gt;Performance often comes from understanding the layers below you&lt;/h3&gt;
&lt;p&gt;Understanding memory layout and allocation patterns helps explain why some code performs better than others.&lt;/p&gt;
&lt;h3 id="systems-knowledge-compounds"&gt;Systems knowledge compounds&lt;/h3&gt;
&lt;p&gt;Once you understand memory allocators, many other topics suddenly make more sense:&lt;/p&gt;
&lt;p&gt;• garbage collectors&lt;br&gt;
• runtime design&lt;br&gt;
• kernel memory management&lt;br&gt;
• high performance servers&lt;/p&gt;
&lt;h3 id="final-thoughts"&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;You don’t need to write your own allocator for production software.&lt;/p&gt;
&lt;p&gt;But building one, even a small version, forces you to think like the runtime.&lt;/p&gt;
&lt;p&gt;And that perspective is valuable.&lt;/p&gt;
&lt;p&gt;The more you understand the layers beneath your tools, the better engineer you become.&lt;/p&gt;
&lt;p&gt;GITHUB =&amp;gt; &lt;a href="https://github.com/tacheraSasi/malloc.git"&gt;https://github.com/tacheraSasi/malloc.git&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=57eae5b35166" alt=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@tacherasasi/what-building-malloc-taught-me-about-memory-57eae5b35166?source=rss-9a41d7ec29fb------2"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item><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>