Skip to content

Channels

A channel is a typed conduit through which goroutines communicate. Sending a value into a channel and receiving from it are the two operations that transfer both data and synchronization in a single step.

ch := make(chan int) // unbuffered channel of int
ch <- 42 // send (blocks until a receiver is ready)
v := <-ch // receive (blocks until a sender is ready)

The type system enforces what flows through a channel. A chan string will not accept an int.

UnbufferedBuffered
Created withmake(chan T)make(chan T, capacity)
Send blocks?Yes — until a receiver is readyNo — until the buffer is full
Receive blocks?Yes — until a sender is readyNo — until the buffer is empty
Synchronizes?Yes — sender and receiver meet at the same momentNo — the buffer decouples timing

Unbuffered channels provide the strongest synchronization guarantee: the sender knows the receiver has the value before it continues.

You can restrict a channel parameter to send-only or receive-only to encode the intent at the type level:

func producer(out chan<- int) { out <- 1 } // send-only
func consumer(in <-chan int) { v := <-in; _ = v } // receive-only

A bidirectional chan T is assignable to either direction. The compiler enforces that a receive-only channel cannot be sent to, and vice versa.

The sender calls close(ch) to signal that no more values will be sent. Receivers can use a for range loop to drain all values until close:

close(ch)
for v := range ch { // exits when ch is closed and empty
fmt.Println(v)
}

A receive expression can return a second boolean that tells you whether the channel is still open:

v, ok := <-ch
// ok == true → v is a real value
// ok == false → channel is closed and drained; v is the zero value
package main
import "fmt"
func producer(ch chan<- int, n int) {
for i := 1; i <= n; i++ {
ch <- i
}
close(ch)
}
func main() {
// Unbuffered channel: producer runs in a goroutine
ch := make(chan int)
go producer(ch, 5)
for v := range ch {
fmt.Println(v)
}
// Buffered channel: all sends fit in the buffer
buf := make(chan string, 3)
buf <- "a"
buf <- "b"
buf <- "c"
close(buf)
for s := range buf {
fmt.Println(s)
}
// Comma-ok: distinguish real value from closed zero value
done := make(chan bool, 1)
done <- true
close(done)
v1, ok1 := <-done
fmt.Println(v1, ok1) // true true
v2, ok2 := <-done
fmt.Println(v2, ok2) // false false
}
What happens when you receive from an unbuffered channel when no goroutine is sending?
You call close(ch) on a buffered channel that still has 3 items in it. What happens when you range over it?
What does the second return value ok in `v, ok := <-ch` tell you?
Which channel direction annotation restricts a parameter to receive-only?