---
# === IDENTITY ===
id: software/debugging/go-goroutine-leak/2026
canonical_question: "How do I detect and fix goroutine leaks in Go?"
aliases:
  - "Go goroutine leak"
  - "goroutine leak detection Go"
  - "goleak goroutine testing"
  - "Go pprof goroutine debug"
  - "blocked goroutine Go"
  - "goroutine not exiting Go"
  - "context cancellation goroutine leak"
  - "Go channel leak goroutine"
  - "runtime.NumGoroutine leak"
  - "goroutine leak prevention Go"
entity_type: software_reference
domain: software > debugging > go_goroutine_leak
region: global
jurisdiction: global
temporal_scope: 2012-2026

# === VERIFICATION ===
last_verified: 2026-05-17
confidence: 0.93
version: 1.1
first_published: 2026-02-20

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Go 1.26 goroutineleak pprof profile, experimental (2026-02)"
  next_review: 2026-11-13
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "Go-only — does not apply to Java thread leaks, Python asyncio task leaks, or Rust async task leaks; each runtime has fundamentally different concurrency primitives"
  - "goleak (VerifyNone / VerifyTestMain) only detects leaked goroutines at test cleanup time — it is not a production runtime detector and cannot be used for live monitoring"
  - "pprof /debug/pprof/goroutine shows all goroutines (including runtime-internal ones) grouped by stack — it shows count and state, but does not identify which specific goroutines are 'leaked' vs intentional"
  - "context.WithCancel / WithTimeout must always be followed by defer cancel() immediately — forgetting cancel() is itself a goroutine/timer leak source, not a fix"
  - "The goroutineleak pprof profile (Go 1.26, released 2026-02, opt-in via GOEXPERIMENT=goroutineleakprofile) uses GC reachability — it cannot detect goroutines blocked on globally-reachable channels or sync primitives, only unreachable ones. Planned default-on in Go 1.27"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Go program crashes with 'nil pointer dereference' panic (no goroutine count growth)"
    use_instead: "software/debugging/go-nil-pointer/2026 — nil pointer panics are crashes, not leaks"
  - condition: "Java application throws OutOfMemoryError or thread count grows unbounded"
    use_instead: "software/debugging/java-outofmemoryerror/2026 — JVM thread/memory management differs entirely from Go goroutines"
  - condition: "Go program memory usage grows but runtime.NumGoroutine() remains stable"
    use_instead: "Use pprof heap profile (go tool pprof /debug/pprof/heap) — this is a memory leak without goroutine involvement"
  - condition: "Python asyncio tasks are not completing or accumulating"
    use_instead: "Python asyncio task leak debugging — different runtime, event loop model, and tooling (asyncio.all_tasks(), TaskGroup)"

# === AGENT HINTS ===
inputs_needed:
  - key: "go_version"
    question: "Which Go version is the project using?"
    type: choice
    options: ["1.21 or older", "1.22-1.23", "1.24", "1.25", "1.26+"]
  - key: "environment"
    question: "Where is the leak occurring?"
    type: choice
    options: ["unit tests", "integration tests", "production", "development"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/debugging/go-goroutine-leak/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-05-17)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/debugging/go-nil-pointer/2026"
      label: "Go Nil Pointer Dereference"
    - id: "software/debugging/nodejs-memory-leaks/2026"
      label: "Node.js Memory Leaks"
    - id: "software/debugging/python-memory-leaks/2026"
      label: "Python Memory Leaks"
  often_confused_with:
    - id: "software/debugging/go-nil-pointer/2026"
      label: "Go Nil Pointer Dereference (crash, not leak)"
    - id: "software/debugging/java-outofmemoryerror/2026"
      label: "Java OutOfMemoryError (JVM thread/heap exhaustion, not Go goroutines)"

# === SOURCES ===
sources:
  - id: src1
    title: "Goroutine Leaks — The Forgotten Sender"
    author: Ardan Labs (William Kennedy)
    url: https://www.ardanlabs.com/blog/2018/11/goroutine-leaks-the-forgotten-sender.html
    type: technical_blog
    published: 2018-11-26
    reliability: high
  - id: src2
    title: "uber-go/goleak — Goroutine Leak Detector"
    author: Uber Technologies
    url: https://github.com/uber-go/goleak
    type: community_resource
    published: 2018-07-01
    reliability: high
  - id: src3
    title: "Detecting Goroutine Leaks with synctest/pprof"
    author: Anton Zhiyanov
    url: https://antonz.org/detecting-goroutine-leaks/
    type: technical_blog
    published: 2025-06-15
    reliability: high
  - id: src4
    title: "Early Return and Goroutine Leak"
    author: Redowan Delowar
    url: https://rednafi.com/go/early-return-and-goroutine-leak/
    type: technical_blog
    published: 2024-03-01
    reliability: high
  - id: src5
    title: "goleak package — go.uber.org/goleak"
    author: Uber Technologies
    url: https://pkg.go.dev/go.uber.org/goleak
    type: official_docs
    published: 2024-01-01
    reliability: high
  - id: src6
    title: "Debugging Go Routine Leaks"
    author: MinIO Engineering
    url: https://blog.min.io/debugging-go-routine-leaks/
    type: technical_blog
    published: 2023-08-01
    reliability: high
  - id: src7
    title: "How to Find and Fix Goroutine Leaks in Go"
    author: HackerNoon
    url: https://hackernoon.com/how-to-find-and-fix-goroutine-leaks-in-go
    type: technical_blog
    published: 2024-09-01
    reliability: moderate_high
  - id: src8
    title: "proposal: runtime/pprof — new goroutine leak profile (Issue #74609)"
    author: Go Team
    url: https://github.com/golang/go/issues/74609
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
---

# How Do I Detect and Fix Goroutine Leaks in Go?

## TL;DR

- **Bottom line**: A goroutine leak occurs when a goroutine is started but never terminates, usually because it is blocked on a channel send/receive, waiting on a lock, or stuck in a loop with no exit path. Detect with `runtime.NumGoroutine()` trending, `net/http/pprof` goroutine dumps, or Uber's `goleak` in tests. Fix by ensuring every goroutine has a guaranteed exit path via `context.Context` cancellation, buffered channels, or `errgroup`.
- **Key tool/command**: `go.uber.org/goleak` with `defer goleak.VerifyNone(t)` in tests; `http://localhost:6060/debug/pprof/goroutine?debug=2` in production.
- **Watch out for**: Unbuffered channels where the sender blocks forever when the receiver returns early (the "forgotten sender" pattern). This is the #1 cause of goroutine leaks.
- **Works with**: Go 1.13+. `goleak` v1.3.0 (Oct 2024) supports the two most recent Go minor versions. `synctest` experimental in Go 1.24, production-ready in Go 1.25+. Experimental `goroutineleak` pprof profile in Go 1.26 (released Feb 2026, opt-in via `GOEXPERIMENT=goroutineleakprofile`), planned default-on in Go 1.27.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- The Go runtime does NOT forcibly terminate goroutines. Once started, a goroutine runs until it returns or the program exits. There is no `goroutine.Kill()`.
- `goleak.VerifyNone(t)` is incompatible with `t.Parallel()`. Use `goleak.VerifyTestMain(m)` for parallel test packages. [src2, src5]
- `runtime.NumGoroutine()` includes runtime-internal goroutines (GC, finalizer, signal handler). Raw count comparisons can produce false positives. Use `goleak` or pprof for accurate detection. [src6]
- Never rely solely on `go vet` or the race detector to find goroutine leaks — neither detects them. [src4]
- The `synctest` package (Go 1.24+) only detects goroutines blocked on synchronization primitives. Goroutines stuck in CPU-bound infinite loops are not detected. [src3]

## Quick Reference

| # | Cause | Likelihood | Signature | Fix |
|---|---|---|---|---|
| 1 | Unbuffered channel — forgotten sender | ~35% of cases | Goroutine blocked on `ch <-` with no receiver | Use buffered channel `make(chan T, 1)` or drain channel before return [src1] |
| 2 | Unbuffered channel — forgotten receiver | ~20% of cases | Goroutine blocked on `<-ch` with no sender/close | Close channel when sender is done: `defer close(ch)` [src1] |
| 3 | Missing context cancellation | ~20% of cases | Goroutine in `select{}` without `ctx.Done()` case | Always pass and check `context.Context` [src7] |
| 4 | Early return skipping channel reads | ~10% of cases | Multiple goroutines spawned, only first result consumed | Use `errgroup.Group` or drain all channels [src4] |
| 5 | Range over unclosed channel | ~5% of cases | `for v := range ch` blocks forever | Sender must `close(ch)` when done [src3] |
| 6 | Infinite loop without exit condition | ~5% of cases | Goroutine in `for{}` or `for { select{} }` with no `return` | Add `ctx.Done()` or done channel check [src7] |
| 7 | Leaked `time.Ticker` goroutine | ~3% of cases | `time.NewTicker` without `defer ticker.Stop()` | Always `defer ticker.Stop()` [src7] |
| 8 | Orphaned background worker | ~2% of cases | Library starts goroutine; caller forgets `Stop()`/`Close()` | Follow library contract — call cleanup methods [src3] |

## Decision Tree

```
START — suspected goroutine leak
├── In tests?
│   ├── YES → Add `defer goleak.VerifyNone(t)` [src2]
│   │   ├── Using t.Parallel()? → Use `goleak.VerifyTestMain(m)` instead [src5]
│   │   └── Go 1.24+? → Consider synctest.Test() for built-in detection [src3]
│   └── NO ↓
├── In production/development?
│   ├── Can add pprof endpoint?
│   │   ├── YES → import _ "net/http/pprof"; visit /debug/pprof/goroutine?debug=2 [src6]
│   │   └── NO → Log runtime.NumGoroutine() periodically; alert on growth [src7]
│   └── Have Prometheus?
│       └── YES → Export go_goroutines metric; alert on sustained increase [src7]
│
├── Leak confirmed — what is blocked?
│   ├── Channel send (`ch <-`) → Receiver missing or returned early
│   │   ├── Can buffer channel? → make(chan T, 1) [src1]
│   │   └── Multiple senders? → Use errgroup or drain all channels [src4]
│   ├── Channel receive (`<-ch`) → Sender never sends or closes
│   │   └── Ensure sender calls close(ch) or sends before exiting [src1]
│   ├── select{} without ctx.Done() → Add context cancellation case [src7]
│   ├── Mutex/lock contention → Check for deadlock (separate issue)
│   └── I/O operation (net.Conn, http.Client) → Add timeout via context or Deadline [src7]
│
└── DEFAULT → Use pprof goroutine dump to identify blocked stack trace [src6]
```

## Step-by-Step Guide

### 1. Add goleak to your test suite

The fastest way to catch goroutine leaks is in tests. [src2, src5]

```bash
go get -u go.uber.org/goleak
```

```go
package mypackage

import (
    "testing"
    "go.uber.org/goleak"
)

func TestNoLeak(t *testing.T) {
    defer goleak.VerifyNone(t)
    // Your test code here.
    // goleak checks for unexpected goroutines when the test exits.
}
```

**Verify**: Run `go test -v ./...` — if a goroutine leaks, goleak prints the leaked goroutine's stack trace and fails the test.

### 2. Use goleak.VerifyTestMain for package-wide detection

For parallel tests or package-wide coverage. [src2, src5]

```go
package mypackage

import (
    "testing"
    "go.uber.org/goleak"
)

func TestMain(m *testing.M) {
    goleak.VerifyTestMain(m)
}
```

**Verify**: `go test -v ./...` — all tests in the package are checked for leaks after the entire suite finishes.

### 3. Enable pprof for runtime detection

For production or development environments. [src6, src7]

```go
import (
    "net/http"
    _ "net/http/pprof" // registers /debug/pprof/* handlers
)

func main() {
    // In production, bind to a separate internal port
    go func() {
        http.ListenAndServe("localhost:6060", nil)
    }()
    // ... rest of application
}
```

**Verify**: `curl http://localhost:6060/debug/pprof/goroutine?debug=2` — shows full stack traces of all goroutines. Look for goroutines blocked on channel operations or select statements.

### 4. Monitor runtime.NumGoroutine() over time

For quick sanity checks and alerting. [src6, src7]

```go
import (
    "log"
    "runtime"
    "time"
)

func monitorGoroutines(interval time.Duration) {
    ticker := time.NewTicker(interval)
    defer ticker.Stop()
    baseline := runtime.NumGoroutine()
    for range ticker.C {
        current := runtime.NumGoroutine()
        if current > baseline*2 {
            log.Printf("WARNING: goroutine count %d exceeds 2x baseline %d", current, baseline)
        }
    }
}
```

**Verify**: Run application under load, then let it idle. Goroutine count should return to baseline. If it keeps climbing, you have a leak.

### 5. Use synctest for built-in detection (Go 1.24+, production-ready in 1.25+)

No third-party dependencies needed. [src3]

```go
package mypackage

import (
    "testing"
    "testing/synctest"
)

func TestNoLeakSynctest(t *testing.T) {
    synctest.Test(t, func(t *testing.T) {
        // Your test code with goroutines
        synctest.Wait() // waits for all goroutines in the bubble
    })
    // Panics if blocked goroutines remain after test function exits
}
```

**Verify**: `GOEXPERIMENT=synctest go test -v ./...` (Go 1.24 only). In Go 1.25+, no experiment flag needed — `synctest` is production-ready.

### 6. Analyze pprof goroutine dump to find the leak

Once you know goroutines are leaking, identify the specific blocked stacks. [src6]

```bash
# Get goroutine dump (debug=2 shows full stack traces)
curl -s http://localhost:6060/debug/pprof/goroutine?debug=2 > goroutines.txt

# Look for the most common blocked stacks
curl -s http://localhost:6060/debug/pprof/goroutine?debug=1 | head -50

# Use go tool pprof for interactive analysis
go tool pprof http://localhost:6060/debug/pprof/goroutine
# Then: top10, list funcName, web (for graph)
```

**Verify**: Blocked goroutines will show stack traces ending in `chan send`, `chan receive`, `select`, or `sync.Mutex.Lock`. The function names tell you which code path is leaking.

### 7. Enable the Go 1.26 goroutineleak pprof profile (long-running services)

For long-running services where `goleak` cannot help, Go 1.26 (released Feb 2026) ships an experimental GC-based profile that flags goroutines blocked on concurrency primitives the GC has marked unreachable. [src3, src8]

```bash
# Build with the experiment enabled
GOEXPERIMENT=goroutineleakprofile go build -o myapp ./cmd/myapp

# Then expose net/http/pprof as usual and curl the new endpoint
curl -s http://localhost:6060/debug/pprof/goroutineleak > leak.pb.gz
go tool pprof leak.pb.gz
# Commands: top10, list funcName, web
```

**Verify**: Only orphaned leaks (primitives unreachable from any runnable goroutine) are reported. Logically-stuck goroutines parked on a globally-reachable channel will NOT appear — pattern-based review and `goleak` are still required. Production-ready per the proposal; planned default-on in Go 1.27.

## Code Examples

### Go: Fix forgotten sender with buffered channel

> Full script: [go-fix-forgotten-sender-with-buffered-channel.go](scripts/go-fix-forgotten-sender-with-buffered-channel.go) (31 lines)

```go
// Input:  Function that spawns a goroutine to do work with a timeout
// Output: Result or timeout error — no goroutine leak
package main
import (
    "context"
# ... (see full script)
```

### Go: Fix early return with errgroup

> Full script: [go-fix-early-return-with-errgroup.go](scripts/go-fix-early-return-with-errgroup.go) (30 lines)

```go
// Input:  Multiple concurrent tasks where any failure should cancel the rest
// Output: First error encountered, all goroutines cleaned up
package main
import (
    "context"
# ... (see full script)
```

### Go: Worker pool pattern preventing unbounded goroutines

> Full script: [go-worker-pool-pattern-preventing-unbounded-gorout.go](scripts/go-worker-pool-pattern-preventing-unbounded-gorout.go) (35 lines)

```go
// Input:  Stream of jobs
// Output: Processed results with bounded goroutine count
package main
import (
    "context"
# ... (see full script)
```

## Anti-Patterns

### Wrong: Unbuffered channel with early return

```go
// BAD — goroutine writing to ch2 leaks when ch1 returns an error [src4]
func fetchTwo() error {
    ch1 := make(chan error)
    ch2 := make(chan error)

    go func() { ch1 <- doWork1() }()
    go func() { ch2 <- doWork2() }()

    if err := <-ch1; err != nil {
        return err // ch2 sender blocks forever — LEAKED
    }
    return <-ch2
}
```

### Correct: Use errgroup to coordinate goroutines

```go
// GOOD — errgroup waits for all goroutines; no leak possible [src4]
func fetchTwo() error {
    var g errgroup.Group
    g.Go(func() error { return doWork1() })
    g.Go(func() error { return doWork2() })
    return g.Wait() // waits for both; returns first error
}
```

### Wrong: Goroutine blocked on unbuffered channel after timeout

```go
// BAD — if timeout fires first, the goroutine sending to ch blocks forever [src1]
func fetchWithTimeout() (string, error) {
    ch := make(chan string) // unbuffered!
    go func() {
        result := slowOperation()
        ch <- result // blocks forever if main returned via timeout
    }()

    select {
    case r := <-ch:
        return r, nil
    case <-time.After(1 * time.Second):
        return "", fmt.Errorf("timeout")
        // goroutine is now LEAKED — blocked on ch <- result
    }
}
```

### Correct: Buffer the channel so sender can always complete

```go
// GOOD — buffered channel lets goroutine complete even if nobody reads [src1]
func fetchWithTimeout() (string, error) {
    ch := make(chan string, 1) // buffered!
    go func() {
        result := slowOperation()
        ch <- result // always succeeds — buffer absorbs the value
    }()

    select {
    case r := <-ch:
        return r, nil
    case <-time.After(1 * time.Second):
        return "", fmt.Errorf("timeout")
        // goroutine completes its send into the buffer and exits cleanly
    }
}
```

### Wrong: Range over channel with no close

```go
// BAD — consumer goroutine blocks forever because nobody closes ch [src3]
func process(items []int) <-chan int {
    ch := make(chan int)
    go func() {
        for _, item := range items {
            ch <- item * 2
        }
        // BUG: forgot to close(ch)
    }()
    return ch
}

// Consumer: for result := range process(items) { ... } // blocks forever after last item
```

### Correct: Always close the channel when the sender is done

```go
// GOOD — close(ch) signals the consumer to exit the range loop [src3]
func process(items []int) <-chan int {
    ch := make(chan int)
    go func() {
        defer close(ch) // always close when done sending
        for _, item := range items {
            ch <- item * 2
        }
    }()
    return ch
}

// Consumer: for result := range process(items) { ... } // exits cleanly after close
```

## Common Pitfalls

- **Forgetting to cancel derived contexts**: Creating `ctx, cancel := context.WithCancel(parentCtx)` and not calling `defer cancel()` leaks the goroutine watching the context timer. Always `defer cancel()` immediately after creation. [src7]
- **Using `time.After` in a loop**: Each call to `time.After` in a `select` inside a loop creates a new timer goroutine that isn't collected until it fires. Use `time.NewTimer` and `timer.Reset()` instead. [src7]
- **Ignoring `goleak` false positives from third-party libraries**: Some libraries (gRPC, database drivers) start background goroutines. Use `goleak.IgnoreTopFunction("google.golang.org/grpc...")` to whitelist known safe goroutines. [src2, src5]
- **`select{}` without a done channel**: An empty `select{}` blocks the goroutine forever. Always include a `case <-ctx.Done(): return` branch. [src7]
- **Not draining channels on context cancellation**: When a producer goroutine checks `ctx.Done()` but the consumer has already returned, the producer's final send still blocks. Use buffered channels or a separate drain goroutine. [src1, src4]
- **Testing only the happy path**: Goroutine leaks often occur in error paths and timeout paths. Test these paths explicitly with `goleak.VerifyNone(t)`. [src2]

## Diagnostic Commands

```bash
# === Runtime goroutine count ===
# Add to your Go code:
# fmt.Println("Goroutines:", runtime.NumGoroutine())

# === pprof goroutine dump (full stack traces) ===
curl -s http://localhost:6060/debug/pprof/goroutine?debug=2

# === pprof goroutine summary (grouped by stack) ===
curl -s http://localhost:6060/debug/pprof/goroutine?debug=1

# === Interactive pprof analysis ===
go tool pprof http://localhost:6060/debug/pprof/goroutine
# Commands: top10, list funcName, web, traces

# === Download goroutine profile for offline analysis ===
curl -o goroutine.pb.gz http://localhost:6060/debug/pprof/goroutine
go tool pprof goroutine.pb.gz

# === Compare goroutine profiles (before/after load test) ===
go tool pprof -base goroutine_before.pb.gz goroutine_after.pb.gz

# === Run tests with goleak ===
go test -v -run TestMyFunc ./...

# === Check goroutine count in tests ===
go test -v -count=1 ./... 2>&1 | grep -i "goroutine\|leak"

# === Go 1.24 synctest (1.25+ no flag needed) ===
GOEXPERIMENT=synctest go test -v ./...

# === Go 1.26 goroutineleak profile (build-time opt-in) ===
GOEXPERIMENT=goroutineleakprofile go build -o myapp ./cmd/myapp
curl -s http://localhost:6060/debug/pprof/goroutineleak > leak.pb.gz
go tool pprof leak.pb.gz
```

## Version History & Compatibility

| Feature / Tool | Available Since | Notes |
|---|---|---|
| `runtime.NumGoroutine()` | Go 1.0 | Built-in; includes runtime goroutines [src7] |
| `net/http/pprof` goroutine profile | Go 1.0 | `debug=1` (summary), `debug=2` (full stacks) [src6] |
| `go.uber.org/goleak` v1.0 | 2018 | `VerifyNone`, `VerifyTestMain` [src2] |
| `goleak` v1.1.0 (`IgnoreCurrent`) | 2021 | Filter pre-existing goroutines [src5] |
| `goleak` v1.3.0 (`IgnoreAnyFunction`) | 2024 | Match function anywhere in stack [src5] |
| `context.WithCancel` / `WithTimeout` | Go 1.7 | Standard cancellation pattern [src7] |
| `golang.org/x/sync/errgroup` | Go 1.7 (module) | Coordinated goroutine groups [src4] |
| `testing/synctest` package | Go 1.24 (2025-02, experimental); Go 1.25 (production-ready) | Built-in leak detection in tests; no flag needed in 1.25+ [src3] |
| `runtime/pprof` goroutineleak profile | Go 1.26 (2026-02, experimental) | GC-based leak detection, opt-in via `GOEXPERIMENT=goroutineleakprofile`; planned default-on in Go 1.27 [src3, src8] |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Goroutine count grows during idle | Memory usage grows but goroutine count stable | Memory profiler (`pprof heap`) |
| Test occasionally hangs or times out | Test fails deterministically with stack trace | Standard debugging / `go vet` |
| `pprof/goroutine` shows blocked stacks | All goroutines are active (CPU-bound) | CPU profiler (`pprof profile`) |
| Channel operations block indefinitely | Mutex deadlock (all goroutines blocked) | Deadlock detector (`go vet`, `-race`) |
| Need CI-integrated leak detection | Production monitoring only | Prometheus `go_goroutines` metric |

## Important Caveats

- **`goleak` uses polling with exponential backoff**: It checks up to 20 times with delays from 1us to 100ms. Very fast goroutines that start and stop between checks may not be caught. [src2]
- **`synctest` is still evolving**: The API changed between Go 1.24 and 1.25. Pin your Go version and check release notes before upgrading. [src3]
- **The `goroutineleak` pprof profile (Go 1.26, released Feb 2026, experimental) uses GC marking**: It cannot detect goroutines blocked on reachable synchronization objects (e.g., a global channel). It only catches goroutines blocked on unreachable objects. Enable at build time with `GOEXPERIMENT=goroutineleakprofile`; profile is exposed at `/debug/pprof/goroutineleak`. Planned default-on in Go 1.27. Adds zero runtime overhead unless actively in use. [src3, src8]
- **`runtime.NumGoroutine()` baseline varies**: HTTP servers, database connection pools, and gRPC clients all maintain background goroutines. Establish a per-application baseline before alerting. [src6]
- **Goroutine leaks compound**: Each leaked goroutine holds its entire stack (2-8 KB initial, can grow to 1 GB). In high-throughput services, 50,000 leaked goroutines can consume hundreds of MB of RAM and thousands of file descriptors. [src6]

## Related Units

- [Go Nil Pointer Dereference](/software/debugging/go-nil-pointer/2026) — often_confused_with: crash, not leak
- [Node.js Memory Leaks](/software/debugging/nodejs-memory-leaks/2026)
- [Python Memory Leaks](/software/debugging/python-memory-leaks/2026)
- [Java OutOfMemoryError](/software/debugging/java-outofmemoryerror/2026) — often_confused_with: JVM thread/heap exhaustion, not Go goroutines
