Golang select keyword
last modified May 7, 2025
This tutorial explains how to use the select
keyword in Go. We'll
cover channel operations basics with practical examples of concurrent patterns.
The select statement lets a goroutine wait on multiple communication operations. It chooses one ready case randomly if multiple are available.
In Go, select
is essential for coordinating between goroutines.
It handles channel sends/receives and implements timeouts and non-blocking ops.
Basic select with two channels
This example shows the simplest use of select
with two channels.
It waits for data from either channel.
package main import ( "fmt" "time" ) func main() { ch1 := make(chan string) ch2 := make(chan string) go func() { time.Sleep(1 * time.Second) ch1 <- "from ch1" }() go func() { time.Sleep(2 * time.Second) ch2 <- "from ch2" }() for i := 0; i < 2; i++ { select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) } } }
The select waits for either channel to have data. Since ch1 is faster, its message prints first. The loop ensures both messages are received.
Select with default case
Adding a default case makes select non-blocking. This example demonstrates immediate execution when no channels are ready.
package main import "fmt" func main() { ch := make(chan string) select { case msg := <-ch: fmt.Println("Received:", msg) default: fmt.Println("No message received") } }
Since the channel is empty and no goroutine is sending, the default case executes immediately. This pattern is useful for polling channels.
Select with timeout
Using time.After in select implements timeouts. This prevents indefinite blocking on channel operations.
package main import ( "fmt" "time" ) func main() { ch := make(chan string) go func() { time.Sleep(3 * time.Second) ch <- "result" }() select { case res := <-ch: fmt.Println(res) case <-time.After(2 * time.Second): fmt.Println("timeout") } }
The timeout case triggers after 2 seconds, while the goroutine takes 3 seconds. This ensures the program doesn't wait indefinitely for slow operations.
Select for sending and receiving
Select can handle both send and receive operations. This example demonstrates coordinating between different channel directions.
package main import ( "fmt" "time" ) func main() { ch := make(chan int) done := make(chan bool) go func() { for { select { case ch <- 1: fmt.Println("Sent 1") case <-done: fmt.Println("Stopping") return } } }() go func() { time.Sleep(2 * time.Second) done <- true }() for i := 0; i < 5; i++ { fmt.Println("Received:", <-ch) time.Sleep(300 * time.Millisecond) } }
The first goroutine alternates between sending values and checking the done channel. After 2 seconds, the done channel stops the sender.
Select with multiple ready cases
When multiple cases are ready, select picks one randomly. This example shows the non-deterministic behavior.
package main import "fmt" func main() { ch1 := make(chan string, 1) ch2 := make(chan string, 1) ch1 <- "one" ch2 <- "two" for i := 0; i < 10; i++ { select { case msg := <-ch1: fmt.Println(msg) ch1 <- "one" case msg := <-ch2: fmt.Println(msg) ch2 <- "two" } } }
Both channels are ready, so select chooses randomly between them. The output shows a mix of "one" and "two" in unpredictable order.
Select with nil channels
Select ignores nil channels. This example demonstrates using nil to disable cases dynamically.
package main import ( "fmt" "time" ) func main() { var ch chan string go func() { time.Sleep(2 * time.Second) ch = make(chan string, 1) ch <- "message" }() for { select { case msg := <-ch: fmt.Println("Received:", msg) return default: fmt.Println("No channel yet") time.Sleep(500 * time.Millisecond) } } }
Initially ch is nil, so only the default case executes. After 2 seconds, ch becomes active and can receive the message.
Practical example: Worker pool
This practical example shows using select in a worker pool pattern to coordinate multiple goroutines.
package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker %d started job %d\n", id, j) time.Sleep(time.Second) fmt.Printf("worker %d finished job %d\n", id, j) results <- j * 2 } } func main() { jobs := make(chan int, 100) results := make(chan int, 100) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= 5; j++ { jobs <- j } close(jobs) for a := 1; a <= 5; a++ { select { case res := <-results: fmt.Println("result:", res) case <-time.After(2 * time.Second): fmt.Println("timeout waiting for result") } } }
The select waits for worker results with a timeout. It ensures the program doesn't hang if a worker fails to complete its task.
Source
This tutorial covered the select
keyword in Go with practical
examples of channel operations in concurrent programming.
Author
List all Golang tutorials.