Go Select Statement
The select statement waits on multiple channel operations at once. It is like a switch statement but for channels. When multiple channels are ready at the same time, select picks one at random. This is the primary tool for managing multiple concurrent operations.
Basic Select
package main
import "fmt"
func main() {
ch1 := make(chan string, 1)
ch2 := make(chan string, 1)
ch1 <- "from channel 1"
ch2 <- "from channel 2"
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
}
}
Output (either one — select picks randomly when multiple cases are ready):
from channel 1
Select Flow Diagram
select {
case <-ch1: ──► ch1 ready? Run this
case <-ch2: ──► ch2 ready? Run this
case <-ch3: ──► ch3 ready? Run this
default: ──► none ready? Run this immediately
}
If multiple cases are ready → one is chosen at random
If no cases are ready (and no default) → select blocks and waits
Select with a Timeout
Combine select with time.After to set a deadline. If no channel sends a value within the time limit, the timeout case runs.
package main
import (
"fmt"
"time"
)
func slowOperation(ch chan<- string) {
time.Sleep(2 * time.Second)
ch <- "operation result"
}
func main() {
ch := make(chan string)
go slowOperation(ch)
select {
case result := <-ch:
fmt.Println("Got:", result)
case <-time.After(1 * time.Second):
fmt.Println("Timeout — operation took too long")
}
}
Output:
Timeout — operation took too long
Select with Default – Non-Blocking Check
Adding a default case makes select non-blocking. If no channel is ready, the default case runs immediately.
package main
import "fmt"
func main() {
ch := make(chan int)
select {
case val := <-ch:
fmt.Println("Received:", val)
default:
fmt.Println("No value available — channel is empty")
}
}
Output:
No value available — channel is empty
Select in a Loop – Fan-In Pattern
Use select inside a loop to continuously receive from multiple channels until a quit signal arrives.
package main
import (
"fmt"
"time"
)
func numbers(ch chan<- int) {
for i := 1; i <= 3; i++ {
time.Sleep(50 * time.Millisecond)
ch <- i
}
}
func letters(ch chan<- string) {
for _, l := range []string{"A", "B", "C"} {
time.Sleep(70 * time.Millisecond)
ch <- l
}
}
func main() {
nums := make(chan int)
lets := make(chan string)
go numbers(nums)
go letters(lets)
for i := 0; i < 6; i++ {
select {
case n := <-nums:
fmt.Println("Number:", n)
case l := <-lets:
fmt.Println("Letter:", l)
}
}
}
Output (interleaved based on timing):
Number: 1
Letter: A
Number: 2
Letter: B
Number: 3
Letter: C
Key Points
- Select waits on multiple channel operations simultaneously
- When multiple cases are ready, select picks one uniformly at random
- Use
time.Afterin a select case to implement operation timeouts - A
defaultcase makes select non-blocking — runs immediately if no channel is ready - Select inside a loop is the standard pattern for multiplexing multiple goroutines
