Skip to content
Theory · Goroutines and channels · lesson 8 of 13

select

Picture a waiter standing between two serving hatches: hot dishes come through one, drinks through the other. If the waiter waits only at the hatch for hot dishes, ready drinks sit there until a hot dish is served. It works better to stand between the hatches and take whatever appears first.

Channels work the same way. A receive from a channel waits until a value appears in the channel. When a value may arrive on either of two channels, waiting on one of them does not work: <-nums waits forever if the value came into words.

Waiting on several channels

The select statement waits on several channel operations at once and runs the one that is ready:

GO
package main

import "fmt"

func main() {
    nums := make(chan int)
    words := make(chan string, 1)
    words <- "go"
    select {
    case n := <-nums:
        fmt.Println("number", n)
    case w := <-words:
        fmt.Println("word", w) // word go
    }
}

Let's walk through the example step by step:

  1. nums := make(chan int) creates a channel nobody sends into.
  2. words := make(chan string, 1) creates a buffered channel, and words <- "go" puts "go" in the buffer.
  3. select watches both hatches: the receive from nums is not ready, the receive from words is.
  4. The second case runs, and the program prints word go.

The cases of select

select is written like switch, but each case in it is a channel operation. There are two such operations.

Receive

The case case w := <-words: runs when a value can be received from the channel. A variable declared in a receive, like w in the example, exists only inside its own case. The received value need not be stored: case <-done:.

Send

A case can also send: case ch <- v: runs when the channel is ready to take the value.

How a case is chosen

select follows three rules:

  • if no operation is ready, select waits until one becomes ready;
  • only one operation runs: the other cases are skipped, and no value is received from their channels;
  • if several operations are ready, select picks one of them at random.

This is where the waiter picture stops working: a person would walk to the nearer hatch, while select draws lots.

The order of the cases in the code does not affect the choice, unlike switch, which checks from top to bottom. So a program with several ready channels may print different things on different runs. Interviewers like to ask this: which case runs if both are ready? The answer is either one; the choice is random.

The default case

The default branch runs when no operation is ready, and then select does not wait:

GO
ch := make(chan int, 1)
select {
case v := <-ch:
    fmt.Println(v)
default:
    fmt.Println("empty") // empty
}

Here is what happens:

  • the channel is empty, so default runs: the waiter glanced at the hatch, saw an empty shelf and went off to other work;
  • if a value is sent into ch before the select, the first case runs and prints it.

This is how a goroutine checks a channel without stopping: a receive from an empty channel without select would wait until someone sends a value.

Common mistake

A select sits inside a for loop, and the loop is meant to end with break when the done channel is closed:

GO
for {
    select {
    case v := <-nums:
        fmt.Println(v)
    case <-done:
        break // ✗
    }
}

Here is what happens:

  1. A break inside select leaves only the select, as it does in switch.
  2. The for loop goes on.
  3. A closed done channel is always ready to receive, so the loop spins forever.

There are two ways to fix it:

  • move the loop into its own function and replace break with return;
  • put a label before for and write break with its name.

Key points

  • select waits on several channel operations and runs one that is ready.
  • If several are ready, the choice is random; the order of the cases in the code does not matter.
  • default runs when nothing is ready, and then select does not wait.
  • A break inside select leaves only the select, not the loop around it.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way