compare Closing Channels with an alternative
Fast recall
Quick revision
Core answer
Practical understanding of Closing Channels.
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
A shared channel with multiple senders has a coordination problem: close means 'no one will ever send again', but each sender knows only its own intent. The two canonical solutions:
Fan-in pattern — a dedicated merger owns the output:
func merge(outputs ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
wg.Add(len(outputs))
for _, ch := range outputs {
go func(c <-chan int) {
defer wg.Done()
for v := range c { // each input closes normally
out <- v
}
}(ch)
}
go func() {
wg.Wait() // ALL forwarding goroutines done
close(out) // now, and only now: nobody will send
}()
return out
}Each upstream producer closes its own channel (its prerogative — it's the sole sender there). The merge goroutines drain those inputs; the closer waits for every one to finish, then closes the single output once. Exactly one close of the shared channel, executed by the one goroutine that has observed every sender finish.
WaitGroup-at-source pattern — when the senders write directly into a shared channel:
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for _, v := range work(id) {
results <- v
}
}(i)
}
go func() {
wg.Wait()
close(results)
}()Same law, different placement: the closer waits on the count of all senders. What both patterns share — and what the interview answer should name — is that 'who closes' is answered by 'who can PROVE production ended', and with multiple producers, proof is a synchronization primitive (WaitGroup) or an architecture (fan-in), never a per-sender close.
compare Closing Channels with an alternative
Direct Answer
Centralize: one owner closes. Either a fan-in goroutine that merges all senders and owns the output's close, or a WaitGroup over senders with a closer that waits. No sender ever closes 'its own' shared channel.
Deep Dive & Architecture
The underlying synchronization both patterns build is a happens-before chain that the close semantics require: the close must happen-after every send, and receivers' end-signal observation must happen-after the close. The WaitGroup gives you the first edge: each sender's Done() happens-before Wait() returns — so all sends happen-before the close in the closer goroutine. No send can race the close, so no send-after-close panic can occur, and no buffered value can be lost after close (close is only called with the queue already final).
The fan-in variant composes the same edge through channel closes: each upstream close happens-before the forwarding goroutine's range-termination, each termination happens-before its Done, all Dones happen-before Wait returns, which happens-before the output close. It's the same chain, one hop longer — which is why pipelines built entirely of close-terminating stages shut down so cleanly: every hop is a happens-before edge the runtime guarantees.
The alternative people reach for before learning these patterns — a shared 'done' flag channel that senders select on — is solving a DIFFERENT problem (cancellation: stopping senders early) and doesn't answer the close question at all. The two problems compose: cancellation (context/select) for early exit, the single-owner close for normal completion. Production Go uses both, and confusing them is the top source of pipeline deadlocks — a cancelled sender that never lets the WaitGroup reach zero, so the closer never fires, so receivers block forever. The complete pattern: senders select on ctx.Done() AND wg.Done() on every exit path.
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 Closing Channels, and when would you use it?
- When would you use Closing Channels in a real project?
- What is a common mistake when using Closing Channels?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.