Closing Channels
Fast recall
Quick revision
Core answer
Whether you understand Closing Channels beyond memorized syntax.
Giving a definition without explaining a practical trade-off.
Connect the concept to a small production example and state its complexity or failure mode.
Interview-ready guide
Interview answer
close(ch) is a broadcast with exactly one meaning: no more values will ever be sent on this channel.
What it does, in order:
- Buffered values remain receivable. The close is a tombstone placed BEHIND the queue — receivers drain the real values first.
- After the buffer empties, every receive returns immediately with (zero value, ok=false) — forever. A closed channel is permanently 'readable and empty'.
- All goroutines blocked receiving on the channel wake up — with buffered values if available, otherwise with the zero/false result. No receiver waits forever on a closed channel.
- A range loop over the channel terminates when the buffered values are exhausted and close is seen.
- Sends after close panic:
send on closed channel.
Executed demo of the drain-then-end behavior:
ch := make(chan int, 1)
ch <- 10
close(ch)
v, ok := <-ch // (10, true) — buffered value delivered
v2, ok2 := <-ch // (0, false) — channel closed and drainedWhat close does NOT do: it doesn't free the channel (GC does that when no references remain), doesn't interrupt in-flight sends (there are none — close means you promise not to send), and doesn't notify senders of anything (senders are the ones who called it).
The one-sentence ownership law that prevents every close bug: the goroutine that owns sending closes — after its last send, once, with defer — and nobody else.
Closing Channels
Direct Answer
close(ch) — one-time, sender-side. It wakes all receivers with the 'no more values' signal: buffered values drain first, then receives return zero+false. Range loops terminate. Send after close panics.
Deep Dive & Architecture
Mechanically, close(ch) takes the channel lock, sets the closed flag, and then walks both wait queues — every parked receiver gets woken; every one receives either a buffered value or the end-signal. If senders are parked (they were blocked on a full channel), close wakes them too — into the panic path: a sender parked on a full channel that gets closed wakes up to send on closed channel. That's an edge worth knowing: the send-after-close panic isn't only for sends that START after close — a send already blocked when close lands also panics.
The tombstone-behind-the-buffer structure explains the drain order: the close operation doesn't touch the ring buffer at all — values sent before close are in the ring, and receivers take them in FIFO order as normal. The closed flag is only consulted when a receiver finds the buffer empty. So close is literally: mark + wake; the data path continues unchanged until the data runs out.
The range termination mechanic: range over a channel is a receive loop that checks ok — it's the compiler writing the comma-ok loop for you. When it receives the (zero, false), the loop exits. That's why range needs close to terminate: an open, empty channel just blocks the range forever, waiting for a value that may come. Range + close is the canonical pipeline: the producer's defer close(out) is the signal that makes every downstream range end cleanly — one line upstream, correct shutdown everywhere downstream.
Common Mistakes & Pitfalls
Giving a definition without explaining a practical trade-off.
Check your understanding
Answer these without looking back at the article.
- When would you use Closing Channels in a real project?
- How would you compare Closing Channels with an alternative?
- What is a common mistake when using Closing Channels?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.