Explanation
What just happened
main allocated fast, so collections kept starting. Each one began with main itself stopping the world, briefly. Then a GC worker goroutine marked the live data while main and ticker kept running. main was allocating faster than the marking could keep up, so the runtime made it help: a mark assist. A second short stop-the-world finished the cycle.
ticker never allocated, yet the two stops paused it too. Everything else, it barely noticed.
Why it works like this
Go’s collector is concurrent: most of its work runs alongside your code instead of pausing it. Making the allocating goroutine assist keeps the heap from growing faster than the collector can mark.
Watch out
Which goroutine runs the final stop changes from run to run: a GC worker, or main when its assist completes the mark. Read these timelines as who can run what, not as exact timings.
Common questions
Does Go's garbage collector stop the world?
Yes, twice per collection: once to start it and once to finish it. In between, the marking runs while your goroutines keep running, and it takes most of the collection's time.
Which goroutine runs the garbage collector?
Several. The goroutine whose allocation starts a collection runs the first stop-the-world itself, GC worker goroutines do the marking, and whichever goroutine completes the mark runs the final stop.
What is a GC mark assist in Go?
When a goroutine allocates faster than marking keeps up, the runtime makes it do some marking before it allocates more. A goroutine that never allocates never assists.
Verified: this timeline matches a real program, checked in CI on Go 1.26 and 1.27. How we verify. Unclear or wrong? Tell us