How does Go decide whether a variable lives on the stack or the heap?
The language spec does not mention stack or heap. The compiler decides, using escape analysis . If it can prove a value is never referenced after its function returns,…
38 Go interview questions on memory, gc & runtime internals, with detailed answers and code.
The language spec does not mention stack or heap. The compiler decides, using escape analysis . If it can prove a value is never referenced after its function returns,…
Build with go build -gcflags='-m' ./... . Use -m -m for the reason chain behind each decision, and -gcflags='-m -l' to turn off inlining so the output is easier to…
Returning or storing its address somewhere that outlives the frame: a global, a heap object, or a channel send of a pointer. Interface conversion , where a non-pointer…
A goroutine starts with a small stack, historically 2 KB. Since Go 1.19 the runtime picks the starting size from the average stack usage it has observed. Every function…
The allocator is derived from TCMalloc: Size classes : there are about 68 of them, covering objects up to 32 KB. Each request is rounded up to its class, so a 33-byte…
Go's GC is concurrent, non-moving, non-generational, tri-color mark-sweep . Objects are in one of three sets: White : not yet seen. Any object still white at the end of…
A write barrier is a small piece of code the compiler inserts around pointer writes into heap memory . It runs only while the GC is marking; otherwise it is skipped by a…
Sweep termination (STW, microseconds) : finish sweeping the previous cycle, reach a safe point on every P, and enable the write barrier. Concurrent mark : the world…
Neither. Go's GC does not move objects, so it never compacts, and it has no generations. Reasons given by the Go team: Escape analysis already keeps many short-lived…
GOGC sets the heap growth target relative to the live data left after the last collection. Since Go 1.18 the formula also counts GC roots: heapGoal = liveHeap +…
GOMEMLIMIT (Go 1.19+), or debug.SetMemoryLimit , sets a soft limit on all memory the Go runtime manages: heap, goroutine stacks and runtime metadata. It does not cover…
GODEBUG=gctrace=1 output?gc 14 @2.104s 3%: 0.021+4.1+0.034 ms clock, 0.17+1.2/7.9/15+0.27 ms cpu, 48->52->21 MB, 50 MB goal, 0 MB stacks, 0 MB globals, 8 P gc 14 @2.104s 3% : the 14th cycle, 2.1…
RSS includes more than live objects: Garbage not yet collected. With GOGC=100 the heap may reach about twice the live size. Idle spans that were freed but not yet…
Measure first : go test -bench . -benchmem , then pprof -sample_index=alloc_space . Preallocate : make([]T, 0, n) and make(map[K]V, n) , and use strings.Builder.Grow .…
sync.Pool behave, and what are its pitfalls?sync.Pool is a concurrency-safe cache of temporary, interchangeable objects. Each P has its own local shard, so Get and Put take no lock in the common case. Objects can…
In benchmarks, call b.ReportAllocs() or run with -benchmem to get B/op and allocs/op . Since Go 1.24, prefer b.Loop() . It resets the timer automatically and keeps the…
string and []byte NOT allocate?In general, string(b) and []byte(s) copy the data, because strings are immutable and byte slices are not. The compiler removes the copy in patterns where it can prove…
package main import ( "fmt" "unsafe" ) type A struct { a bool b int64 c bool } type B struct { b int64 a bool c bool } func main() { fmt.Println(unsafe.Sizeof(A{}),…
Atomic alignment: on 32-bit platforms (386, ARM, 32-bit MIPS), 64-bit atomic operations need 8-byte alignment, but int64 fields are only guaranteed 4-byte alignment.…
type T struct { x int64 z struct{} } type U struct { z struct{} x int64 } func main() { fmt.Println(unsafe.Sizeof(T{}), unsafe.Sizeof(U{}),…
unsafe.Pointer usage?The unsafe package documents six valid patterns. Everything else is invalid, even if it happens to work today: Convert *T1 to unsafe.Pointer to *T2 , when T2 is no…
uintptr dangerous even if the object is still referenced elsewhere?There are two independent problems: No liveness. The GC does not treat a uintptr as a reference. If the uintptr is the last "reference", the object can be collected and…
[]byte ↔ string conversion correctly, and what can go wrong?Since Go 1.20, use the dedicated functions and not the old header casts: // BytesToString: b must not be modified while the string is in use. func BytesToString(b…
type Res struct{ name string } func main() { r := &Res{name: "db"} runtime.SetFinalizer(r, func(r *Res) { fmt.Println("finalized", r.name) }) r = nil runtime.GC()…
runtime.AddCleanup and why is it preferred over SetFinalizer?Go 1.24 added: func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup func (c Cleanup) Stop() Once ptr is unreachable, the runtime calls cleanup(arg) at some…
Go 1.24 added the weak package. weak.Make(p) returns a weak.Pointer[T] that does not keep *p alive. wp.Value() returns the original pointer, or nil once the object has…
runtime.KeepAlive do and when is it required?The GC decides liveness from the point of last use , not from lexical scope. An object can therefore become unreachable, and its finalizer or cleanup can run, in the…
func header(data []byte) []byte { return data[:4] } func main() { big := make([]byte, 10<<20) // 10 MiB h := header(big) big = nil runtime.GC() fmt.Println(len(h),…
Reslicing only changes len . The elements past the new length stay in the backing array and still reference their objects, so the GC scans the whole array and keeps…
No. delete and clear(m) free the slots for reuse, but the map's bucket and table storage never shrinks. If a map briefly held 10 million entries, it keeps that memory…
A goroutine blocked forever is never collected, and neither is everything its stack references. Goroutine leaks are the most common real-world Go memory leak. // LEAK:…
time.After in a loop a memory leak? What changed in Go 1.23?func consume(ch <-chan Event) { for { select { case e := <-ch: handle(e) case <-time.After(time.Minute): return // idle timeout } } } Before Go 1.23: every iteration…
import _ "net/http/pprof" // registers /debug/pprof/* on DefaultServeMux func main() { go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() // ... }…
GOMAXPROCS is the number of Ps, which is the maximum number of threads running Go code at the same time. Before Go 1.25 it defaulted to the host's CPU count and ignored…
type Stringer interface{ String() string } func main() { var ( s string sl []int m map[string]int ch chan int e any i Stringer f func() p *int )…
Every index or slice expression is checked against the length at run time, and panics if out of range. The SSA backend's prove pass removes checks it can prove are…
PGO (GA in Go 1.21) feeds a CPU profile from production back into the compiler. If a file named default.pgo is in the main package's directory, go build uses it…
The GC and the scheduler both sometimes need to stop a goroutine at a point where its stack and registers can be described exactly: which slots hold live pointers. Those…