Concurrency Patterns in Go
In real-world Go applications, concurrency is not just about using goroutines. It is about structuring them correctly using proven concurrency patterns.
In this lesson, you will learn the most important concurrency patterns used in production Go systems such as APIs, background workers, data pipelines, and distributed services.
What Are Concurrency Patterns?
Concurrency patterns are reusable designs that solve common problems related to:
- Task coordination
- Parallel execution
- Cancellation and timeouts
- Data sharing between goroutines
Go encourages simple, readable concurrency patterns using goroutines, channels, and select statements.
Why Concurrency Patterns Matter
Poorly designed concurrency can lead to:
- Deadlocks
- Race conditions
- Memory leaks
- Unpredictable behavior
Using standard patterns helps you build safe, maintainable, and scalable systems.
Pattern 1: Fan-Out / Fan-In
This pattern distributes work to multiple goroutines (fan-out) and collects results into a single channel (fan-in).
Use Case
Processing a large dataset in parallel.
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
results <- job * 2
}
}
jobs := make(chan int, 5)
results := make(chan int, 5)
for i := 1; i <= 3; i++ {
go worker(i, jobs, results)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= 5; a++ {
fmt.Println(<-results)
}
Multiple workers process jobs concurrently, and results are collected safely.
Pattern 2: Pipeline
A pipeline breaks a task into stages, where each stage runs in its own goroutine and passes data to the next stage.
Use Case
Data processing pipelines (ETL systems).
func generate(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
for result := range square(generate(1, 2, 3, 4)) {
fmt.Println(result)
}
Each stage is isolated and easy to test.
Pattern 3: Worker Pool
Worker pools limit the number of concurrent goroutines processing tasks.
Use Case
Handling API requests or background jobs.
const workers = 3
jobs := make(chan int)
results := make(chan int)
for w := 1; w <= workers; w++ {
go worker(w, jobs, results)
}
This prevents system overload by controlling concurrency.
Pattern 4: Cancellation with Context
Goroutines must stop work when cancellation is requested.
select {
case <-ctx.Done():
return
case data := <-input:
process(data)
}
This pattern is essential for graceful shutdown.
Pattern 5: Timeout Pattern
Prevent blocking forever by enforcing time limits.
select {
case res := <-ch:
fmt.Println(res)
case <-time.After(2 * time.Second):
fmt.Println("Timeout")
}
Pattern 6: Select Multiplexing
The select statement allows waiting on multiple channels.
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
default:
fmt.Println("No data available")
}
Pattern 7: Graceful Shutdown
Graceful shutdown ensures all goroutines complete before program exit.
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
<-signalChan
fmt.Println("Shutting down...")
Real-World Example
Modern Go servers combine multiple patterns:
- Worker pools for request handling
- Pipelines for data processing
- Context cancellation for timeouts
- Graceful shutdown on signals
These patterns together create robust production systems.
Common Mistakes
- Launching unlimited goroutines
- Ignoring cancellation signals
- Blocking on channels without timeouts
- Sharing mutable state without protection
Practice Exercises
Exercise 1
Implement a worker pool that processes numbers and returns their squares.
Exercise 2
Build a pipeline with three stages and observe data flow.
Key Takeaways
- Concurrency patterns improve reliability
- Go provides simple primitives to implement them
- Patterns reduce bugs and complexity
- Used extensively in production systems
What’s Next?
In the next lesson, you will move into building APIs with Go and apply concurrency patterns in real projects.