Skip to content

How to Fix Go Nil Channel Send and Receive Deadlocks

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about How to Fix Go Nil Channel Send and Receive Deadlocks. We cover key concepts, practical examples, and best practices.

Go programs deadlock when sending to or receiving from a nil channel. A nil channel blocks forever on send or receive, unlike a closed channel which returns the zero value. This often happens when a channel is declared as a struct field but not initialized.

Quick Fix

Wrong

var ch chan int
go func() {
    ch <- 42
}()
<-ch
fatal error: all goroutines are asleep - deadlock!

The nil channel blocks both the sender and receiver forever.

ch := make(chan int)
go func() {
    ch <- 42
}()
result := <-ch
fmt.Println(result)
42

Fix when channel is a struct field

type Worker struct {
    done chan struct{}
}

// Wrong:
w := Worker{}
<-w.done  // blocks forever on nil channel

// Right:
w := Worker{done: make(chan struct{})}
close(w.done)  // or send on it

Fix for bidirectional nil channels

func process(done chan struct{}) {
    // Wrong: if done is nil, this blocks forever
    <-done

    // Right: check for nil
    if done != nil {
        <-done
    }
}

process(nil) // no longer deadlocks

Prevention

  • Always initialize channels with make(chan T) before sending or receiving.
  • Use select with default case to make channel operations non-blocking.
  • Check for nil before using channels from external callers.
  • Use buffered channels when the sender should not block on the receiver.
  • Close channels from the sender side when no more data is expected.

DodaTech Tools

Doda Browser's Go concurrency visualizer detects nil channel usage in channel flow graphs. DodaZIP archives goroutine dump data for post-mortem analysis. Durga Antivirus Pro identifies blocked goroutines from nil channel sends.

Common Mistakes with nil channel send

  1. Forgetting deriving (Show, Eq) on custom data types needed for debugging
  2. Placing the wildcard pattern first in case expressions, making all subsequent patterns unreachable
  3. Using head and tail instead of pattern matching, causing runtime errors on empty lists

These mistakes appear frequently in real-world GO code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

What is the difference between a nil channel and a closed channel?

Sending on a closed channel panics. Receiving from a closed channel returns the zero value immediately. A nil channel blocks indefinitely on both send and receive, making it useful in select statements to disable cases.

How do I use nil channels intentionally in select?

Set a channel variable to nil to disable a case in a select statement: ch = nil makes that case ignored until the channel is reassigned. This is commonly used in fan-in and fan-out patterns.

Can I reuse a channel after closing it?

No, once a channel is closed, sending on it causes a panic. Create a new channel if you need to resume communication. A closed channel can still be received from until all buffered values are drained.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro