Go Rant: Buffered Channels Should be Tossed in a Fire

Empirical and Theoretical Arguments Why This Feature Sucks

Nick Santos
3 min readMay 21, 2021

Channels are Go’s fundamental tool for concurrent programming.

Buffered channels are a small, innocent-looking feature on top of channels. I’m going to try to convince you that they’re a monstrous abomination.

Quick Recap on What I’m Ranting About

First, a quick recap of how channels work if you haven’t used Go in a while.

A channel lets you send data on one goroutine, and receive it on another, concurrent goroutine.

In a normal unbuffered channel, sending data will block until the data is received.

This blocks forever:

// https://play.golang.org/p/eJGdsxiHOIg
ch := make(chan int, 0)
ch <- 1

This prints “Received 1\nPossible”

// https://play.golang.org/p/bBIkIiFXeF2
ch := make(chan int, 0)
go func() {
fmt.Printf("Received: %d\n", <-ch)
ch <- 1

But you can make buffered channels of arbitrary size N. A buffer of size N will block if you send more than N times before receiving.

This prints “Possible” then deadlocks.

// https://play.golang.org/p/GCL8SY_8AUk
ch := make(chan int, 1)
ch <- 1
ch <- 1

My Assertion

There are only 3 reasonable values of N:

  • 0 — the channel always blocks.
  • 1 — the channel lets you push one element at a time.
  • Infinite — the channel lets you keep pushing elements until you run out of memory.

All other values of N will inevitably lead to bugs.


There are two ways to make this case: the empirical argument (why real projects blow up when they use buffered channels) and the theoretical one (why even hypothetical imagined projects will blow up when they use buffered channels).

From the Empirical Argument

Every opensource project I’ve ever seen that used an arbitrary-N buffered channel was using that channel incorrectly. And ended up having bugs.

Someone would inevitably send a list of elements to that channel. But the problem is that you can only do this safely if:

  • You can guarantee that the list is less than N.
  • You can guarantee that no one else is also sending on the channel.
  • OR you can guarantee that another goroutine is available to receive on the channel.

These are often hard guarantees to make! They’re often about other actors that you don’t control (particularly if you got your list of elements somewhere else.) And they’re guarantees that the programming language doesn’t help you enforce at all.

That hints at:

From the Theoretical Argument

“I can only call this function N times synchronously” is an abnormal API constraint. And it’s not a constraint that programming languages do anything to help you enforce.

Programming languages have lots of constraints they do help you to enforce!

  • They help you ensure that a function isn’t called (think: private functions).
  • They help you ensure that a function is called exactly once (think: constructors, sync.Once).
  • They help you ensure that a function is called only once at a time (think: locks/mutexes and all the tooling around them).

Now, I don’t want to harsh your buzz for programming language constraint checks. We live in 2021! Rust’s borrow checker can do compile-time checks that I never would have imagined!

But let’s be honest: the Go-team does not have the appetite for the kind of programming language features that would help you use buffered channels well.


I liked this proposal for Go2 for an unlimited-capacity buffered channel.

But I also liked Ian Lance Taylor’s response!

If we had generic types, unlimited channels could be implemented in a library with full type safety. A library would also make it possible to improve the implementation easily over time as we learn more.

So I’m hoping for a one-two punch:

  1. Go gets generics!
  2. We can add a channel wrapper that makes more sense!!
  3. They can remove buffered channels from the core language!!!
  4. Profit!!!!



Nick Santos

Software Engineer. Trying new things @tilt_dev. Formerly @Medium, @Google. Yay Brooklyn.