Fatal Error All Goroutines Are Asleep Deadlock Explained
Fix fatal error all goroutines are asleep deadlock in Go: the causes with reproductions, how to read the trace, and why partial deadlocks are never reported.

This message ends Go programs quickly and explains itself well once you know how to read it. The runtime keeps count of goroutines that are able to run. When that count drops to zero and nobody is waiting on a timer or the network, the program can never make progress again, and the runtime says so and exits with status 2. It is a mercy: most hangs give no message at all.
Tested with Go 1.25.6, macOS on Apple Silicon (arm64)
This page reproduces the common causes, shows the trace in each case, and then covers the dangerous variant, the deadlock that is never reported. It belongs to the concurrency material and relates to channels, WaitGroups and mutexes. Everything ran on Go 1.25.6, and the traces are real output.
The smallest case
package main
import "fmt"
func main() {
ch := make(chan int)
ch <- 1 // nobody is receiving
fmt.Println("never reached")
}fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
example.test/lab/main.go:7 +0x34
exit status 2An unbuffered send waits for a receiver. There is only one goroutine, and it is the one waiting. The output names the state: goroutine 1 [chan send]: followed by the line of the send. That bracketed text is the most useful part of the trace.
Reading the trace
The runtime prints the fatal error, a blank line, and then every goroutine with its state in brackets. The state says what the goroutine is waiting on:
| State | The goroutine is waiting on |
|---|---|
chan send | a send on a channel that has no receiver ready |
chan receive | a receive on a channel with nothing to deliver |
chan receive (nil chan) / chan send (nil chan) | an operation on a nil channel, which blocks forever |
select / select (no cases) | a select where no case can proceed; select {} has no cases at all |
sync.WaitGroup.Wait | a WaitGroup whose counter never reaches zero |
sync.Mutex.Lock | a mutex held by someone who will never release it |
semacquire | other sync primitives, in older Go versions and some types |
Below the state is the call stack, with the file and line of the blocked operation. Find it in your source and ask the question: what would have to happen for this to proceed, and can that happen? If the answer is "another goroutine would have to send", look for that goroutine in the trace; it is either missing, blocked itself, or has exited.
Cause 1: a send or receive with no partner
The basic case, in its realistic forms. A worker sends results and nobody collects them, or a main loop waits for a result from a goroutine that returned early:
package main
import "fmt"
func main() {
results := make(chan int)
go func() {
results <- 42
}()
fmt.Println(<-results)
fmt.Println(<-results) // a second receive, but only one value was ever sent
}42
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
example.test/lab/main.go:13 +0xc4
exit status 2The first receive succeeds. The second waits for a send that will never come, and the goroutine has exited, so nothing is left to run. The state is chan receive. The fix is to match sends and receives: count how many values are produced and read exactly that many, or close the channel from the sender and range over it.
Cause 2: a WaitGroup that never reaches zero
wg.Wait() blocks until the counter hits zero. If a goroutine returns without Done, or Add was called more times than there are Dones, the wait never ends:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
if i == 1 {
return // forgot wg.Done() on this path
}
defer wg.Done()
fmt.Println("done", i)
}()
}
wg.Wait()
}done 2
done 0
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [sync.WaitGroup.Wait]:
sync.runtime_SemacquireWaitGroup(0x1025b7ff0?, 0x30?)
runtime/sema.go:114 +0x38
sync.(*WaitGroup).Wait(0x14000114020)
sync/waitgroup.go:206 +0xa8
main.main()
example.test/lab/main.go:20 +0xa8
exit status 2One goroutine exits without decrementing the counter, so Wait blocks (the trace shows sync.WaitGroup.Wait) and the runtime reports the deadlock. The fix is to put defer wg.Done() first in the function, before any early return, or to use wg.Go, which pairs the two for you (WaitGroup.Go and the WaitGroup guide). The order in the demo, defer after a conditional return, is a real-world way to write this bug.
Cause 3: ranging over a channel that is never closed
for v := range ch ends only when ch is closed. If the producer finishes and forgets to close, the consumer waits forever:
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
for i := 1; i <= 3; i++ {
ch <- i
}
// missing: close(ch)
}()
for v := range ch {
fmt.Println(v)
}
fmt.Println("done")
}1
2
3
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
example.test/lab/main.go:14 +0xb8
exit status 2The three values print, then the loop waits for a fourth, the producer has exited, and the runtime reports the deadlock. The fix is defer close(ch) in the producer (who closes).
Cause 4: locking a mutex twice
sync.Mutex is not reentrant. A goroutine that locks and then calls something that locks the same mutex waits on itself:
package main
import (
"fmt"
"sync"
)
type Counter struct {
mu sync.Mutex
n int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.n++
}
func (c *Counter) IncTwice() {
c.mu.Lock()
defer c.mu.Unlock()
c.Inc() // locks the same mutex again
c.Inc()
}
func main() {
var c Counter
c.IncTwice()
fmt.Println(c.n)
}fatal error: all goroutines are asleep - deadlock!
goroutine 1 [sync.Mutex.Lock]:
internal/sync.runtime_SemacquireMutex(0x14000110e88?, 0x6c?, 0x100583160?)
runtime/sema.go:95 +0x28
internal/sync.(*Mutex).lockSlow(0x14000108020)
internal/sync/mutex.go:149 +0x170
internal/sync.(*Mutex).Lock(...)
internal/sync/mutex.go:70
sync.(*Mutex).Lock(...)
sync/mutex.go:46
main.(*Counter).Inc(0x14000108020)
example.test/lab/main.go:14 +0x74
main.(*Counter).IncTwice(0x14000108020)
example.test/lab/main.go:22 +0x98
main.main()
example.test/lab/main.go:28 +0x2c
exit status 2IncTwice holds the lock and calls Inc, which tries to take it. The goroutine is blocked on itself, and the runtime reports fatal error: all goroutines are asleep - deadlock! with the state sync.Mutex.Lock. The fix is to split each method into a locking public one and a non-locking internal one (inc), which the public ones call while holding the lock (mutexes shows the pattern).
Cause 5: an empty select, or only a nil channel
select {} blocks forever by design, and in a program where nothing else runs, the runtime treats that as a deadlock. The same happens with a receive from a nil channel. Programs sometimes use select {} at the end of main to wait for background goroutines, which works as long as one of them keeps running and is a trap when they all finish.
The dangerous version: the deadlock that is not reported
The detector fires only when every goroutine is blocked and there is nothing that could wake one. If any goroutine can still run, or if any is waiting on a timer or network event, the runtime cannot prove a deadlock and says nothing:
package main
import (
"fmt"
"os"
"time"
)
func main() {
stuck := make(chan int)
go func() {
<-stuck // blocked forever
}()
go func() {
for { // a goroutine that keeps the program "alive"
time.Sleep(10 * time.Millisecond)
}
}()
go func() {
time.Sleep(100 * time.Millisecond)
fmt.Println("still running, no deadlock was reported; exiting by hand")
os.Exit(0)
}()
<-stuck // main is also stuck
}still running, no deadlock was reported; exiting by handMain and one goroutine are stuck forever, but a timer-driven goroutine is alive, so the runtime does not report anything. Without the os.Exit this program would hang until killed. This is the reality of production services: they always have goroutines running (the HTTP server, tickers, metrics), so a deadlocked subsystem never triggers the fatal error. It shows up as requests that hang, queues that stop draining or a health check that times out.
For those, the tools are different. Send SIGQUIT to dump all goroutine stacks, or fetch /debug/pprof/goroutine?debug=2 from a service that exposes pprof, and look for many goroutines in the same blocked state on the same line (goroutine dumps). The block and mutex profiles show where goroutines wait (mutex and block profiles), and a context deadline on every blocking operation converts a silent hang into an error you can log.
Preventing deadlocks
- Give every blocking operation a way out.
selectwithctx.Done()or a timeout around channel operations. - Close what you range over, from the sender, with
defer. - Pair
AddandDonemechanically:wg.Go, ordefer wg.Done()as the first line. - Keep lock scopes small and never call out to unknown code while holding a lock.
- Take multiple locks in a fixed order, because two goroutines that take A then B and B then A can deadlock each other, and that case is reported only if nothing else is running.
- Run tests with
-raceand a timeout (go test -timeout 30s). The timeout turns a silent hang into a test failure with a dump of all goroutines.
For the runtime's own description of the error, see the runtime package documentation and the Go blog's Go Concurrency Patterns, whose rules about channel ownership prevent most of these.


