Skip to content

Select

select is Go’s way to wait on multiple channel operations simultaneously. It blocks until one of its cases is ready, then executes that case. If multiple cases are ready at the same time, one is chosen at random — this is a deliberate language design choice to avoid starvation.

select {
case v := <-ch1:
fmt.Println("received from ch1:", v)
case ch2 <- 99:
fmt.Println("sent to ch2")
}

A select case can be a send or a receive — whichever operation can proceed without blocking.

Adding a default case makes the select non-blocking. If no channel is ready, the default branch runs immediately:

select {
case v := <-ch:
fmt.Println("got", v)
default:
fmt.Println("nothing ready yet")
}

This is the idiomatic way to poll a channel without blocking.

A common pattern is combining select with time.After, which returns a channel that receives a value after the specified duration:

select {
case result := <-work:
fmt.Println("done:", result)
case <-time.After(2 * time.Second):
fmt.Println("timed out")
}

For production code where many timeouts are set, prefer context.WithTimeout (covered in the Context lesson) because it propagates cancellation and avoids timer leaks.

The snippet below demonstrates all three select behaviours in sequence — deterministically by using separate selects with pre-loaded buffered channels, followed by a default branch demo.

package main
import "fmt"
func main() {
// Pre-load two buffered channels so both are ready
ch1 := make(chan string, 1)
ch2 := make(chan string, 1)
ch1 <- "one"
ch2 <- "two"
// Drain ch1 with its own select — guaranteed to receive "one"
select {
case msg := <-ch1:
fmt.Println("ch1:", msg)
}
// Drain ch2 with its own select — guaranteed to receive "two"
select {
case msg := <-ch2:
fmt.Println("ch2:", msg)
}
// default makes select non-blocking
ch3 := make(chan int)
select {
case v := <-ch3:
fmt.Println("received", v)
default:
fmt.Println("no value ready, default taken")
}
// Fan-in: select from whichever of two channels has a value
// (use separate selects for deterministic playground output)
r1 := make(chan int, 1)
r2 := make(chan int, 1)
r1 <- 10
r2 <- 20
select {
case v := <-r1:
fmt.Println("r1:", v)
}
select {
case v := <-r2:
fmt.Println("r2:", v)
}
fmt.Println("done")
}
What does Go do when multiple cases in a select are simultaneously ready?
What is the effect of adding a `default` case to a select?
What does `time.After(d)` return?