Skip to content

How to Detect and Fix Go Goroutine Leaks

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about How to Detect and Fix Go Goroutine Leaks. We cover key concepts, practical examples, and best practices.

Go goroutine leaks occur when goroutines exit without being cleaned up, staying blocked on channel operations, mutex locks, or I/O waits. These leaks cause memory growth and performance degradation over time.

Quick Fix

Wrong

func main() {
    ch := make(chan int)
    go func() {
        <-ch  // blocks forever, no one sends
    }()
    // Goroutine leaks
}

The goroutine waits on a channel that never receives a value. It stays in memory forever.

func main() {
    ch := make(chan int, 1)
    ch <- 42
    go func() {
        val := <-ch
        fmt.Println("Received:", val)
    }()
    time.Sleep(100 * time.Millisecond) // give goroutine time to run
}
Received: 42

Fix with context cancellation

func worker(ctx context.Context, ch <-chan int) {
    for {
        select {
        case val := <-ch:
            fmt.Println("Got:", val)
        case <-ctx.Done():
            fmt.Println("Worker stopping")
            return
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    ch := make(chan int)
    go worker(ctx, ch)
    cancel() // stops the worker
}
Worker stopping

Detect leaks with runtime.NumGoroutine

func main() {
    before := runtime.NumGoroutine()
    // ... run code ...
    after := runtime.NumGoroutine()
    if after > before+1 {
        fmt.Printf("Possible leak: %d goroutines\n", after-before)
    }
}

Prevention

  • Always use context.WithCancel or context.WithTimeout for goroutine lifecycle management.
  • Use buffered channels to prevent sender blocks.
  • Never let a goroutine block on a channel without a select with context or timeout.
  • Monitor with runtime.NumGoroutine() in health checks.
  • Test with -race and pprof goroutine profiling.

DodaTech Tools

Doda Browser's Go profiling dashboard shows goroutine count over time with leak detection alerts. DodaZIP archives goroutine stack dumps for analysis. Durga Antivirus Pro monitors goroutine counts in production and alerts on abnormal growth.

Common Mistakes with goroutine leak detect

  1. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
  2. Non-exhaustive pattern matches that compile with warnings then crash at runtime
  3. Misunderstanding that String is [Char] with poor performance for large text operations

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

How do I find leaking goroutines in production?

Use pprof to capture goroutine profiles: go tool pprof http://localhost:6060/debug/pprof/goroutine. Look for goroutines stuck on channel operations or mutex locks that should have been resolved.

What is the biggest cause of goroutine leaks?

Blocked channel operations are the most common cause. A goroutine waiting on <-ch never proceeds if nothing sends on the channel. Always ensure every send has a corresponding receive and vice versa.

Can I set a timeout on a channel operation?

Use select with time.After: select { case v := <-ch: ...; case <-time.After(5*time.Second): ... }. This prevents indefinite blocking.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro