Go by Example

Channel Buffering

Buffered channels accept sends without a waiting receiver - a bounded queue between goroutines.

A buffered channel holds a fixed number of values without a receiver being ready. Sends block only when the buffer is full; receives block only when the buffer is empty.

Pass a second argument to make to set the buffer size. Here, two sends complete without a corresponding receive because the buffer has capacity two.

package main
 
import "fmt"
 
func main() {
    ch := make(chan string, 2)
 
    ch <- "first"
    ch <- "second"
 
    fmt.Println(<-ch) // first
    fmt.Println(<-ch) // second
}

len returns the number of values currently in the buffer; cap returns the total capacity:

ch := make(chan int, 5)
ch <- 1
ch <- 2
fmt.Println(len(ch), cap(ch)) // 2 5

A buffered channel of size 1 is often enough to let two goroutines avoid deadlocking when both want to send before either receives:

ping := make(chan string, 1)
ping <- "ping"        // does not block - buffer absorbs it
msg := <-ping
fmt.Println(msg)

In production

A buffered channel is a bounded queue - use it to decouple producer and consumer speeds, not to hide race conditions. A buffer of 1 can break a deadlock between two goroutines that both need to send before receiving. Buffer sizes above roughly 100 usually signal a design problem: the producer is outrunning the consumer faster than any fixed buffer can absorb. In that case, add backpressure (block the producer or drop work explicitly) rather than increasing the buffer. Monitor len(ch) relative to cap(ch) in production to detect sustained queue buildup before it becomes a latency spike.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free