Skip to content

Select

In TypeScript Promise.race accepts an array of Promises and resolves with whichever settles first:

const result = await Promise.race([
fetch('/api/primary'),
fetch('/api/backup'),
]);

In Go, select does the same for channels — it waits on multiple channel operations and proceeds with the first one that is ready:

select {
case msg := <-ch1:
fmt.Println("from ch1:", msg)
case msg := <-ch2:
fmt.Println("from ch2:", msg)
}

select blocks until at least one case can proceed. If multiple cases are ready simultaneously, Go picks one uniformly at random — keeping the scheduling fair but non-deterministic.

TypeScript
// TypeScript: Promise.race for the first result
async function withTimeout<T>(
promise: Promise<T>,
ms: number
): Promise<T> {
const timeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error("timeout")), ms)
);
return Promise.race([promise, timeout]);
}
Go
// Go: select picks the first ready channel
package main
import "fmt"
func main() {
ch1 := make(chan string, 1)
ch2 := make(chan string, 1)
ch1 <- "one"
ch2 <- "two"
// Use separate selects so output is deterministic
select {
case msg := <-ch1:
fmt.Println("Received from ch1:", msg)
}
select {
case msg := <-ch2:
fmt.Println("Received from ch2:", msg)
}
}

Add a default branch to make select return immediately when no channel is ready instead of blocking:

TypeScript
// TypeScript: non-blocking check via Promise.race with null fallback
async function tryReceive(): Promise<string | null> {
return Promise.race([
getMessage(),
Promise.resolve(null), // resolves immediately
]);
}
Go
// Go: non-blocking select with default
package main
import "fmt"
func main() {
ch := make(chan string, 1)
select {
case msg := <-ch:
fmt.Println("Got:", msg)
default:
fmt.Println("No message available") // runs immediately
}
ch <- "hello"
select {
case msg := <-ch:
fmt.Println("Got:", msg)
default:
fmt.Println("No message available")
}
}

The most common real-world use of select is to simultaneously listen for incoming work and a cancellation signal from a context.Context:

TypeScript
// TypeScript: AbortController for cooperative cancellation
async function processItems(
items: string[],
signal: AbortSignal
) {
for (const item of items) {
if (signal.aborted) break;
await processItem(item);
}
}
Go
// Go: select listening to both a jobs channel and ctx.Done()
package main
import (
"context"
"fmt"
"sync"
)
func worker(ctx context.Context, jobs <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case j, ok := <-jobs:
if !ok {
return // channel was closed — no more work
}
fmt.Println("Processing job:", j)
case <-ctx.Done():
fmt.Println("Worker cancelled:", ctx.Err())
return
}
}
}
package main
import "fmt"
func main() {
ch1 := make(chan string, 1)
ch2 := make(chan string, 1)
ch1 <- "hello"
ch2 <- "world"
// Receive from ch1 first (deterministic: separate selects)
select {
case msg := <-ch1:
fmt.Println("ch1:", msg)
}
// Receive from ch2
select {
case msg := <-ch2:
fmt.Println("ch2:", msg)
}
// default: non-blocking — ch1 is empty now
select {
case msg := <-ch1:
fmt.Println("extra:", msg)
default:
fmt.Println("no more messages")
}
}
What JavaScript construct is most analogous to Go's select statement?
When multiple cases in a select are ready at the same time, what happens?
What does the default case in a select do?