Golang Buffered Channel: Capacity, Blocking, and Buffered vs Unbuffered

Learn buffered channels in Go: make(chan T,n) capacity, len and cap, when send and receive block, buffered vs unbuffered channels, backpressure, deadlocks, choosing buffer size, and practical patterns.

Published

Updated

Read time 5 min read

Reviewed byDeepak Prasad

Golang Buffered Channel: Capacity, Blocking, and Buffered vs Unbuffered

This page explains buffered channels in Go: how make(chan T, n) differs from an unbuffered channel, exactly when sends and receives block, how len and cap relate to capacity, and why buffering is backpressure rather than an unlimited queue. For the full channel tour (range, close, select), read channels in Go; for workers and fan-out, see goroutines and multiple receivers on one channel.

Tested with Go 1.24 on Linux.


Quick answer: what “buffered” means

A buffered channel has a fixed capacity n from make(chan T, n). Values sit in a queue until a receiver takes them: a send blocks only when the buffer is already full, and a receive blocks only when the buffer is empty. An unbuffered channel has capacity zero and synchronizes each send with a matching receive.


What is a buffered channel?

A channel carries typed values between goroutines. With capacity greater than zero, the runtime stores up to n values between sender and receiver so the two sides can proceed at different speeds until the queue fills or drains. That decouples producer and consumer up to n—it does not remove the need for a receiver forever.


Syntax: make(chan T, n), len, and cap

go
package main

import "fmt"

func main() {
	unbuf := make(chan int)
	buf := make(chan int, 3)
	fmt.Println(cap(unbuf), cap(buf))
}
Output

You should see 0 and 3. cap(ch) is always the buffer size. For an unbuffered channel, cap is zero.

go
package main

import "fmt"

func main() {
	ch := make(chan int, 3)
	fmt.Println("empty", len(ch), cap(ch))
	ch <- 10
	ch <- 20
	fmt.Println("two items", len(ch), cap(ch))
	<-ch
	fmt.Println("after one recv", len(ch), cap(ch))
}
Output

You should see empty 0 3, then two items 2 3, then after one recv 1 3. len(ch) is how many values are queued right now; it can change the instant another goroutine runs, so do not use len for cross-goroutine synchronization.


Send and receive blocking rules

Operation Unbuffered (make(chan T)) Buffered (make(chan T, n), n > 0)
Send Blocks until another goroutine receives Blocks only when len == cap (buffer full)
Receive Blocks until another goroutine sends Blocks only when len == 0 (buffer empty)

The buffer is not an infinite queue: if the producer keeps sending faster than the consumer receives, the buffer fills and the producer blocks—that is backpressure.


Buffered vs unbuffered channels

Unbuffered channels act like a handoff: the sender and receiver meet at the same time. Buffered channels let the sender deposit values into the queue (up to n) before a receiver is ready, and let the receiver drain multiple values that arrived earlier.

go
package main

import "fmt"

func main() {
	ch := make(chan string)
	go func() { ch <- "ping" }()
	fmt.Println(<-ch)
}
Output

You should see ping (unbuffered rendezvous).

go
package main

import "fmt"

func main() {
	ch := make(chan string, 2)
	ch <- "a"
	ch <- "b"
	fmt.Println(<-ch, <-ch)
}
Output

You should see a b (two sends completed before receives, because cap is 2).


Capacity, backpressure, and producer–consumer behavior

Small buffers smooth short bursts: the producer can get a few values ahead of the consumer. A large buffer delays visible blocking but stores more in-flight work and can hide a slow consumer until memory and latency grow. Choose capacity from expected burst size, a concurrency limit (similar to a semaphore), or measured behavior—not “as large as possible” by default.


Producer, consumer, close, and range

Only the sender side should close a channel when no more values will be sent. Receivers use for v := range ch to drain until close.

go
package main

import (
	"fmt"
	"sync"
)

func main() {
	ch := make(chan int, 2)
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 1; i <= 5; i++ {
			ch <- i
			fmt.Println("sent", i)
		}
		close(ch)
	}()
	for v := range ch {
		fmt.Println("recv", v)
	}
	wg.Wait()
}
Output

You should see interleaved sent and recv lines; with buffer 2 the producer can usually send two values before it may block waiting for space.


Real-world uses (brief)

Pattern Role of buffering
Worker pool / job queue Bounded queue between producer and fixed workers
Rate limiting / semaphore Token buffer caps concurrent work
Bursty logging or metrics Short queue absorbs spikes without blocking callers
Pipeline stages Small buffer between stages reduces stalls

Deadlocks and common mistakes

Sending on a full buffer with no receiver and no other goroutine to free space deadlocks single-threaded programs. Receiving from an empty buffer with no sender deadlocks the same way. A huge buffer does not fix a missing consumer—it only postpones blocking and uses more memory.

Relying on len(ch) to decide “safe to send” is a race unless you already hold synchronization elsewhere. Assuming close is optional for every receive path is wrong: range over an open channel never ends if nobody closes.


Choosing buffer size

Start small (0, 1, or a few times your natural parallelism). Match a known limit (for example “at most three downstream RPCs in flight”). Measure under load and adjust; if latency grows with buffer size, the consumer is slower than the producer and buffering only queues work.


Cheat sheet

Concept Meaning
make(chan T) Unbuffered channel
make(chan T, n) with n > 0 Buffered channel, capacity n
cap(ch) Maximum queued values
len(ch) Values currently queued (not for sync)
Send to full buffer Blocks
Receive from empty buffer Blocks
close(ch) No more sends; range ends after drain
Large buffer More queueing, more memory, can hide slowness
Small buffer Tighter backpressure, less smoothing

Summary

A golang buffered channel uses make(chan T, n) with n > 0 so values queue until the buffer fills; sends block when len == cap, receives block when len == 0. Unbuffered channels synchronize each send with a receive. Use cap and len for inspection only; size buffers for real bursts and backpressure, not as unlimited queues. Close from the sender; pair close with range or explicit receives to avoid leaks and deadlocks.


References


Frequently Asked Questions

1. What is a golang buffered channel?

A channel created with make(chan T, n) where n > 0; it can hold up to n values so sends do not block until the buffer is full and receives do not block until the buffer is empty.

2. What is the difference between buffered and unbuffered channels in Go?

make(chan T) or make(chan T, 0) is unbuffered: each send synchronizes with a receive. With capacity n > 0, sends can queue values until the buffer fills without a waiting receiver.

3. When does a send on a buffered channel block?

When len equals cap the buffer is full and the sender waits until a receive frees a slot (unless a select default handles it).

4. What do len and cap return for a channel?

cap is the buffer size (0 if unbuffered). len is the number of values currently queued in the buffer; it is not a synchronization primitive.

5. Is a large buffer always better?

No; a large buffer hides slow consumers, uses more memory, and increases latency; size for a known burst or concurrency limit, then measure.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …