Ad Space — Top Banner

panic: send on closed channel

Go Programming Language

Severity: Critical

What Does This Error Mean?

This panic means a goroutine tried to send data to a channel that has already been closed. In Go, once a channel is closed, you can no longer send to it — only receive remaining values. Sending to a closed channel causes an immediate panic. The fix is to ensure only one goroutine is responsible for closing a channel and only after all sends are complete.

Affected Models

  • Go 1.0 and later
  • All Go versions

Common Causes

  • Closing a channel and then trying to send more values to it
  • Multiple goroutines closing the same channel — the second close also panics
  • A goroutine sends to a channel after another goroutine has already closed it (race condition)
  • Closing a channel in a defer while the function might still be sending to it
  • Not synchronizing channel close operations between goroutines

How to Fix It

  1. Follow the rule: only the goroutine that sends to a channel should close it — and only after it is done sending.

    Never close a channel from the receiver side. The receiver has no way to know if the sender is done. The sender always knows when it has sent everything.

  2. When multiple goroutines send to a single channel, use sync.WaitGroup to wait for all senders to finish, then close the channel in a coordinator goroutine.

    go func() { wg.Wait(); close(ch) }() — this closes the channel only after all senders have called wg.Done()

  3. Never close the same channel twice. Use a sync.Once to guarantee a channel is only closed one time.

    var once sync.Once; closeChannel := func() { once.Do(func() { close(ch) }) } — calling closeChannel() multiple times is now safe.

  4. Use the context package for cancellation signals instead of closing channels for signaling. context.CancelFunc is designed for this pattern.

    Create a cancelable context: ctx, cancel := context.WithCancel(context.Background()) — goroutines check ctx.Done() and cancel() can be called multiple times safely.

  5. Add logging or use the Go race detector (go run -race yourfile.go) to find race conditions where channels are closed before all sends complete.

    The race detector is a built-in Go tool that detects concurrent access issues at runtime. Run tests with -race flag: go test -race ./...

When to Call a Professional

Channel panics in Go are serious concurrency bugs. The general rule: only the sender should close a channel, and only after all sends are complete. Use sync.WaitGroup and careful design to coordinate channel lifetimes.

Frequently Asked Questions

What is a channel in Go?

A channel is a typed pipe that goroutines use to communicate with each other. One goroutine sends values into the channel; another goroutine receives them. Channels make concurrent code safe because they transfer data between goroutines without shared memory. Create a channel: ch := make(chan int). Send: ch <- 42. Receive: value := <-ch.

Why does reading from a closed channel not panic but writing does?

By design — closed channels can still have buffered values that need to be received. Reading from a closed channel returns remaining buffered values, then returns zero values with ok=false when empty. This is how Go signals 'no more data' to receivers. Writing to a closed channel makes no sense — the channel is done, so Go panics to catch this programming error.

How do I check if a channel is closed?

Use the two-value receive form: value, ok := <-ch — if ok is false, the channel is closed and no more values will come. There is no way to check if a channel is closed without receiving from it. This is intentional — the receiver finds out the channel is done when it drains the last value. The for range loop over a channel automatically handles this: for v := range ch { ... } stops when ch is closed.