Go's garbage collector is normally something you can ignore, which is the highest compliment you can pay one. It becomes interesting at scale: large heaps, many cores, pointer-heavy data. That is where its design starts to show.
Green Tea is a rethink of how the collector traverses memory, and the reasoning behind it is worth following even if you never touch a GC setting — because the problem it solves is not really about garbage collection.
NOTE
Written in November 2025, when Green Tea was opt-in behind GOEXPERIMENT=greenteagc in Go 1.25.
It has since shipped: Go 1.26 enables Green Tea by default, with the Go team reporting a 10–40% reduction in garbage collection overhead on real programs that allocate heavily. The old collector remains available as GOEXPERIMENT=nogreenteagc and that escape hatch is scheduled for removal in Go 1.27.
So the "experimental" framing below is now historical. The explanation of why it works is not.
The problem with chasing pointers
The existing collector marks and sweeps. Conceptually, it walks the object graph: start from the roots, follow every pointer, mark everything reachable.
Correct, and hostile to hardware.
Live objects are scattered across the heap. You mark one, follow a pointer to something in a completely different region, mark that, follow another pointer somewhere else again. Each hop is likely a cache miss, and a cache miss costs a couple of hundred cycles during which the CPU does nothing.
The collector spends most of its time waiting for memory rather than doing work. And this gets worse as machines improve: processors have got dramatically faster while memory latency has barely moved, so every year the penalty for an unpredictable access pattern grows.
You cannot fix that by making the marking loop faster. The loop is not the bottleneck.
Scanning blocks instead of objects
Green Tea changes the unit of work.
Go already groups memory into spans — roughly 8 KB regions holding objects of a single size class. The old collector treated these as an allocation detail and ignored them while marking.
Green Tea makes the span the thing it queues:
- Find a live object.
- Queue the entire span containing it, not just that object.
- When the span is processed, the CPU pulls it into cache once and everything inside is scanned together.
The insight is that the expensive part was never the marking — it was the trip to memory. Once you have paid for the cache line, scanning the neighbours is nearly free. So batch the work by physical location rather than by graph structure.
This is spatial locality, applied to the collector itself.
The obvious objection
If a span holds one live object and is otherwise empty, does this not scan 8 KB to mark 32 bytes?
It would, so it does not. Green Tea uses a representative object: when a span contains only one live object, it marks that object and moves on without scanning the rest. The full scan is reserved for spans with enough live data to make it worthwhile.
The heuristic matters more than it sounds. Without it, the technique would be strictly worse on sparse heaps — which is exactly the case the old collector already handled adequately, and exactly the case you cannot afford to regress.
What it buys
From the Go team's benchmarks at the time of the experiment:
- 10–50% less CPU time in garbage collection for pointer-heavy workloads.
- Pause times roughly unchanged, slightly higher in some cases.
- Little effect on programs that barely touch the heap or hold few pointers.
That last point is the honest one. This is not a free speed-up for everyone. If your program allocates rarely, or holds mostly flat non-pointer data, there was not much scanning to optimise and you will see nothing.
The programs that gain are the ones that were suffering: large heaps, many cores, and the pointer-rich structures typical of servers built out of interfaces, maps, and slices of pointers.
Trying it
On Go 1.26 and later there is nothing to do — it is the default.
If you are on Go 1.25, it is one environment variable at build time:
GOEXPERIMENT=greenteagc go buildNo code changes, no API. It is a runtime implementation swap.
Going the other way on 1.26, while that remains possible:
GOEXPERIMENT=nogreenteagc go buildIf you are evaluating either, benchmark your own workload rather than trusting a headline percentage. GC behaviour is extremely sensitive to allocation patterns, and published numbers describe someone else's program.
Why this is the interesting part
The lesson generalises well past Go.
For most of computing history, making things faster meant doing less work. That instinct is now frequently wrong. On current hardware the constraint is usually memory latency, not compute — a modern CPU can execute a great many instructions in the time it takes to service one cache miss.
Green Tea does not reduce the amount of marking. It does the same marking in an order the hardware can predict. The algorithmic complexity is unchanged; the constant factor moves by tens of percent.
That is a pattern worth recognising, because it applies to code you write. It is why columnar storage beats row storage on analytical scans, why an array of structs and a struct of arrays perform differently with identical data, and why buffering helps so much for so little effort.
Same work, better layout. Increasingly that is where the performance is.
Reference
- The Green Tea garbage collector (opens in a new tab) — the Go team's own write-up.
- Go 1.26 release notes (opens in a new tab)