← Back to list

Go Memory Allocation — A Deep Dive (Made Simple)

Understanding memory in Go can feel intimidating at first — but it doesn’t have to be.

Harsh Vishnoi · 2026-04-06 21:12 · 0 claps · 4.4 min read
#golang #allocation-golang #memory-allocation #golang-stack #golang-heap
Open on Medium ↗
Wiki topics: 💑 · Relationships

Go Memory Allocation — A Deep Dive (Made Simple)

Understanding memory in Go can feel intimidating at first — but it doesn’t have to be.

This guide breaks everything down in a way that actually sticks. No jargon overload. Just clear intuition, relatable examples, and the right mental models.

How is memory allocated in Golang?

Go manages memory automatically, deciding whether each variable lives on a stack or the heap. By default, small, short-lived data stays on the goroutine’s stack, while dynamic or longer-lived data “escapes” to the heap. Stack allocation is very fast (just bumping a pointer) and is freed automatically on function return; heap allocation is slower and must be tracked by Go’s garbage collector.

Stack (fast, auto-managed): Local variables and short-lived data go here. No GC needed — when a function ends, its entire stack frame is freed. Heap (dynamic, GC-managed): Data that outlives its creating function (or whose address is taken, stored in interfaces, etc.) goes here. Heap allocation costs more and increases GC work.

In short, Go tries to keep values on the stack whenever possible (for speed and lower GC pressure), but if the compiler detects they might escape, it places them on the heap.

Now pause for a second and think 🤔 How does Go actually decide — stack or heap? Where does your variable really go?

And another question — when memory needs to be cleaned up, who does it? Does the Garbage Collector handle everything, or just specific parts like the heap?

We’ll cover allocation below and the Garbage Collector in a separate guide, since it’s important to first understand its basics clearly before diving deeper.

🏃‍♂️ Escape Analysis: How Go Decides

The Go compiler uses something called escape analysis to decide where a variable should live — stack or heap.

Think of it like this: “Will this variable stay inside this function, or will it need to live longer?”

🧠 Simple Rule

  • If a variable is used only inside the function → it stays on the stack
  • If a variable escapes the function → it goes to the heap

Pause and Think 🤔

If the function finishes… but someone still needs that variable… Where should it live? Not stack (it will be destroyed), so Go puts it on the heap

Want to see what Go is doing internally? 🔍 Don’t guess — verify it.

// stackHeap.go
package main

func stackAlloc() int {
    x := 10
    return x // stays on stack
}

func heapAlloc() *int {
    x := 20
    return &x // escapes → heap
}

func main() {
    stackAlloc()
    heapAlloc()
}

Try this yourself: explore hidden memory escapes — such as those caused by interfaces, closures, and goroutines — where things tend to get tricky, even though they were covered in the last section as bonus points.

⚙️ Go’s Memory Allocator (Under the Hood)

Now that we know when Go uses the heap, the next question is: How does Go actually allocate memory so fast?

🧠 The Core Idea Go uses a multi-layer allocator (inspired by tcmalloc) to:

  • Make allocations fast ⚡
  • Avoid too much locking 🔒
  • Reduce memory fragmentation 🧩

Think for a moment If every memory allocation needed a global lock … What would happen in a highly concurrent program? 👉 It would slow down badly.

🚀 Go’s Solution So Go uses a layered allocator (inspired by tcmalloc), Let’s understand this with a real-world analogy.

mcache → mcentral → mheap

Let’s forget complex terms for a second and think like this: When Go needs memory, where does it go first?

Imagine a company where workers constantly need small items (like screws, tools, etc.).

Step 1: Your Desk Drawer (mcache)

You’re working and need a screw. First thought: “Do I already have it?”

  • You open your desk drawer
  • It already has screws of different sizes
  • You pick one instantly

🧠 Think

Why keep items in the drawer? Because you use them very frequently

Step 2: Storage Room (mcentral)

Now imagine:

  • Your drawer is empty 😬

What do you do?

  • You go to the shared storage room [ Slight delay ]
  • Ask for a box of screws (grouped by size)
  • Refill your drawer

🧠 Important Detail

You don’t take one screw … You take a batch

Why?

So you don’t have to come back again and again

Step 3: Main Warehouse (mheap)

Now imagine the worst case:

  • The storage room is also empty 😨

What happens now?

  • Storage manager goes to the main warehouse [ Slow process ]
  • Requests a large shipment
  • Warehouse gives a big container (span)

Another Key Idea

Warehouse doesn’t give single screws

  • It gives big chunks (spans)
  • These are later divided into smaller pieces

Full Flow (Real Intuition)

You need memory (object):

  1. Check mcache (drawer) → fast
  2. If empty → refill from mcentral (storage room)
  3. If empty → get from mheap (warehouse)]

💡 Why This Works So Well

Think about it:

  • Most work happens at the desk (mcache)
  • Rarely goes to storage (mcentral)
  • Rarely to warehouse (mheap)

🧰 Tools & Tracing (See What Go Is Really Doing)

So far, we’ve understood how memory works.

*But here’s the real question 👇 *How do you verify* what Go is actually doing?***

1. Escape Analysis (Compiler View)

Want to know: stack or heap? Run:

go build -gcflags="-m"

🧠 What does this tell you?

  • Which variables escaped to the heap
  • Which stayed on the stack

2. Allocation / Free Tracing (Runtime View)

Want to see: actual allocations happening at runtime? Run:

GODEBUG=allocfreetrace=1 go run main.go

🧠 What will you see?

Logs like:

tracealloc(0xc0000b0000, 0x50, *main.MyType)
goroutine 10 [running]:
    runtime.mallocgc(...)

tracefree(0xc0000b0000, 0x50)

🤔 What does this mean?

  • tracealloc → memory allocated
  • tracefree → memory freed

Final Takeaway

Most Go performance issues are not about CPU. They are about: Memory allocations + GC pressure

Master this…

In Go, performance isn’t just about writing fast code — it’s about writing code that allocates wisely.

BONUS

Understanding theory is great — but a few subtle mistakes can quietly kill performance in real systems.

Next: Go through **Real-World Performance Pitfalls (That Bite Hard)**


메타데이터
post_id
faa2ffaf6a65
slug
go-memory-allocation-a-deep-dive-made-simple-faa2ffaf6a65
url
https://medium.com/@harsh.vishnoi01/go-memory-allocation-a-deep-dive-made-simple-faa2ffaf6a65
canonical_url
https://medium.com/@harsh.vishnoi01/go-memory-allocation-a-deep-dive-made-simple-faa2ffaf6a65
author_url
https://medium.com/@harsh.vishnoi01
status
ok
fetched_at
2026-08-20 05:27:18