The small syntax hides a lifecycle.
Anton Zhiyanov's new interactive book walks through goroutines, channels, select, pipelines, wait groups, data races, race conditions, mutexes, and atomic values. Its channel examples make the useful part visible. An unbuffered send waits for a receiver. A nil channel never becomes ready. Writing to a closed channel panics.[1]
The Go specification states the same mechanics in exact terms. A zero-capacity channel communicates only when sender and receiver are both ready. A buffered channel can send while capacity remains and receive while values remain. Channel direction can restrict a function to sending or receiving.[2]
The code may fit on one screen. The lifetime crosses every goroutine that can wait on it.
Put ownership in the function shape.
A function that creates an output channel, starts the sender, and returns a receive-only channel gives reviewers a useful ownership clue. The producer controls sends and closure. The caller consumes. Directional channel types do not prove the implementation is correct, but they remove operations that the caller should not perform.
Close has one specific job. It tells receivers that no more values will arrive. It is not a general cleanup signal. The producer that knows sending is finished should own that transition. Shared or vague closure authority invites duplicate closes and sends after close.
Early return is the sharp corner.
The Go pipeline article shows why a clean happy path is not enough. If a downstream stage stops receiving, an upstream goroutine can remain blocked on send. Goroutines are not garbage-collected. They must return on their own.[3]
A larger buffer can hide one blocked send. It cannot replace a stop contract. The pipeline article calls a buffer chosen around expected unread values fragile because a changed input count or downstream read count can block the system again. Cancellation must reach every stage that may be waiting.
Backpressure is a contract, not a tuning knob.
Buffer capacity changes when producers are allowed to run ahead. That can be correct when the queue has a measured bound and an explicit overload policy. It can also move the stall, increase retained work, or hide a slow consumer until the buffer fills.
Write the meaning beside the number. State what occupies a slot, why the capacity is bounded, what happens when it is full, and which latency or memory measurement justifies it. A bare make(chan Job, 100) leaves the most important review questions in the author's head.
The race detector needs a driven route.
Go's race detector reports conflicting accesses that occur while the instrumented program runs. The documentation is explicit that it cannot find races in paths that were not executed. The recommended starting point is go test -race, followed by a realistic workload when tests do not cover enough behavior.[4]
Keep three receipts separate. A unit test can check the expected value. A cancellation test can prove blocked work exits. A race-enabled run can report conflicting memory access on the path it exercised. None of those receipts replaces the others.