Menu
Coddy logo textTech

Select Statement

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 62 of 107.

The select statement lets a goroutine wait on multiple channel operations simultaneously. It's like a switch statement, but each case involves a channel send or receive. When multiple channels are ready, select picks one at random.

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() { ch1 <- "from channel 1" }()
    go func() { ch2 <- "from channel 2" }()
    
    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    }
}

Without select, receiving from ch1 would block until data arrives, potentially missing data on ch2. With select, whichever channel receives data first gets handled.

The default case makes select non-blocking—it executes immediately if no channels are ready:

select {
case msg := <-messages:
    fmt.Println("Received:", msg)
default:
    fmt.Println("No message available")
}

A common pattern combines select with timeouts using time.After, which returns a channel that receives a value after the specified duration:

select {
case result := <-ch:
    fmt.Println("Got result:", result)
case <-time.After(2 * time.Second):
    fmt.Println("Timeout!")
}

This prevents your program from waiting forever when a channel operation might never complete.

challenge icon

Challenge

Easy

Let's build a service monitor that uses the select statement to handle multiple concurrent data sources. Your monitor will listen to different service channels simultaneously and respond appropriately when data arrives or when no updates are available.

You'll organize your code across two files:

  • monitor.go: Define your service monitoring logic with select-based channel handling.

    Create a ServiceUpdate struct with Name (string) and Status (string) fields.

    Implement a Monitor function that takes three channels: primary chan ServiceUpdate, backup chan ServiceUpdate, and done chan bool. This function should use select to listen on all three channels simultaneously:

    • When receiving from primary: return the formatted string Primary: [Name] is [Status]
    • When receiving from backup: return the formatted string Backup: [Name] is [Status]
    • When receiving from done: return Monitoring stopped

    Implement a CheckStatus function that takes a single updates chan ServiceUpdate and uses select with a default case to perform a non-blocking check:

    • If an update is available: return Update available: [Name]
    • If no update is available (default case): return No updates pending
  • main.go: Set up the channels and demonstrate both blocking and non-blocking select behavior.

    Read a mode from input that determines which scenario to run: primary, backup, stop, or check.

    For modes primary, backup, and stop: also read a service name and status. Create the three channels, launch a goroutine that sends data to the appropriate channel based on the mode, then call Monitor and print its result.

    For mode check: read a service name and status, plus a flag (send or nosend) indicating whether to send data before checking. Create a buffered channel with capacity 1. If the flag is send, send an update to the channel before calling CheckStatus. Print the result of CheckStatus.

The following inputs will be provided:

  • Line 1: Mode (primary, backup, stop, or check)
  • Line 2: Service name
  • Line 3: Service status
  • Line 4 (only for check mode): send or nosend

For example, given:

primary
Database
healthy

Your output should be:

Primary: Database is healthy

And given:

check
Cache
active
nosend

Your output should be:

No updates pending

Cheat sheet

The select statement allows a goroutine to wait on multiple channel operations simultaneously. It works like a switch statement, but each case involves a channel send or receive:

select {
case msg1 := <-ch1:
    fmt.Println(msg1)
case msg2 := <-ch2:
    fmt.Println(msg2)
}

When multiple channels are ready, select picks one at random. Without select, receiving from one channel would block and potentially miss data on another channel.

The default case makes select non-blocking—it executes immediately if no channels are ready:

select {
case msg := <-messages:
    fmt.Println("Received:", msg)
default:
    fmt.Println("No message available")
}

A common pattern combines select with timeouts using time.After, which returns a channel that receives a value after the specified duration:

select {
case result := <-ch:
    fmt.Println("Got result:", result)
case <-time.After(2 * time.Second):
    fmt.Println("Timeout!")
}

This prevents your program from waiting forever when a channel operation might never complete.

Try it yourself

package main

import (
	"fmt"
)

func main() {
	var mode string
	fmt.Scanln(&mode)

	var serviceName string
	fmt.Scanln(&serviceName)

	var serviceStatus string
	fmt.Scanln(&serviceStatus)

	// TODO: Handle different modes: "primary", "backup", "stop", "check"
	
	// For modes "primary", "backup", "stop":
	// - Create three channels: primary, backup, done
	// - Launch a goroutine that sends data to the appropriate channel
	// - Call Monitor and print the result
	
	// For mode "check":
	// - Read the additional flag ("send" or "nosend")
	// - Create a buffered channel with capacity 1
	// - If flag is "send", send an update to the channel
	// - Call CheckStatus and print the result
	
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming