Write a fan-in merge that funnels N input channels into one output channel
Implement merge that takes any number of input channels and returns one output channel carrying every value from all of them. The output must close exactly once, after all inputs are fully drained, and no goroutine may leak.
func merge(cs ...<-chan int) <-chan int {
// your code here
}
Write the implementation.
Start one goroutine per input channel, each range-ing its channel and forwarding every value to a shared out. A sync.WaitGroup counts the forwarders; a separate goroutine calls wg.Wait() then close(out), so the output closes exactly once after all inputs drain. Return out immediately. Ranging the inputs and closing out only after Wait avoids both a send-on-closed panic and a goroutine leak.
- ✗Closing
outinside each forwarder, causing a send-on-closed panic on the other goroutines - ✗Using a single
selectand assuming one receive per channel means it is drained - ✗Never closing
out, so the caller'srangeblocks forever
- →How would you add cancellation so
mergestops early when acontext.Contextis cancelled? - →Why must
close(out)happen in its own goroutine rather than after theforloop that starts the forwarders?
Task
Merge N input channels into one output channel (fan-in). The output closes once — after every input is drained.
func merge(cs ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
wg.Add(len(cs))
for _, c := range cs {
go func(c <-chan int) {
defer wg.Done()
for v := range c {
out <- v
}
}(c)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
How it works
- One goroutine per input: it
ranges the channel (ending when that input is closed) and forwards values into the sharedout. - A
WaitGroupcounts the forwarders; a separate goroutine waits onwg.Wait()and only then closesout— so the close happens once, after everyone has stopped sending (nosend on closed channelpanic). outis returned immediately, before the goroutines finish, so the caller can startrange merge(...)right away.
⚠️ Classic trap: closing out inside a forwarder, or forgetting the () on the closing goroutine — either way the caller's range hangs.