SyntaxStudy
Sign Up
Go Select Statement and Channel Patterns
Go Beginner 1 min read

Select Statement and Channel Patterns

The `select` statement lets a goroutine wait on multiple channel operations simultaneously. It is analogous to a switch statement, but each case is a channel send or receive. When multiple cases are ready at the same time, one is chosen at random. A `default` case makes select non-blocking: if no channel is ready, the default case executes immediately. Common patterns enabled by select include timeouts (selecting between a result channel and a `time.After` channel), cancellation (selecting between work channels and a done channel), fan-out (distributing work to multiple worker channels), and fan-in (merging results from multiple channels into one). These patterns compose naturally to build complex concurrent pipelines. A done channel (`chan struct{}`) is the standard way to signal cancellation across goroutines. Closing the done channel broadcasts the signal to all goroutines that are waiting on it, unlike a regular send which only unblocks one receiver. The zero-sized `struct{}` type is used to minimise memory overhead since no data is communicated, only the signal.
Example
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func slowOp(id int) <-chan string {
    ch := make(chan string)
    go func() {
        delay := time.Duration(rand.Intn(300)) * time.Millisecond
        time.Sleep(delay)
        ch <- fmt.Sprintf("result from op %d (took %v)", id, delay)
    }()
    return ch
}

// fanIn merges multiple channels into one
func fanIn(cs ...<-chan string) <-chan string {
    merged := make(chan string)
    for _, c := range cs {
        c := c // capture
        go func() {
            for v := range c {
                merged <- v
            }
        }()
    }
    return merged
}

func main() {
    // Select with timeout
    ch := slowOp(1)
    select {
    case result := <-ch:
        fmt.Println(result)
    case <-time.After(150 * time.Millisecond):
        fmt.Println("timed out waiting for op 1")
    }

    // Non-blocking receive with default
    ready := make(chan int, 1)
    select {
    case v := <-ready:
        fmt.Println("got:", v)
    default:
        fmt.Println("nothing ready, continuing")
    }

    // Fan-in from multiple goroutines
    merged := fanIn(slowOp(2), slowOp(3), slowOp(4))
    timeout := time.After(400 * time.Millisecond)
    for i := 0; i < 3; i++ {
        select {
        case msg := <-merged:
            fmt.Println(msg)
        case <-timeout:
            fmt.Println("overall timeout reached")
            return
        }
    }
}