Code of the Day
AdvancedConcurrency

Lab: concurrency patterns

Scenario questions on goroutine leaks, channel direction, WaitGroup vs channels, and context cancellation.

Lab · optionalGoAdvanced15 min
Recommended first
By the end of this lesson you will be able to:
  • 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. 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. 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. 1.
    A function signature is: func produce(out chan<- int). What does chan<- mean?
  2. 2.
    A bidirectional chan int can be passed to a function that accepts a send-only chan<- int.

WaitGroup vs channels

Knowledge check

  1. 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. 2.
    You launch 10 goroutines and need to collect all their results. Which approach handles this naturally?

Context cancellation

Knowledge check

  1. 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. 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.

Finished reading? Mark it complete to track your progress.

On this page