Must every Go channel be closed, and does an unclosed one leak?
answer
- not like a file handle
- the collector does not care either way
- ask who needs to be told
- a one-shot reply channel is fine unclosed
- an extra close is an extra place to panic
basics
~20 sNo. Closing a Go channel is a signal to receivers, not a resource release, and an unreachable channel is garbage collected whether it was closed or not. Close only when receivers must learn that no more values are coming.
solid answer
~50 s`close` is not `Close` on a file or a socket — no descriptor, no buffer flush, no kernel object. A channel is an ordinary garbage-collected value, and once nothing references it, it is collected along with anything still buffered in it, closed or not. So closing is required only when a receiver's control flow depends on learning that the stream has ended: a `for v := range ch` loop that must terminate, a `v, ok := <-ch` test, or a broadcast wake-up where closing a `done` channel releases every waiter at once. A one-shot reply channel that carries exactly one value to exactly one receiver never needs closing, and closing it out of habit only adds a place where the code can panic on a double close. What does leak in Go is a goroutine parked forever on a channel operation nobody will ever satisfy — but that is a goroutine problem, not memory held by the channel itself.
code
go · 4 linesreply := make(chan int)
go func() { reply <- 42 }()
fmt.Println(<-reply) // prints: 42
// nothing closes reply; once unreachable it is collected like any valuego deeper
Know that close is optional and exists to tell receivers the stream has ended. You are not expected to explain the collector, only to avoid claiming close is mandatory cleanup.
Explain that a channel is an ordinary heap value reclaimed when unreachable, and give the concrete test for needing a close: does some receiver's control flow depend on hearing the end?
Separate the myth from the real leak — blocked goroutines retain their stacks forever, while channels are collected normally — and name the diagnostic you would use to see it.
Decide and document the close contract for every channel a package hands out. Callers build loops on that promise, so it is API surface, not an implementation detail.
## The question behind the question Engineers arriving from languages where every queue, stream or handle has a `close`/`dispose` that must be called — often enforced by a linter, a `try`-with-resources block, or a finalizer warning — reasonably assume Go's `close` is the same obligation. It is not, and the mismatch is worth spelling out because the habit it produces (closing everything, defensively) directly causes the double-close and send-after-close panics that are this area's classic failures. ## What a channel actually is, memory-wise A channel value is a pointer to a runtime structure holding a lock, a ring buffer of element slots (empty for an unbuffered channel), and queues of waiting senders and receivers. It is allocated by `make` on the heap and it is reachable from whatever variables refer to it. There is no operating-system resource behind it — no file descriptor, no socket, no memory mapping — so there is nothing that must be released promptly. When the last reference to a channel goes away, the garbage collector reclaims the channel and every value still sitting in its buffer. Whether `close` was ever called makes no difference to that. `close` sets a flag; it does not free. ## When closing is actually required Close when, and only when, a receiver needs the information. Three shapes need it: 1. **A `for v := range ch` loop.** The loop's only exit condition is the close, so a producer that never closes leaves its consumer parked at the receive. 2. **A `v, ok := <-ch` test.** If the consumer distinguishes "end of stream" from "a value arrived", something has to produce the `false`. 3. **A broadcast.** Closing a `chan struct{}` releases every goroutine blocked on receiving from it, simultaneously and unmissably. This is the only way to signal an unknown number of waiters with one operation; sending a value would reach exactly one of them. ## When closing is unnecessary — and mildly harmful - **A one-shot reply channel.** A caller makes a channel, hands it to one worker, receives one value, and drops it. The receiver already knows how many values to expect, so the close carries no information. Adding it only creates a second thing that must happen exactly once. - **A channel with several senders.** Nobody is in a position to close it safely, so the answer is usually not to close it at all and to signal completion on a different channel instead. - **A long-lived channel that lives as long as the process.** Nothing observes the close; the process exit does the same job. The cost of a habitual, unnecessary close is not memory — it is that `close` is one of only three channel operations that can panic, so every extra close site is an extra place where a shutdown path can crash. ## The leak that is real There is a genuine leak in this neighbourhood, but the channel is not what leaks. A goroutine blocked on a channel operation that will never be satisfied is unreachable to the scheduler's notion of progress yet still alive: its stack, and everything its stack references, stays allocated for the life of the process, and the goroutine count climbs. The memory retained is the goroutine's, not the channel's. So the right mental model is: **channels are collected like any value; goroutines are not collected at all while they are blocked.** ## Answering it in an interview Say three things and you are done. Closing is a signal, not a release. The garbage collector does not care whether a channel was closed. And the test for whether you need one is whether some receiver's control flow depends on hearing that the stream is over.
- Does closing a channel free the values still buffered in it?No. Closing sets a flag; the buffered values stay in the channel and are still delivered to receivers. Both the channel structure and anything left in its buffer are reclaimed only when the channel itself becomes unreachable, exactly like any other heap value.
- You return a channel from a library function. What must the documentation say?Whether you ever close it, and under what condition. Callers write `for v := range ch` loops against that promise, so "the returned channel is closed when the source is exhausted" and "the returned channel is never closed" produce completely different caller code. Silence forces every caller to guess, and it is not a detail you can change later without breaking them.
Closing a channel is like announcing "that was the last item" at an auction, not like locking up the building. The building is tidied away by itself once everyone has left, whether or not the announcement was ever made.
saying these in an interview costs you the question
- Closes every channel out of habit, including one-shot reply channels
- Says an unclosed channel leaks until the process exits
- Treats close as the equivalent of Close on a file or socket
- Has the receiver close the channel to tidy up
- Thinks closing frees the values still buffered in the channel