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.After in a select case to implement operation timeouts
  • A default case makes select non-blocking — runs immediately if no channel is ready
  • Select inside a loop is the standard pattern for multiplexing multiple goroutines

Leave a Comment