compare Send Receive Operations with an alternative
Fast recall
Quick revision
Core answer
Practical understanding of Send Receive Operations.
Repeating a definition without saying when to use it.
Give one trade-off and say how you would test it.
Interview-ready guide
Interview answer
The type system answers directly:
func consume(in <-chan int) {
close(in) // compile error: cannot close receive-only channel
}The error is at compile time — the direction narrowing isn't just documentation, it's enforcement of the close ownership rule.
Why receivers can't close: close is a broadcast to all receivers that no more values are coming. A receiver closing the channel races every OTHER receiver — one consumer deciding 'we're done' while another is still waiting for a value it was promised would arrive. And it races the senders: the legitimate producer would then send-after-close — a panic — through no fault of its own logic. Close is a production-side fact ('I will never send again'), and only the producer knows it.
The pattern this forces (correctly): a consumer that's finished early doesn't close the input — it just stops reading and lets GC collect the channel when all references drop, or it signals via a SEPARATE cancellation channel (the context.Context pattern — Done() is a <-chan that the producer side closes). Cancellation and completion flow through their own channels, each closed by its owner. That separation — data channels closed by producers, cancellation channels closed by whoever owns the cancellation — is the idiom that keeps pipelines composable.
compare Send Receive Operations with an alternative
Direct Answer
No — close is a send-side operation, and the type system forbids it: compile error on a <-chan. Closing is the sender announcing 'no more values', which a receiver has no authority to do.
Deep Dive & Architecture
The compile enforcement is a type-system story worth one layer down: chan<- T and <-chan T are distinct types, and the close operation is defined only on chan<- T (and the bidirectional chan T). When you convert a bidirectional channel to a receive-only view, the runtime value is unchanged — the same pointer — but the compile-time type no longer HAS the close method. The compiler rejects the call before any runtime question arises.
It's the same narrow-by-conversion trick as the send/receive restrictions, applied to lifecycle: the direction type says what you can DO, and close is an action reserved to the direction that owns the channel's end-of-life. Note the asymmetry with send: a <-chan can't send (compile) and can't close (compile) — the receive-only view is purely a consuming view. The chan<- (send-only) CAN close — the sender owns the end — and that's the signature of the canonical producer:
func produce(out chan<- int) {
defer close(out) // legal on chan<-, idiomatic at the producer
for _, v := range data {
out <- v
}
}
defer close(out) at the top of a producer is the lifecycle idiom — no matter how the function returns, the close fires, every receiver's range terminates, and the pipeline drains cleanly. That one line is the ownership model in miniature: the function that has the send authority exercises the close authority at the same scope.
Common Mistakes & Pitfalls
Repeating a definition without saying when to use it.
Check your understanding
Answer these without looking back at the article.
- What is Send Receive Operations, and when would you use it?
- When would you use Send Receive Operations in a real project?
- What is a common mistake when using Send Receive Operations?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.