How does Go's garbage collector work, at a high level?

6 minadvancedgogarbage-collectormemoryperformance

Quick Answer

Go uses a concurrent, tri-color mark-and-sweep garbage collector that runs largely alongside your program instead of stopping it entirely. It works in phases: a brief stop-the-world pause to start marking, a concurrent mark phase (running alongside your goroutines, using a write barrier to track new pointer writes correctly) that walks live objects from roots (globals, stacks) and colors them white/grey/black, another brief stop-the-world pause to finish marking, and a concurrent sweep phase that reclaims unmarked (white) memory. Go's GC is tuned to prioritize low pause times over maximum throughput, targeting sub-millisecond stop-the-world pauses even on large heaps, and it's triggered automatically based on heap growth (controlled by the GOGC environment variable, default 100, meaning it targets roughly doubling live heap size before the next collection).

Detailed Answer

Go's GC design specifically optimizes for latency, which is a deliberate tradeoff given Go's common use in network services where a long pause directly hurts request latency.

The tri-color mark-and-sweep model

White: not yet visited — candidate for collection
Grey:  visited, but its children haven't been scanned yet
Black: visited, and all its children have been scanned — definitely reachable

The collector starts by marking roots (global variables, goroutine stacks) grey, then repeatedly picks a grey object, scans its pointers, marks anything it points to grey, and marks the object itself black. When no grey objects remain, everything still white is garbage.

Why "concurrent" matters

Old GC approach (older Go versions, and many other languages):
  stop the world -> mark everything -> sweep -> resume

Go's current approach:
  brief stop-the-world (start marking)
  -> CONCURRENT marking, alongside your running goroutines
  -> brief stop-the-world (finish marking)
  -> CONCURRENT sweeping, alongside your running goroutines

Running the mark phase concurrently, with a write barrier to correctly track pointer writes that happen while marking is in progress, is what keeps Go's stop-the-world pauses in the sub-millisecond range, even for multi-gigabyte heaps.

Tuning with GOGC

GOGC=100  # default: trigger GC roughly when heap has doubled since the last collection
GOGC=200  # collect less often, use more memory, generally reduce GC CPU overhead
GOGC=50   # collect more often, use less memory, spend more CPU on GC
GOGC=off  # disable GC entirely (rarely appropriate outside short-lived batch jobs)

The practical takeaway for interviews

Go doesn't require manual memory management, but understanding that GC pauses are brief and concurrent (not the multi-second stop-the-world pauses associated with some older GC designs) is exactly why Go remains viable for low-latency network services, unlike languages whose GC pause times can spike unpredictably under load.