What happens sending to closed channel

Sending to a closed channel immediately panics your program, terminating the goroutine and potentially crashing the entire application.

Sending to a closed channel immediately panics your program, terminating the goroutine and potentially crashing the entire application. This is a hard runtime error in Go, distinct from receiving from a closed channel, which safely returns the zero value and a false boolean.

To prevent this, always ensure the sender checks if the channel is closed before writing, or rely on a single, well-defined closer pattern where only one goroutine is responsible for closing the channel. If you must handle dynamic scenarios, use a select statement with a default case or a separate "done" channel to coordinate shutdown, rather than blindly sending.

Here is a minimal example demonstrating the panic:

package main

import "fmt"

func main() {
    ch := make(chan int)
    close(ch) // Channel is closed immediately

    // This line will panic: send on closed channel
    ch <- 42 
    fmt.Println("This will never print")
}

If you run this, the output will be a panic message like panic: send on closed channel followed by a stack trace.

A safer pattern involves using a select statement with a default case to attempt a non-blocking send, or ensuring the receiver signals completion before the sender tries to write. However, the most robust approach is architectural: define a clear ownership model where the channel creator or a specific manager goroutine is the only entity allowed to call close().

If you need to gracefully handle a scenario where a channel might be closed unexpectedly by another part of the system, you can wrap the send in a recover block, though this is often a sign of a design flaw. A better alternative is to use a sync.Once to ensure the channel is closed only once, or to use a context to signal cancellation instead of closing the channel for control flow.

package main

import (
    "fmt"
    "sync"
)

func safeSend(ch chan int, val int, closed *bool, mu *sync.Mutex) {
    mu.Lock()
    defer mu.Unlock()
    if *closed {
        fmt.Println("Channel closed, skipping send")
        return
    }
    ch <- val
}

func main() {
    ch := make(chan int)
    closed := false
    var mu sync.Mutex

    go func() {
        close(ch)
        mu.Lock()
        closed = true
        mu.Unlock()
    }()

    // This will skip the send instead of panicking
    safeSend(ch, 10, &closed, &mu)
}

In production code, prefer using context.Context to signal cancellation and stop goroutines, rather than relying on closing channels as a primary control mechanism. This avoids the race condition where a sender tries to write after a receiver has closed the channel.