compare Channel Creation with an alternative
Fast recall
Quick revision
Core answer
Practical understanding of Channel Creation.
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
Every channel you make is bidirectional, but the type system can narrow it:
ch := make(chan int) // bidirectional: chan int
var sendOnly chan<- int = ch // send-only
var recvOnly <-chan int = ch // receive-onlyThe conversion is free and instant — same runtime value, restricted compile-time view. Send-only can send and close but not receive; receive-only can receive but not send or close.
Their real home is function signatures, where the narrowing documents and enforces the contract:
func produce(out chan<- int) { out <- 1 } // this function only sends
func consume(in <-chan int) { <-in } // this function only receivesThe compiler now enforces the design: produce literally cannot receive on out, consume cannot send on in. Executed: a value sent through a sendOnly reference comes out the recvOnly view — one channel, two restricted views.
The convention to internalize: functions take <-chan for inputs (they consume) and return <-chan for outputs (callers only consume what's given); chan<- marks a function's job as feeding a channel. A pipeline built this way reads as data flowing one direction through typed contracts — the direction is in the signature, and the compiler catches any attempt to swim upstream.
compare Channel Creation with an alternative
Direct Answer
chan<- T is send-only, <-chan T is receive-only. Created by conversion from a bidirectional channel — you never make one. They appear in function signatures to narrow what a function can do.
Deep Dive & Architecture
Why does the conversion exist? Because ownership errors are the top concurrency bug class, and signature narrowing is compile-time enforcement of ownership. Without directional types, a consumer that accidentally sends back up the input channel compiles — the bug is a runtime surprise. With them, the misuse is a compile error at the exact function that misunderstood its role.
The mechanics are pure type system: chan int, chan<- int, and <-chan int are distinct types. Bidirectional converts to either direction implicitly; a directional type cannot convert back (once narrowed, the view stays narrow — the original bidirectional variable still exists if the caller kept it). close is allowed on chan<- (the sender owns lifecycle) and forbidden on <-chan (a receiver closing a channel would send the close signal to every other receiver — receivers never own that power).
Standard-library signature-reading, because it calibrates your own: time.After returns <-chan Time (you only consume). time.NewTimer's .C field is <-chan Time (same reason). context.Done() is <-chan struct{} — always receive-only. The ecosystem says: producers expose receive-only views; consumers receive receive-only; the send side stays private. That idiom makes the ownership explicit in every API, and your own pipelines should read the same way.
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 Channel Creation, and when would you use it?
- When would you use Channel Creation in a real project?
- What is a common mistake when using Channel Creation?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.