Skip to content

Go Buffered Channels

By default, channels are unbuffered, meaning they only accept sends if there is a corresponding receive ready. Buffered channels accept a limited number of values without a corresponding receiver for those values.

1. Basic Example

You specify the buffer capacity as the second argument to make.

package main

import "fmt"

func main() {
    // 1. Create a channel buffered up to 2 values
    messages := make(chan string, 2)

    // 2. Because this channel is buffered, we can send these 
    // values into the channel without a corresponding concurrent receive.
    messages <- "buffered"
    messages <- "channel"

    // 3. Later we can receive these two values as usual.
    fmt.Println(<-messages)
    fmt.Println(<-messages)
}

2. Blocking Behavior

  • Sending: A send into a buffered channel blocks only when the buffer is full.
  • Receiving: A receive from a buffered channel blocks only when the buffer is empty.
1
2
3
4
5
6
7
ch := make(chan int, 1)

ch <- 1    // Works immediately
// ch <- 2 // Would BLOCK here because the buffer is full

fmt.Println(<-ch) // 1
// fmt.Println(<-ch) // Would BLOCK here because the buffer is empty

Why use Buffered Channels?

  1. Performance: They can reduce overhead by allowing a "burst" of work to be sent without waiting for the consumer to catch up immediately.
  2. Decoupling: Senders and receivers don't have to stay in perfect "lock-step."
  3. Rate Limiting: They are often used to limit the number of concurrent operations (e.g., only allow 100 HTTP requests at a time).

Common Pattern: Waiting for Workers

Buffered channels are useful when you know exactly how many results you are waiting for.

results := make(chan int, 3)

for i := 0; i < 3; i++ {
    go func(n int) {
        results <- n * 2
    }(i)
}

// We can receive 3 results without worrying about timing
for i := 0; i < 3; i++ {
    fmt.Println(<-results)
}