A select statement coordinates a goroutine that must communicate with more than one channel. Each case contains a channel send or receive. Select blocks until a case can proceed, then runs that case; if several cases are ready, it chooses one at random, so case order is not a scheduling guarantee. section 8.1 section 8.1.1
Receive whichever message arrives first
Two goroutines can emit "Tick" every second and "Tock" every three seconds on separate channels. A loop containing one receive case per channel waits for either message, prints it, and returns to select. This keeps one receiver responsive without predicting which producer will run next. section 8.1.1
A default case makes channel access non-blocking. When no send or receive case is ready, default runs immediately, allowing useful fallback work instead of waiting. In a loop, that fallback repeats until a channel becomes ready, so deliberately pacing it with a sleep can make its observable behavior manageable. section 8.1.2
Select is not limited to receiving. It can combine sends and receives, choosing the operation that unblocks first. For example, a goroutine can feed random values into an input channel while simultaneously collecting prime values from a filtering goroutine, continuing until it has collected the required results. section 8.1.5
Nil channels provide a way to disable a case dynamically. A send or receive on a nil channel blocks; inside select, that means the affected case cannot be selected. When a receive reports that a source channel is closed, assign nil to that channel so its case no longer wins repeatedly, and continue until every input has been disabled. section 8.1.6
Listing 8.12 A main() goroutine using the nil select pattern
func main() { sales := generateAmounts(50) #1 expenses := generateAmounts(40) #2 endOfDayAmount := 0 for sales != nil || expenses != nil { #3 select { case sale, moreData := <-sales: #4 if moreData { fmt.Println("Sale of:", sale) endOfDayAmount += sale #5 } else { sales = nil #6 } case expense, moreData := <-expenses: #7 if moreData { fmt.Println("Expense of:", expense) endOfDayAmount -= expense #8 } else { expenses = nil #9 } } } fmt.Println("End of day profit and loss:", endOfDayAmount) } #1 Generates 50 amounts on the sales channel #2 Generates 40 amounts on the expenses channel #3 Continues to loop while there is a non-nil channel #4 Consumes the next amount and channel open flag from the sales channel\ #5 Adds the sales amount to the total end-of-day balance #6 If the channel has been closed, marks the channel as nil, disabling this select case #7 Consumes the next amount and channel open flag from the expenses channel #8 Subtracts the expense amount from the total end-of-day balance #9 If the channel has been closed, marks the channel as nil, disabling this select case
The sales-and-expenses loop shows the nil-channel pattern in context: receive an amount while data remains, then set each closed channel to nil and finish after both sources close. section 8.1.6
Common coordination uses include:
- Wait for messages from several producers without knowing which channel will deliver next. section 8.1.1
- Attempt a channel operation without waiting by adding a default case. section 8.1.2
- Stop a sender when a quit channel is closed, rather than continuing to generate values. section 9.2.1
- Use select to send and receive on paired channels, which can also help avoid a circular wait. section 8.1.5 section 11.4
Select supports message-passing designs in which goroutines keep isolated state and exchange values through channels. This can make loosely coupled code easier to read and maintain, although message passing may use more memory and can be a poor fit when large data or extremely many messages must be exchanged. section 8.4








