Go

Why does this loop spin at 100% CPU after one channel closes?

Question 496HardGo 1.22 to 1.25
func merge(a, b <-chan int, out chan<- int) {
    for {
        select {
        case v := <-a:
            out <- v
        case v := <-b:
            out <- v
        }
    }
}

A receive from a closed channel is always ready and returns the zero value right away. Once a closes, select keeps choosing that case, and the loop floods out with zeros. It also never ends.

Fix: use the comma-ok form and set a finished channel to nil. A nil channel blocks forever, so its case can never be chosen again:

func merge(a, b <-chan int, out chan<- int) {
    defer close(out)
    for a != nil || b != nil {
        select {
        case v, ok := <-a:
            if !ok {
                a = nil
                continue
            }
            out <- v
        case v, ok := <-b:
            if !ok {
                b = nil
                continue
            }
            out <- v
        }
    }
}

The nil-channel trick is the idiomatic way to turn off a select case. Also mention adding a case <-ctx.Done(): return so the goroutine can be cancelled.

More on Tricky Output & Code-Review Puzzles

All 38 Tricky Output & Code-Review Puzzles questions