Go's garbage collector runs while your goroutines run

Two brief stop-the-world steps bracket a concurrent mark. The goroutine that allocates starts the collection and helps mark; the others barely notice.

Read the explanation
012345678910allocatemain: stop the world: start GCstopallocatemain: mark assistassistallocateallocateticktick: runs during the marktickconcurrent markGC worker: stop the world: finish GCstop

Tap any block to see what that thread is doing.

  • Running
Step 1/5

Lanes are goroutines. main allocates fast; ticker never allocates. The collector has goroutines of its own.

1var keep [][]byte // live data the collector has to mark2 3func allocate() {4    for range 300_000 { // allocates fast, so collections keep starting5        keep = append(keep, make([]byte, 1024))6        if len(keep) > 1000 {7            keep = keep[:0] // the old buffers become garbage8        }9    }10}11 12func ticker(stop *atomic.Bool, ticks *atomic.Int64) {13    for !stop.Load() { // never allocates14        ticks.Add(1)15    }16}17 18func main() {19    var stop atomic.Bool20    var ticks atomic.Int6421    var wg sync.WaitGroup22    wg.Go(func() { ticker(&stop, &ticks) })23    allocate()24    stop.Store(true)25    wg.Wait()26    fmt.Println("ticker kept ticking:", ticks.Load() > 0)27}

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