Introduction
The select statement is Go's way of waiting on multiple channel operations simultaneously. It is to channels what switch is to values — but with the added power of non-deterministic choice when multiple cases are ready.
Key Concepts
- Select: A control structure that blocks until one of its channel operations can proceed.
- Random Selection: If multiple cases are ready simultaneously, one is chosen at random.
- Default Case: If present, executes immediately when no other case is ready, making the select non-blocking.
Real World Context
In an HTTP server with graceful shutdown, a select statement waits on both the server error channel and an OS signal channel. Whichever fires first determines whether the server crashed or was asked to shut down, and the appropriate cleanup runs.
Deep Dive
select lets a goroutine wait on multiple channel operations:
goselect { case v := <-ch1: fmt.Println("From ch1:", v) case v := <-ch2: fmt.Println("From ch2:", v) case ch3 <- 42: fmt.Println("Sent to ch3") default: fmt.Println("No communication ready") }
Rules
- If multiple cases are ready, one is chosen at random.
defaultexecutes immediately if no other case is ready.- Without
default, select blocks until a case is ready.
Common Patterns
Timeout
goselect { case v := <-ch: process(v) case <-time.After(1 * time.Second): fmt.Println("Timeout") }
Non-blocking Send/Receive
goselect { case ch <- v: fmt.Println("Sent") default: fmt.Println("Channel full, dropping value") }
Common Pitfalls
- Using
time.Afterin a loop — Each call totime.Aftercreates a new timer that is not garbage collected until it fires. In a tight loop, this leaks memory. Usetime.NewTimerwithReset()instead. - Forgetting the
defaultcase in a polling loop — Withoutdefault, the select blocks. If you need non-blocking behavior, always includedefault.
Best Practices
- Prefer
context.Contextovertime.Afterfor timeouts — Contexts compose and propagate cancellation through call chains. - Use select to multiplex done channels — Combine
ctx.Done()with work channels to make goroutines cancellation-aware.
Summary
selectwaits on multiple channel operations; if multiple are ready, one is chosen at random.defaultmakes select non-blocking.- Use
selectwithctx.Done()for cancellation-aware goroutines. - Avoid
time.Afterin loops; usetime.NewTimerwithReset()instead.
Code Examples
go
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
ch1 <- "one"
}()
go func() {
time.Sleep(200 * time.Millisecond)
ch2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
}
}
}