Buffered Channels in Go
In the previous lesson, you learned about unbuffered channels, where sending and receiving operations block until both sides are ready. In this lesson, we introduce buffered channels, which allow a limited number of values to be sent without immediate receiving.
What Is a Buffered Channel?
A buffered channel has a fixed capacity. It can store a certain number of values internally before blocking the sender.
This makes buffered channels useful when:
- Producers are faster than consumers
- You want limited asynchronous communication
- You want to reduce blocking between goroutines
Creating a Buffered Channel
Buffered channels are created using make with a capacity value.
ch := make(chan int, 3)
This channel can hold up to 3 integers before blocking the sender.
How Buffered Channels Work
Buffered channels behave differently depending on their state:
- Sending blocks only when the buffer is full
- Receiving blocks only when the buffer is empty
Think of a buffered channel like a queue with limited capacity.
Simple Buffered Channel Example
package main
import "fmt"
func main() {
ch := make(chan string, 2)
ch <- "Order A"
ch <- "Order B"
fmt.Println(<-ch)
fmt.Println(<-ch)
}
Here, two values are sent without blocking because the buffer size is 2.
Real-World Example: Order Processing System
Imagine an e-commerce system where orders are queued before processing. Each order is represented by an ID.
func main() {
orders := make(chan int, 5)
for i := 101; i <= 105; i++ {
orders <- i
fmt.Println("Queued order:", i)
}
close(orders)
for order := range orders {
fmt.Println("Processing order:", order)
}
}
The buffered channel stores multiple orders before they are processed.
Checking Channel Capacity and Length
Go provides built-in functions to inspect buffered channels.
fmt.Println(len(ch)) // current items
fmt.Println(cap(ch)) // maximum capacity
These are useful for monitoring queue behavior in real systems.
Buffered vs Unbuffered Channels
Key differences:
- Unbuffered channels synchronize immediately
- Buffered channels allow limited decoupling
- Buffered channels reduce blocking but require careful sizing
When Buffered Channels Are Useful
- Task queues
- Worker pools
- Batch processing
- Rate-limited systems
However, buffered channels should not replace proper concurrency design.
Common Mistakes with Buffered Channels
- Using excessively large buffers without need
- Assuming buffered channels remove all blocking
- Ignoring channel closure
- Using buffers to hide poor synchronization logic
Buffered Channels with Goroutines
Buffered channels shine when combined with goroutines.
func worker(ch chan int) {
fmt.Println("Processed:", <-ch)
}
func main() {
ch := make(chan int, 2)
ch <- 10
ch <- 20
go worker(ch)
go worker(ch)
}
The buffer allows values to be queued before workers start.
Practice Exercises
Exercise 1
Create a buffered channel of size 3, send three numbers, and print them.
Exercise 2
Try sending a fourth value and observe what happens.
Key Takeaways
- Buffered channels store values temporarily
- They reduce blocking between goroutines
- Capacity must be chosen carefully
- They are ideal for queues and pipelines
What’s Next?
In the next lesson, you will learn about the Select Statement and how to work with multiple channels efficiently.