Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
217: Escape Analysis and Stack vs Heap Allocation
If you've spent any time with C or C++, your brain is probably wired to scream when you see a function return a pointer to a variable created inside that same function. You've been taught that local variables live on the stack, and once the function returns, that stack frame is popped and gone. Returning a pointer to that memory would lead to a "dangling pointer" and a guaranteed crash or a security vulnerability. This is a habit that's hard to break, and it's the biggest misconception I see when people move to Go.
The "Dangling Pointer" Panic vs. Go's Reality
In many languages, returning a pointer to a local variable is a cardinal sin. But look at this Go code:
type User struct {
Name string
Age int
}
func NewUser(name string, age int) *User {
u := User{Name: name, Age: age}
return &u
}
If Go worked like C, u would be allocated on the stack, and the moment NewUser returned, that memory would be invalid. But in Go, this is not only legal—it's a standard pattern. Why? Because the Go compiler performs Escape Analysis.
The compiler doesn't blindly put everything "local" on the stack. It analyzes the scope of the variable. In the example above, the compiler sees that the address of u is being returned to the caller. Since the variable "escapes" the function's scope, the compiler automatically moves it to the heap. You don't have to tell it to do this; it just happens.
Stack Allocation vs. Heap Allocation
Before we dive into how to control this, we need to be clear on why we care. The stack is incredibly fast. It's just a pointer move in memory. When a function returns, the stack is cleaned up instantly. The heap, however, is a big messy pool of memory. Allocating there is slower, and more importantly, it creates work for the Garbage Collector (GC). If everything escaped to the heap, your Go program would spend half its time cleaning up memory instead of running your logic.
Generally, the compiler keeps a variable on the stack if:
- The variable is not referenced outside the function.
- The size of the variable is known at compile time.
- The variable isn't too large (huge arrays will be pushed to the heap regardless).
Peeking Under the Hood with gcflags
I don't want you to just take my word for it. You can actually ask the compiler to tell you what it's doing. There's a specific flag you can pass to the build tool to see the escape analysis decisions.
Try running this in your terminal:
go build -gcflags="-m" main.go
The -m flag tells the compiler to print optimization decisions. You'll see lines like moved to heap: u or u does not escape. I use this all the time when I'm profiling a hot loop in a production service. If I see a small object escaping to the heap inside a loop that runs a million times a second, I know exactly where my GC pressure is coming from.
One common "gotcha" that surprises people is using interfaces. Because interfaces are dynamic, the compiler often can't prove that a value won't escape, so it frequently moves values passed into fmt.Printf (which takes interface{}) to the heap. It's a small price to pay for flexibility, but it's a good reminder that "invisible" conversions can trigger heap allocations.
📋 Practical Task
Refactoring for Stack Allocation in a Metrics Logger
You are optimizing a high-throughput metrics system. Currently, the system creates a small Metric struct for every single event, and it's causing significant GC overhead because the structs are escaping to the heap.
Your Goal: Modify the provided code so that the Metric struct stays on the stack instead of escaping to the heap. You will know you've succeeded when go build -gcflags="-m" no longer reports that the metric "escapes to heap".
package main
import "fmt"
type Metric struct {
Value float64
ID int
}
// This function currently causes the Metric to escape
func LogMetric(id int, val float64) {
m := &Metric{ID: id, Value: val}
fmt.Printf("Metric %d: %f\n", m.ID, m.Value)
}
func main() {
for i := 0; i < 10; i++ {
LogMetric(i, float64(i)*1.5)
}
}
Hint: Think about how the Metric is being declared and how it's being passed to fmt.Printf. Try changing the pointer to a value and see if you can eliminate the heap allocation while keeping the logic identical.
There are no comments for now.