Go Lesson 34 – Buffered Channels | Dataplexa

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.