~/brav0kado
← guides
#Go#Concurrency#Backend

Go Concurrency Patterns You'll Actually Use

Practical patterns for goroutines, channels, and context cancellation — without the theory overload.

January 5, 2026·10 min read

The three patterns

Most real-world Go concurrency fits into three shapes: fan-out/fan-in, worker pools, and pipelines.

Fan-out / fan-in

Launch N goroutines for independent work, collect results via a channel:

results := make(chan Result, len(items))
for _, item := range items {
    go func(i Item) { results <- process(i) }(item)
}
for range items {
    r := <-results
    // handle r
}

Worker pools

Cap concurrency at N workers. Use a buffered channel as a semaphore:

sem := make(chan struct{}, maxWorkers)
for _, item := range items {
    sem <- struct{}{}
    go func(i Item) {
        defer func() { <-sem }()
        process(i)
    }(item)
}

Context cancellation

Always accept a context.Context and respect it:

func doWork(ctx context.Context) error {
    select {
    case <-ctx.Done():
        return ctx.Err()
    case result := <-workCh:
        return handle(result)
    }
}

Pass the context down — never store it in a struct.