Close a channel using the built-in close() function, but only when you are certain no more values will be sent and all receivers have finished processing. Closing a channel signals to receivers that the stream is complete, allowing range loops to exit naturally; attempting to close an already closed channel or sending to a closed channel will cause a panic.
Use close() explicitly when the sender is done, typically in a defer statement or after a loop. Receivers should check the second return value of a receive operation to detect closure, or use a range loop which automatically stops when the channel is closed and empty.
// Sender: Close after sending all data
func worker(done chan<- int) {
defer close(done) // Ensures channel closes when function returns
for i := 0; i < 3; i++ {
done <- i
}
}
// Receiver: Detect closure via range or explicit check
func main() {
ch := make(chan int)
go worker(ch)
// Option 1: Range loop (stops automatically when closed and empty)
for val := range ch {
fmt.Println("Received:", val)
}
fmt.Println("Channel closed, loop exited")
// Option 2: Explicit check (useful if you need to handle partial reads)
// val, ok := <-ch
// if !ok { /* channel is closed */ }
}
Never close a channel from the receiver side unless you are implementing a specific pattern where the receiver controls the lifecycle, as this can lead to race conditions if the sender tries to write afterward. Also, avoid closing a channel that is currently being read by multiple goroutines without synchronization, as one goroutine might see the close while another attempts to send. If you need to stop a goroutine listening on a channel, close the channel from the sender; the receiver will detect the closure via the ok flag or by exiting a range loop.
If you are unsure whether a channel is closed, do not call close() again. Instead, rely on the sender to manage the lifecycle. For channels used as signals (e.g., done channels), it is common practice to close them immediately after the operation completes to unblock waiting goroutines.
// Signal pattern: Close to unblock waiters
done := make(chan struct{})
go func() {
// ... do work ...
close(done) // Signal completion
}()
<-done // Blocks until closed
Remember that closing a channel does not delete it; it simply marks it as closed. The channel value remains valid, but sending to it panics, and receiving from it returns the zero value with false for the ok flag. Always ensure the sender is the only entity responsible for closing the channel to maintain safety.