Lab: concurrency patterns
Scenario questions on goroutine leaks, channel direction, WaitGroup vs channels, and context cancellation.
- Identify goroutine leak scenarios and their fixes
- Read directional channel types
- Choose WaitGroup vs channels for a given coordination problem
- Trace context cancellation through a call chain
Optional lab. These scenarios cover the most common concurrency mistakes and patterns in production Go code. Goroutine leaks are a particularly common source of hard-to-diagnose memory growth.
Goroutine leaks
Knowledge check
- 1.A goroutine sends a result to an unbuffered channel. The receiver exits early (due to a timeout) without receiving. What happens to the goroutine?
- 2.Which of these is the standard fix for a goroutine that could block forever on an unbuffered channel send?
Channel direction
Knowledge check
- 1.A function signature is: func produce(out chan<- int). What does chan<- mean?
- 2.A bidirectional chan int can be passed to a function that accepts a send-only chan<- int.
WaitGroup vs channels
Knowledge check
- 1.You launch 10 goroutines to process independent tasks. You only need to know when all 10 are done, not their results. Which is most idiomatic?
- 2.You launch 10 goroutines and need to collect all their results. Which approach handles this naturally?
Context cancellation
Knowledge check
- 1.A context.WithTimeout is created in an HTTP handler with a 3-second budget. The handler calls a service function, which calls a database function. The query takes 5 seconds. What happens?
- 2.ctx.Err() returns context.Canceled when a deadline expires.
A complete leak scenario
Read this code and identify the bug:
func fetchAll(ids []int) []string {
results := make(chan string) // unbuffered
for _, id := range ids {
go func(id int) {
results <- fetch(id) // goroutine sends result
}(id)
}
var out []string
for i := 0; i < 3; i++ { // reads only 3 results, not len(ids)
out = append(out, <-results)
}
return out
}If len(ids) is greater than 3, the goroutines for the extra IDs will be permanently blocked on results <- ... with no receiver — a goroutine leak. The fix is either to read all results (for i := 0; i < len(ids); i++) or to use a buffered channel (make(chan string, len(ids))).
This pattern is one of the most common concurrency bugs in Go services. Recognising it by shape is a valuable skill.
Where to go next
The next module, Production Go, moves from concurrency to the full production story: HTTP servers, JSON encoding, profiling, and building and deploying binaries.