What does Go's goroutineleak profile report that the ordinary goroutine profile cannot?
answer
- the ordinary dump makes you judge
- this one reports only what it can prove
- proof comes from reachability, like garbage
- a reachable channel defeats the proof
- no hits is not a clean bill of health
basics
~20 sThe ordinary goroutine profile lists every blocked goroutine and leaves the judgment to you. The goroutineleak profile reports only goroutines the runtime can prove will never resume, so a hit is proof — but an empty result is not.
solid answer
~50 sThe ordinary `/debug/pprof/goroutine` profile is a census: it shows every goroutine and where it is parked, and deciding which of them is leaked is your call, made from counts, growth across snapshots and whether the parked call site can still be woken. The `goroutineleak` profile, served at `/debug/pprof/goroutineleak`, narrows that to goroutines the runtime can **prove** are unresumable — ones blocked on a synchronisation primitive that no live goroutine can still reach, so nothing exists that could ever signal them. A hit is therefore evidence rather than a hypothesis. The limit is the flip side of the proof: a goroutine blocked on a channel a live map still holds, or on a socket read with no deadline, cannot be proven dead and will not appear. It complements the ordinary dump; it does not retire it.
code
text · 5 lines# only goroutines the runtime can prove will never resume
curl -s 'http://localhost:6060/debug/pprof/goroutineleak?debug=1' > leaks.txt
# every goroutine and where it is parked; you classify them
curl -s 'http://localhost:6060/debug/pprof/goroutine?debug=2' > all.txtgo deeper
Know that recent Go added a second, stricter profile aimed specifically at leaked goroutines, and that it is a different endpoint from the ordinary goroutine profile. Recognising the name is enough at this level.
Explain the difference between a profile that lists what is blocked and one that lists what can be proven unable to resume, and why the second needs reachability information from the runtime.
Name a real leak it will miss and say why. An interviewer wants to see that you would not retire the ordinary goroutine dump, or close an incident, on the strength of an empty result here.
Decide how much of the team's leak detection may rest on a profile that exists only in recent Go, and whether that is worth setting a toolchain floor across services other teams build and you get paged for.
## The judgment the ordinary profile leaves you `/debug/pprof/goroutine` is a complete census of goroutines and their parked call sites. It is the right instrument for a leak, but it does not identify one — it hands you a population and asks you to classify it. A goroutine parked for six hours on `chan receive` might be a leak, or it might be an idle worker waiting for work that has not arrived. Distinguishing them is inference: you take two snapshots and see which stack grows, you look at where the count concentrates, and you reason about whether the event that would wake this goroutine can still occur. Experienced engineers do this well and still get it wrong on unfamiliar code. ## What the leak profile does instead The `goroutineleak` profile answers a strictly narrower and much stronger question: **which goroutines can the runtime prove will never run again?** The idea borrows the garbage collector's central insight. The collector calls an object garbage when no live path can reach it. A blocked goroutine is in an analogous position: it is waiting on some synchronisation object — a channel, a lock — and it can only be resumed by another goroutine operating on that same object. If the runtime can establish that no goroutine capable of running can still reach that object, then no send, receive, close or unlock can ever arrive, and the waiting goroutine is *semantically* garbage even though it is technically still alive. It will occupy its stack and pin everything it references for as long as the process lives. That is a proof, not a heuristic. There is no duration threshold, no count comparison, and no snapshot diffing. Every entry in this profile is a goroutine that is definitely stuck, reported with the stack that shows where. ## What it cannot see The proof requires unreachability, and plenty of real leaks are perfectly reachable. Take the classic case in a streaming service: a writer goroutine blocked sending a frame to a subscriber whose connection has gone, where the subscriber's channel is still sitting in the server's map of active subscribers. The channel is reachable from live code. The runtime cannot rule out that some goroutine will one day receive from it, so it cannot prove anything, and the goroutine does not appear in the profile — even though you know, reading the code, that nothing will ever drain it. Fixing the leak here means removing the subscriber from the map, which is also what would make the goroutine provably leaked. The other blind spot is waits that are not synchronisation at all. A goroutine blocked in a socket read with no deadline, or in a blocking syscall, is waiting on the outside world. There is no in-process object whose reachability decides the outcome, so reachability analysis has nothing to say about it. So the two results are asymmetric, and that asymmetry is the whole point of the question. **A non-empty profile is proof of a leak. An empty profile is not proof of no leak.** Anyone who treats a clean `goroutineleak` result as a clean bill of health has drawn exactly the wrong conclusion. ## How to use it It is not a metric to scrape. Establishing unreachability is far more work than counting goroutines, so this profile costs meaningfully more than the ordinary one and is not something to pull every fifteen seconds alongside your other telemetry. The sensible posture is layered: - Keep the cheap goroutine census as your trend line — that is what tells you something is climbing. - When you suspect a leak, pull `goroutineleak` first. If it names something, you have skipped the entire inference step and can go straight to the code. - If it names nothing, fall back to the ordinary method: two snapshots minutes apart, diff them, read the growing stack's parked call site and its `created by` line. The reachable-channel leak above is the common shape, and this is how you catch it. ## The version caveat This is recent. The profile shipped in Go 1.27; Go 1.26 had it only behind an experiment flag, and earlier releases have nothing like it. That matters organisationally more than technically: if your leak-hunting runbook assumes the endpoint exists, it assumes a toolchain floor across every service that might page you, including ones another team builds. Write the runbook so the ordinary goroutine profile remains the path that always works, and treat the leak profile as the shortcut you take when the binary happens to be new enough.
- Give a real goroutine leak this profile will not report.A writer goroutine blocked sending a frame to a subscriber that disconnected, where the subscriber's channel is still held in the server's map of active subscribers. The channel is reachable from live code, so the runtime cannot prove nobody will ever receive from it. The same applies to a goroutine blocked in a socket read with no deadline — that is a wait on the outside world, not on a reachable in-process object.
- Should you scrape /debug/pprof/goroutineleak on a schedule like a metric?No. Establishing that a goroutine is unreachable is far more expensive than counting goroutines, so this profile is a deliberate diagnostic rather than continuous telemetry. Keep the cheap goroutine census as the trend line that tells you something is climbing, and pull the leak profile when that trend already makes you suspicious.
- What does an empty goroutineleak profile actually license you to conclude?Only that the runtime could not prove any goroutine unresumable at that instant. The most common production leaks — a goroutine parked on a channel that a live registry still holds, or on a read with no deadline — are unprovable by construction and stay invisible here. Treat an empty result as one hypothesis eliminated, then go back to diffing two ordinary goroutine profiles.
The ordinary goroutine profile is a list of everyone standing still in a building. The leak profile is the shorter list of people locked in rooms whose only key has been destroyed — certain, but it says nothing about the people simply waiting for a lift that will never come.
saying these in an interview costs you the question
- Treats an empty goroutineleak profile as proof of no leak
- Assumes it replaces reading the ordinary goroutine dump
- Thinks it flags any goroutine blocked for a long time
- Expects the endpoint on an older toolchain
- Believes it unblocks or kills the leaked goroutines
- Scrapes it continuously alongside ordinary metrics