Understanding Memory in Go

Overview

Go manages memory automatically through its runtime, but understanding stack vs heap allocation helps write more efficient code.

Stack vs Heap

Stack

  • Fast allocation/deallocation
  • Function-local variables
  • Fixed size per goroutine (~2KB initial)
  • Automatically cleaned up when function returns

Heap

  • Dynamic allocation
  • Survives function scope
  • Managed by garbage collector
  • More expensive than stack

Escape Analysis

Go’s compiler decides where to allocate based on escape analysis:

func stackAlloc() int {
    x := 42      // Stays on stack
    return x     // Value copied
}

func heapAlloc() *int {
    x := 42      // Escapes to heap
    return &x    // Pointer returned
}

Check Escape Analysis

go build -gcflags="-m" main.go
# Shows: "moved to heap" for escaped variables

What Causes Escape

// 1. Returning pointer to local
func escape1() *int {
    n := 1
    return &n  // Escapes
}

// 2. Storing in interface
func escape2() {
    n := 1
    fmt.Println(n)  // Escapes (interface{} argument)
}

// 3. Closure capturing variable
func escape3() func() int {
    n := 1
    return func() int { return n }  // Escapes
}

// 4. Size too large for stack
func escape4() {
    _ = make([]byte, 10<<20)  // 10MB, escapes
}

Memory Layout

Value Types

type Point struct { X, Y int }
p := Point{1, 2}  // 16 bytes inline

Reference Types

s := make([]int, 10)  // Slice header on stack, data on heap
m := make(map[string]int)  // Map structure on heap

Best Practices

Reduce Allocations

// Bad: allocates each iteration
for i := 0; i < n; i++ {
    data := make([]byte, 1024)
    process(data)
}

// Good: reuse allocation
data := make([]byte, 1024)
for i := 0; i < n; i++ {
    process(data)
}

Preallocate Slices

// Bad: multiple reallocations
var result []int
for _, v := range data {
    result = append(result, v)
}

// Good: preallocate
result := make([]int, 0, len(data))
for _, v := range data {
    result = append(result, v)
}

Use Value Semantics When Possible

// May allocate
func process(p *Point) { }

// Stays on stack
func process(p Point) { }

Summary

Location Characteristics
Stack Fast, automatic, limited size
Heap Dynamic, GC-managed, survives scope
Causes Escape Example
Return pointer return &x
Interface fmt.Println(x)
Closure func() { use(x) }
Large size make([]byte, 1MB)