mediumBackend EngineerDistributed Systems
Explain Go channels — buffered vs unbuffered, select statement, and common concurrency patterns
Posted 18/04/2026
by Mehedy Hasan Ador
Question Details
At a distributed systems company:
> "We need to implement a worker pool that processes jobs concurrently with a max of 10 workers, collects results, and handles cancellation. How do channels solve this?"
> "We need to implement a worker pool that processes jobs concurrently with a max of 10 workers, collects results, and handles cancellation. How do channels solve this?"
Suggested Solution
Channel Basics
// Unbuffered: sender blocks until receiver ready
ch := make(chan string)
// Buffered: sender blocks only when buffer full
ch := make(chan string, 10)
// Sending/receiving
ch <- "hello" // Send (blocks if buffer full / no receiver)
msg := <-ch // Receive (blocks if empty)
Worker Pool Pattern
func worker(id int, jobs <-chan Job, results chan<- Result) {
for job := range jobs {
results <- process(job)
}
}
func main() {
jobs := make(chan Job, 100)
results := make(chan Result, 100)
// Start 10 workers
for i := 0; i < 10; i++ {
go worker(i, jobs, results)
}
// Send jobs
for _, job := range allJobs {
jobs <- job
}
close(jobs)
// Collect results
for i := 0; i < len(allJobs); i++ {
result := <-results
fmt.Println(result)
}
}
Select Statement (multiplexing channels)
select {
case msg := <-ch1:
fmt.Println("Received from ch1:", msg)
case msg := <-ch2:
fmt.Println("Received from ch2:", msg)
case <-time.After(5 * time.Second):
fmt.Println("Timeout!")
default:
fmt.Println("No messages ready") // Non-blocking
}
Cancellation with context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
select {
case result := <-doWork(ctx):
fmt.Println("Done:", result)
case <-ctx.Done():
fmt.Println("Cancelled:", ctx.Err()) // context deadline exceeded
}
Common Patterns
chan struct{} for notification