Done Channel Pattern
Fast recall
Quick revision
Core answer
Whether you understand Done Channel Pattern beyond memorized syntax.
Giving a definition without explaining a practical trade-off.
Connect the concept to a small production example and state its complexity or failure mode.
Interview-ready guide
Interview answer
The pattern:
done := make(chan struct{})
results := make(chan int, 5)
go func() {
for i := 0; i < 5; i++ {
select {
case <-done: // stop signal
close(results)
return
case results <- i: // do work
}
}
close(results)
}()
close(done) // ALL workers selecting on done wake immediately
for v := range results { fmt.Print(v, " ") } // 0 1 2 3 4 — executedWhy CLOSE (not a value): a send wakes ONE receiver; a close wakes EVERY receiver — the broadcast. And close is permanent: every receive after returns immediately. The TYPE — chan struct{} — signals 'zero bytes of payload; the channel exists for its close.'
The combination: done + results, each closed by its owner — the lifecycle is explicit and every receiver terminates cleanly.
What context.Context generalizes: ctx.Done() IS this channel (a <-chan struct{}), and ctx.Cancel() is close. The bare done-channel is the mechanism to learn first; context adds deadlines, values, and parent-child trees on top.
Done Channel Pattern
Direct Answer
A chan struct{} that signals 'stop' by CLOSING. Workers select on <-done; close(done) wakes ALL simultaneously. close is the broadcast; receiving the zero value IS the message.
Deep Dive & Architecture
The broadcast mechanics: close(ch) sets the closed flag and wakes every parked receiver. Each returns (zero, false) — forever. This is WHY close is the cancellation primitive: a send could wake one; a close can't be missed by any goroutine selecting now or later.
The struct{} payload: the done channel carries NO data — the message IS the closure. struct{} costs zero bytes. chan bool works but communicates 'a boolean will be sent.' struct{} says 'there is no message.'
The worker-loop shape: for { select { case <-done: return; case work := <-queue: process(work) } }. The select BEFORE each work item checks done — cancellable between items. The ordering choice (check before vs after) is the semantics: stop-immediately vs stop-after-current (graceful drain).
The context evolution: this pattern WAS the pre-context idiom. ctx.Done() is the done channel; the API adds deadline propagation, values, and the cancellation error (cancelled vs deadline-exceeded). Both are the same mechanism underneath.
Common Mistakes & Pitfalls
Giving a definition without explaining a practical trade-off.
Check your understanding
Answer these without looking back at the article.
- When would you use Done Channel Pattern in a real project?
- How would you compare Done Channel Pattern with an alternative?
- What is a common mistake when using Done Channel Pattern?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.