What is a select statement in Go?

In Go, the select statement is a language construct used for working with multiple channel operations concurrently. It allows a Go program to wait for communication on multiple channels simultaneously and execute code based on the first channel operation that becomes available. The select statement is similar to the switch statement, but it deals with communication actions on channels, such as sending or receiving data.

Blocking and non-blocking statements

In the context of select statements, blocking operations in Go halt the program's execution until any of the monitored channels are ready for I/O, causing potential pauses.

Non-blocking operations, on the other hand, allow the program to continue without waiting and proceed immediately to the default case or the rest of the code if no channels are ready.

Syntax

The basic syntax of the select statement in Go is as follows:

select {
case <-channel1:
// Code to be executed when channel1 is ready for receive
case channel2 <- value:
// Code to be executed when channel2 is ready for send
default:
// Code to be executed when no case is ready
}

Each case in the select statement represents a communication operation, either receiving from (<-) or sending to (<- value) a channel. When the select statement is executed, it will wait for one of the channel operations to proceed, and then the corresponding case's code block will be executed. If none of the cases are ready, the default case (if present) will be executed, or the select statement will block until at least one case is ready.

Note: If a select statement does not contain any case statement, then that select statement waits forever. Therefore, the default statement in the select statement is used to protect select statement from blocking.

Cases

When multiple cases in a select statement are ready to communicate simultaneously, Go's select statement uses a pseudo-random Pseudo-random refers to a sequence of numbers that appears random but is generated by a deterministic algorithm, making it predictable if the initial state is known. selection process to determine which case will be executed.

  • If only one case is ready, it is executed immediately.

  • If multiple cases are ready simultaneously, Go will randomly choose one of the ready cases to execute. This ensures fairness and prevents favoring any particular case.

  • The other non-executed cases will be ignored, and the select statement will continue to execute the code following the selected cases.

Note: If there are no cases ready to communicate (all channels are blocked), and there is a default case available in the select statement, the code in the default case will be executed.

Examples

We will see different examples and use cases of select statements in Go.

Example 1

The select statement will block the program indefinitely if there are no communication operations on channels or other cases for it to proceed. This code is a basic example of an idle select statement that does not perform any specific tasks.

package main
// main function
func main() {
// Select statement
select{ }
}

You will receive an error of deadlock as the program will block forever.

Example 2

In this example, we have two goroutines which are the sender and receiver. The sender function sends a message to the channel after simulating some work, and the receiver function receives a message from the channel after simulating some work.

package main
import (
"fmt"
"time"
)
func sender(ch chan<- string, message string) {
time.Sleep(200 * time.Millisecond) // Simulate some work for 200 milliseconds
ch <- message
}
func receiver(ch <-chan string) {
select {
case msg := <-ch:
fmt.Println("Received:", msg)
case <-time.After(300 * time.Millisecond):
fmt.Println("Timeout! No message received.")
}
}
func main() {
channel := make(chan string)
go sender(channel, "Hello, there!") // Send a message after 200 milliseconds
go receiver(channel) // Receive a message after 100 milliseconds
time.Sleep(500 * time.Millisecond) // Wait for the goroutines to finish
}
  • Lines 15–20: We have a select statement that has two cases:

    • Case msg := <-ch: This case is executed when a message is received from the channel. It prints the received message.

    • Case <-time.After(3 * time.Second): This case is executed if no message is received within 3 seconds. It prints a timeout message.

In this example, the select statement allows the receiver to wait for a message from the channel and handle the case when there is a message, or a timeout occurs. It prevents the receiver from getting blocked indefinitely, ensuring that the program can continue its execution even if a message is not received within a certain timeframe.

Example 3

In this example, we have two goroutines that send data to two different channels (ch1 and ch2) with some delays using the time.Sleep. The main goroutine then uses a select statement within a for loop to receive data from these channels.

package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int)
ch2 := make(chan string)
go func() {
for i := 1; i <= 5; i++ {
time.Sleep(1 * time.Second)
ch1 <- i
}
}()
go func() {
for i := 1; i <= 5; i++ {
time.Sleep(2 * time.Second)
ch2 <- fmt.Sprintf("Message %d", i)
}
}()
for i := 0; i < 10; i++ {
select {
case num := <-ch1:
fmt.Println("Received from ch1:", num)
case msg := <-ch2:
fmt.Println("Received from ch2:", msg)
}
}
}

Lines 26–33: The select statement waits for any of the channels to become ready and prints the received data. Since ch2 has a longer delay, the select statement will often receive data from ch1 first. The for loop iterates 10 times, receiving data from the channels concurrently.

Conclusion

The select statement is a powerful feature in Go, enabling effective concurrent communication and synchronization between goroutines. It provides a concise and efficient way to work with channel operations, making it a valuable tool for easily building concurrent programs.

Free Resources

Copyright ©2025 Educative, Inc. All rights reserved