When would you use Range Over Channel in a real project
Fast recall
Quick revision
Core answer
Practical understanding of Range Over Channel.
Repeating a definition without saying when to use it.
Give one trade-off and say how you would test it.
Interview-ready guide
Interview answer
break works exactly as you'd hope:
for v := range ch {
if v == stopValue {
break // leaves the range; ch is still open
}
process(v)
}The range ends, but the channel's life continues. The consequences differ by what's upstream:
Unbuffered channel with a sender blocked trying to deliver: your break leaves that sender parked on its send — a goroutine leak. The sender needs to see a cancellation (a done channel, a context) to give up.
Buffered channel: subsequent sends fill the buffer (up to capacity), then senders block. Values you didn't consume are discarded when the channel is garbage-collected — but a sender blocked on a full channel leaks exactly as the unbuffered case.
So break answers YOUR side ('stop consuming') but not THEIRS ('stop producing'). The complete early-exit pattern pairs the break with a cancellation the producer selects on:
done := make(chan struct{})
// producer: for { select { case out <- v: case <-done: return } }
for v := range out {
if !keepGoing(v) {
close(done) // tell the producer
break // and leave
}
}That's the two-sided exit: consumer breaks, producer observes the done close, unwinds. context.Context is this same pattern, standardized — Done() is the done channel, and the idiom above is why ctx appears in every pipeline signature.
When would you use Range Over Channel in a real project
Direct Answer
Yes — break exits the range like any loop. The channel stays open, values keep buffering (up to capacity), and the goroutine that would send into it can block later. Early exit needs a cancellation signal if you want senders to stop too.
Deep Dive & Architecture
The break leaves the channel open — and what happens next is a cross-goroutine question, not a loop question. Trace the two topologies:
Unbuffered, sender mid-send: the sender parked in the channel's send queue stays parked. Your range's receive was its rendezvous; you left before the meeting. That goroutine is leaked — holding its stack, its closure references, whatever it captured — until the channel itself is unreachable, which your break may prevent (if the caller keeps the channel variable, the parked sender is permanent). Memory monitors show it as a goroutine stuck on 'chan send'.
Buffered, sender mid-send: your break leaves buffered values unconsumed. Subsequent sends fill the ring to capacity, then park the sender — the leak again, just N sends later. The unconsumed values die with the channel (GC), which is fine; the parked SENDER is the leak.
The cancellation pattern completes the protocol: done := make(chan struct{}); close(done) is a broadcast — every sender selecting on it wakes and returns. That's why done channels (and context.Context, which IS a done channel with plumbing) exist: break answers 'stop receiving'; close(done) answers 'stop sending'. Both sides need both signals for a clean early exit — the consumer's break plus the producer's observation of done is the minimal complete teardown.
One subtlety: close(done) before break vs after. Close first, then break: the producer may send one more value into the buffer after your close (racing its select) — harmless, you're leaving. Break first: the producer might block on an unbuffered send before observing done — leaked until it checks. Order: close(done), then break — the producer's exit path is guaranteed reachable.
Common Mistakes & Pitfalls
Repeating a definition without saying when to use it.
Check your understanding
Answer these without looking back at the article.
- What is Range Over Channel, and when would you use it?
- How would you compare Range Over Channel with an alternative?
- What is a common mistake when using Range Over Channel?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.