A channel is a typed pipe that goroutines use to send and receive values. One goroutine puts something in, another takes it out.
There’s a famous Go saying that sums up the whole philosophy:
“Don’t communicate by sharing memory; share memory by communicating.”
In simple language: instead of multiple goroutines poking at the same variable behind locks, we pass data through a channel. Whoever holds the value owns it. No locks, no races.
Making and using a channel
We create a channel with make, send with ch <- x, and receive with <-ch.
ch := make(chan int) // a channel that carries ints
go func() {
ch <- 42 // send 42 into the channel
}()
value := <-ch // receive from the channel, blocks until something arrives
fmt.Println(value) // 42
The arrow always points in the direction the data flows. ch <- x means “x goes into ch”. <-ch means “pull a value out of ch”.
Unbuffered vs buffered
This is the part interviewers dig into.
An unbuffered channel (make(chan int)) has no storage. A send blocks until someone is ready to receive, and a receive blocks until someone is ready to send. It’s a synchronous handshake — both sides meet at the same moment.
A buffered channel (make(chan int, 3)) has a little waiting room. Sends only block when the buffer is full; receives only block when it’s empty. So the sender can drop a few values and walk away without waiting.
ch := make(chan int, 2) // buffer holds 2
ch <- 1 // doesn't block
ch <- 2 // doesn't block
// ch <- 3 // would block here — buffer is full
fmt.Println(<-ch, <-ch) // 1 2
Closing and ranging
When we’re done sending, we close the channel. Receivers can then detect it.
ch := make(chan int, 3)
ch <- 1
ch <- 2
close(ch) // no more sends allowed after this
for v := range ch { // range keeps receiving until the channel is closed and drained
fmt.Println(v) // 1, then 2
}
The two-value receive tells us if a channel is closed:
v, ok := <-ch // ok is false once the channel is closed and empty
The dangerous bits (interview favorites)
These three rules trip people up constantly:
- Sending on a closed channel panics. Only the sender should close, never the receiver.
- Receiving from a closed channel returns the zero value immediately (with
ok == false). No panic. - A
nilchannel blocks forever. Sending or receiving on anilchannel never proceeds. Sometimes useful inselectto disable a case, but usually a bug.
var ch chan int // nil channel
// <-ch // blocks forever — deadlock
Interview soundbite
A channel is a typed pipe for passing values between goroutines, embodying Go’s motto “share memory by communicating.” Unbuffered channels are a synchronous handshake — send blocks until a receiver shows up; buffered channels only block when full or empty. Remember the gotchas: closing then sending panics, only the sender closes, receiving from a closed channel gives the zero value, and a nil channel blocks forever.