For-select loops
Long-lived channel loops should express multiplexing, shutdown, and backpressure clearly without spinning.
Canonical guidance
- keep each
selectarm meaningful - include shutdown handling in long-lived loops
- nil channels deliberately when you need to disable a case
Use when
- event loops
- brokers and dispatchers
- multiplexed worker control paths
Avoid
defaultcases that busy-spin- mixing too many unrelated responsibilities in one loop
- relying on
selectfairness for correctness
Preferred pattern
for {
select {
case <-ctx.Done():
return ctx.Err()
case item, ok := <-in:
if !ok {
return nil
}
handle(item)
}
}
Anti-pattern
- a
for-selectloop with no cancel path and adefaultthat polls constantly
Explanation: This is tempting because it feels responsive, but it burns CPU and still fails to model lifecycle cleanly.
Why
- most select-loop bugs are lifecycle or progress bugs
Related pages
Sources
- The Go Programming Language Specification - Go Team
- Advanced Go Concurrency Patterns - Sameer Ajmani
- Go Concurrency Patterns: Context - Sameer Ajmani