select is how goroutines react to whichever event comes first: data, cancellation or a deadline.
A select statement waits on several channel operations and runs the case for whichever is ready first:
select {
case msg := <-inbox:
handle(msg)
case out <- next:
sent++
case <-quit:
return
}If several cases are ready at once, select picks one at random, so no channel can starve the others. If none is ready, it blocks.
Common patterns:
- Timeouts: add
case <-time.After(2 * time.Second):to give up waiting. In a long-running loop, prefer onetime.NewTimeror a context deadline, because eachtime.Aftercall creates a new timer. - Non-blocking operations: a
defaultcase runs immediately when nothing else is ready.select { case ch <- v: default: dropped++ }sends if possible and drops otherwise. - Cancellation: include
case <-ctx.Done():(next lessons) so a goroutine stops when its work is no longer wanted. - Event loops:
for { select { ... } }is the standard shape for a goroutine that serves several channels.
A nil channel is never ready, which is a useful trick: set a channel variable to nil to disable its case once it's closed, while still serving the others.
An empty select {} blocks forever. And a default case inside a for loop turns it into a busy loop that burns CPU — only use default when you genuinely have something else to do.
Example
package main
import (
"fmt"
"time"
)
func slowQuery(d time.Duration) <-chan string {
ch := make(chan string, 1) // buffered: sender never leaks
go func() {
time.Sleep(d)
ch <- "result"
}()
return ch
}
func main() {
select {
case r := <-slowQuery(50 * time.Millisecond):
fmt.Println(r)
case <-time.After(10 * time.Millisecond):
fmt.Println("timed out") // this one wins
}
events := make(chan int)
select {
case e := <-events:
fmt.Println(e)
default:
fmt.Println("no event ready") // non-blocking
}
}Remember
- select runs the first ready case; ties are broken randomly
time.Afteror a context gives you timeoutsdefaultmakes an operation non-blocking — avoid it in tight loops
Watch out for
- Using an unbuffered result channel with a timeout: when the timeout wins, the sender blocks forever and leaks.