fatal error: all goroutines are asleep - deadlock!

Quick answer

Every goroutine is blocked and none can wake another, so the Go runtime aborts instead of hanging. The usual causes are an unbuffered channel send with no receiver, a sync.WaitGroup whose Done() is never called, a sync.Mutex locked twice, or range over a channel that's never closed. Read the per-goroutine stack — it shows what each one is waiting on. Note: a deadlock is not only a channel problem — Mutex, RWMutex, Cond, and WaitGroup can all deadlock too.

The exact error string

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        /app/main.go:7 +0x37     <-- blocked sending on a channel

How to diagnose it

The fatal dump lists every goroutine and what it's blocked on in brackets: [chan send], [chan receive], [semacquire] (a mutex/WaitGroup). Read each one and ask "who is supposed to unblock this?" — the deadlock is the line where the answer is "nobody." Because Go only fires this when all goroutines are asleep, the dump is complete: the cause is in there.

Cause 1: unbuffered channel, no receiver

ch := make(chan int)
ch <- 1                 // ❌ blocks: no goroutine is receiving
fmt.Println(<-ch)

// ✅ receive in another goroutine
ch := make(chan int)
go func() { ch <- 1 }()
fmt.Println(<-ch)

// ✅ or buffer it if a single value is fine
ch := make(chan int, 1)
ch <- 1
fmt.Println(<-ch)

Cause 2: WaitGroup Done() never called

var wg sync.WaitGroup
wg.Add(2)
go func() { work(); wg.Done() }()  // only one Done() ...
wg.Wait()              // ❌ counter never reaches 0 -> deadlock

// ✅ defer Done() at the top, and Add the right count
wg.Add(2)
for i := 0; i < 2; i++ {
    go func() { defer wg.Done(); work() }()
}
wg.Wait()

Cause 3: range over a channel that's never closed

ch := make(chan int)
go func() {
    for i := 0; i < 3; i++ { ch <- i }
    // ❌ forgot to close(ch) — the range below blocks forever
}()
for v := range ch { fmt.Println(v) }

// ✅ close from the sender when done
go func() {
    defer close(ch)
    for i := 0; i < 3; i++ { ch <- i }
}()

Cause 4: a mutex locked twice (self-deadlock)

sync.Mutex is not reentrant: locking it again on the same goroutine before unlocking blocks forever. The stack shows [semacquire] rather than a channel op.

var mu sync.Mutex
mu.Lock()
mu.Lock()              // ❌ deadlock: already held by this goroutine

// ✅ lock once, and pair every Lock with an Unlock
mu.Lock()
defer mu.Unlock()
// ... critical section ...

// ❌ a common variant: a "locked" method calling another "locked" method
func (c *Cache) Get(k string) int { c.mu.Lock(); defer c.mu.Unlock(); return c.getLocked(k) }
func (c *Cache) getLocked(k string) int { c.mu.Lock(); ... }  // re-locks -> deadlock
// ✅ split into a public locking method and an internal non-locking helper

How Go detects deadlocks

The Go scheduler tracks how many goroutines exist and how many are runnable. Every time a goroutine blocks (on a channel, a sync.Mutex/RWMutex, a Cond, or a WaitGroup), that count drops. When the number of runnable goroutines reaches zero — nothing can make progress and nothing can wake anything — the runtime concludes the program is stuck and aborts with fatal error: all goroutines are asleep - deadlock!. It's a global check, not a per-goroutine one, which is exactly why it only fires when every goroutine is asleep (see below).

When the runtime won't catch it

Go reports this only when every goroutine is blocked. If one is still alive — an HTTP server, a time.Ticker, a background worker — a partial deadlock among the rest just hangs silently instead. For those, run with the race detector (go run -race), use go vet, or send SIGQUIT (Ctrl-\) to dump all goroutine stacks and see who's stuck.

A real-world example: the WaitGroup that never completes

A classic beginner scenario ties several of these together. You fan out work to goroutines, wait on a WaitGroup, but the counter never reaches zero:

func main() {
    var wg sync.WaitGroup
    results := make(chan int)      // unbuffered

    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            results <- n * n        // blocks: nobody is receiving yet
        }(i)
    }

    wg.Wait()                      // ❌ waits for workers that are all
                                   //    blocked sending on `results`
    for r := range results { fmt.Println(r) }
}

The chain is: the workers block sending on the unbuffered results channel → so Done() never runs → so wg.Wait() in main blocks forever → every goroutine is now asleep → deadlock. The fix is to drain the channel concurrently and close it after the group finishes:

go func() { wg.Wait(); close(results) }()  // ✅ waiter runs in the background
for r := range results {                    //    main drains as workers send
    fmt.Println(r)
}

Common causes at a glance

Blocked onCauseFix
[chan send]no receiver for an unbuffered sendreceive in a goroutine / buffer it
[chan receive]no sender, or channel never closedsend/close from the producer
[semacquire]WaitGroup Done() missingdefer wg.Done(); match Add
[semacquire] (Mutex)sync.Mutex locked twice / not unlockedlock once; defer mu.Unlock()
hangs, no fatal errorone goroutine still running-race, go vet, SIGQUIT dump
main exits earlygoroutines never get to runWaitGroup / channel sync

Debugging checklist

Frequently Asked Questions

What does 'all goroutines are asleep - deadlock' mean?

The Go runtime noticed that every goroutine is blocked (on a channel, mutex, or WaitGroup) and none can ever wake another, so the program can make no progress. Rather than hang forever, the runtime aborts with this fatal error. It is detected only when ALL goroutines are stuck.

Why does sending on an unbuffered channel deadlock?

An unbuffered channel send blocks until another goroutine receives. If you send on the main goroutine with no receiver running, main blocks forever and it's the only goroutine — deadlock. Either receive in a separate goroutine, or use a buffered channel (make(chan T, n)) when appropriate.

Why does my WaitGroup deadlock?

wg.Wait() blocks until the counter hits zero. If you Add(n) but a goroutine never calls Done() (an early return, a panic, or a wrong count), the counter never reaches zero and Wait blocks forever. Use defer wg.Done() at the top of each goroutine and make Add match the number you launch.

Why does ranging over a channel deadlock?

for v := range ch keeps receiving until the channel is closed. If no one closes it, the range blocks forever once the senders are done. Close the channel from the sending side (close(ch)) when all values are sent — never from the receiver, and never close twice.

Does the runtime always detect deadlocks?

No. Go only reports this when EVERY goroutine is blocked. If one goroutine is still running (a background ticker, an HTTP server, a busy loop), a partial deadlock among the others won't trigger it — the program just hangs. Use go vet, the race detector, or goroutine dumps (SIGQUIT) to find those.

How do I see which goroutines are stuck?

The fatal error prints a stack for every goroutine and what each is blocked on (chan send, chan receive, semacquire). Read what each is waiting for and confirm nothing can satisfy it. For hangs that aren't full deadlocks, send SIGQUIT (Ctrl-\) to dump all goroutine stacks.

Can a sync.Mutex deadlock, or is it only channels?

It is not only channels. sync.Mutex, sync.RWMutex, sync.Cond, and sync.WaitGroup can all deadlock. A common case is locking a Mutex twice on the same goroutine — it is not reentrant, so the second Lock() blocks forever and shows as [semacquire] in the stack. Lock once and pair every Lock with a defer Unlock.

How does Go detect a deadlock?

The Go scheduler counts how many goroutines are runnable. Each time a goroutine blocks on a channel, mutex, Cond, or WaitGroup, that count falls. When it hits zero — no goroutine can make progress and none can wake another — the runtime aborts with "all goroutines are asleep - deadlock!". Because it is a global check, it only fires when every goroutine is blocked.

More backend & language errors

Browse the full reference for Go, Node.js, Python, and database errors — exact message, cause, and fix.

All Error References Go: nil pointer dereference Postgres: deadlock detected
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.