Tonight I attended the Go Amsterdam meetup at the JetBrains office in Amsterdam, and one talk stood out for anyone who writes concurrent Go: Jesús Espino’s “Deep dive into the select statement.” The select statement is one of those features every Gopher uses but few dig into past the first example, and Jesús took it apart from the ground up.
Why select Matters
At its core, select lets a goroutine wait on multiple channel operations at once. It is the concurrency primitive that turns Go’s “share memory by communicating” philosophy into something you can actually build on — multiplexing events, fanning in streams, and building responsive servers.
The shape is deliberately close to a switch:
select {
case v := <-ch1:
fmt.Println("from ch1:", v)
case ch2 <- 42:
fmt.Println("sent to ch2")
default:
fmt.Println("no channel ready")
}But the semantics are where the depth lives.
What Jesús Covered
A few points from the talk that are easy to get wrong in production:
- Random fair selection. When several cases are ready, Go picks one at random rather than top-to-bottom. That fairness is a feature — it prevents accidental priority starvation — but it also means you cannot rely on case order.
- The
defaultcase makes select non-blocking. Without it,selectblocks until at least one channel is ready. With it, the statement returns immediately if nothing is ready, which is the backbone of polling loops and timeouts. time.Afterinside aselectcan leak. A common gotcha:case <-time.After(...)allocates a new timer on every iteration. In a long-lived loop that never fires, those timers pile up. Prefer a reusabletime.NewTimer/time.NewTickeryou reset or stop.nilchannels disable a case. Setting a channel variable tonilremoves its case from consideration — a neat trick for dynamically enabling or disabling branches without restructuring theselect.context.Contextis the clean way to cancel.case <-ctx.Done():is how you bail out of aselectwhen the work is no longer needed, avoiding goroutine leaks.
The recurring theme: select is simple to read and easy to misuse. Most “my goroutine is stuck / my memory is climbing” bugs in concurrent Go trace back to a select that never had a way out.
Takeaway
If you write Go services, the select statement is where responsiveness and correctness meet. Treat every select as a small state machine: make sure every branch can exit, prefer context for cancellation, and watch timer allocations in hot loops. Jesús’s deep dive was a good reminder that the language’s most elegant concurrency primitive rewards actually understanding it.
Related Content
- The Go Programming Language: a practical guide
- Go Meetup Amsterdam: The Curious Case of the Missing Memory
- Go Meetup Amsterdam: Staying Passionate in Tech in the Age of AI
- Go Meetup Amsterdam: Event Recap









