Have you ever spent nights trying to understand why your application is using so many resources? Your CPU is on fire, memory keeps growing, latency goes up… and the code looks fine. Probably, you're guessing instead of verifying.
Here's how to verify memory leaks, unused resources, and poorly optimized code with Go's built-in profiling tool pprof.
What is pprof?
pprof is Go's built-in profiling tool for collecting and analyzing runtime data — CPU usage, memory allocations, goroutines, and blocking operations.
In simple terms, pprof answers questions like:
- Why is my application slow?
- Where is CPU time being spent?
- What is allocating so much memory?
- Are goroutines leaking?
Spoiler: pprof does not magically optimize your code. It just tells you where you messed up.
How pprof works
pprof works in two steps: collect metrics from your application, then analyze them with go tool pprof.
At runtime, Go samples execution and records metrics. These samples are aggregated into profiles that you can explore with pprof commands.
You might think this is a lot like traces, but under the hood Go does statistical profiling: your program is periodically interrupted and the runtime records what was running at that exact moment. Over time these snapshots form an accurate picture of resource use.
A simple way to enable profiling in your app is using an HTTP server:
This exposes profiling endpoints under /debug/pprof/, such as /debug/pprof/profile (CPU), /debug/pprof/heap (Memory), etc.
Collecting profiles
When you hit a pprof endpoint like /debug/pprof/profile, what you get back is not a report — it's a binary blob. If you try to open it in the browser, you'll download a file and see what looks like complete garbage.
That's expected: this endpoint returns a serialized pprof profile (a protobuf, usually gzip-compressed) with raw sampled data from the Go runtime — stack traces, counters, timestamps. Not charts. Not tables. Not answers. This binary mess is not meant for humans.
And that's where pprof comes in.
pprof takes this raw, unreadable data and decodes, aggregates, and correlates it into human-readable output.
Analyzing profiles
First, install the pprof CLI:
go install github.com/google/pprof@latest
You should see something like that:
With the web server running, collect a CPU profile (10 seconds):
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=10
If everything worked and you waited 10 seconds, you get an interactive shell. This is where the raw profile data becomes actionable. Once inside pprof, you can run commands like top, list, web, etc. (check the options here)
The Big Bad Boy
Imagine we have an endpoint using a lot of CPU — the "Big Bad Boy." For study purposes, I'll use a /work endpoint.
With the app running, hit /work from another terminal to trigger some CPU load:
curl http://localhost:8080/work
You can repeat it a few times or use a load tool like hey or ab to generate more pressure.
Now collect a 10-second CPU profile from the running server:
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=10
Inside the pprof shell, show the top functions consuming CPU with top.
You'll see an output like:
(pprof) top
Showing nodes accounting for 7470ms, 92.45% of 8080ms total
Dropped 2 nodes (cum <= 40.40ms)
Showing top 10 nodes out of 35
flat flat% sum% cum cum%
2030ms 25.12% 25.12% 2030ms 25.12% math.archExp
1570ms 19.43% 44.55% 1570ms 19.43% math.IsInf (inline)
1500ms 18.56% 63.12% 2830ms 35.02% math.log
560ms 6.93% 70.05% 5800ms 71.78% math.pow
430ms 5.32% 75.37% 1050ms 13.00% math.sin
330ms 4.08% 79.46% 330ms 4.08% runtime.pthread_cond_signal
320ms 3.96% 83.42% 1560ms 19.31% math.frexp
290ms 3.59% 87.00% 290ms 3.59% math.Float64frombits (inline)
220ms 2.72% 89.73% 220ms 2.72% math.IsNaN (inline)
220ms 2.72% 92.45% 230ms 2.85% math.normalize (inline)
We can see a lot of math package operations, but we don't know where those operations are used. Run top again with the -cum (cumulative) flag:
(pprof) top -cum
Showing nodes accounting for 2170ms, 26.86% of 8080ms total
Dropped 2 nodes (cum <= 40.40ms)
Showing top 10 nodes out of 35
flat flat% sum% cum cum%
110ms 1.36% 1.36% 7750ms 95.92% main.heavyComputation
0 0% 1.36% 7750ms 95.92% main.main.func1
0 0% 1.36% 7750ms 95.92% net/http.(*ServeMux).ServeHTTP
0 0% 1.36% 7750ms 95.92% net/http.(*conn).serve
0 0% 1.36% 7750ms 95.92% net/http.HandlerFunc.ServeHTTP
0 0% 1.36% 7750ms 95.92% net/http.serverHandler.ServeHTTP
0 0% 8.29% 2830ms 35.02% math.Log (inline)
Spotted! main.heavyComputation is consuming 95.92% of the total CPU — that is our "Big Bad Boy."
You can do the same for memory, block, mutex, and goroutine profiles
- Memory (heap) – see allocations and leaks:
go tool pprof http://localhost:6060/debug/pprof/heap
- Goroutines – detect leaks or deadlocks:
go tool pprof http://localhost:6060/debug/pprof/goroutine
- Block – see time spent waiting on channels, mutexes, etc.:
go tool pprof http://localhost:6060/debug/pprof/block
- Mutex – see contention hotspots:
go tool pprof http://localhost:6060/debug/pprof/mutex
All of these can be analyzed the same way (top, list, web, svg). The web and svg commands produce graphical visualizations (requires Graphviz), making it much easier to spot hotspots.
Visualization (Graphviz required)
Graphical visualizations make it easier to spot CPU or memory hotspots quickly. Install Graphviz first:
-
web: opens a call graph in your browser -
svg: outputs an SVG call graph -
pdf: outputs a PDF graph
Don't spray and pray
pprof has multiple profile types because performance problems love to hide in different places. Each type answers a different question:
- CPU – where time is spent
- Heap – memory usage and leaks
- Goroutines – leaks or deadlocks
- Block – wait times
- Mutex – contention
Always choose the profile that matches the problem.
ADDITIONAL: Profiling in Kubernetes
If your app runs in a Kubernetes cluster, port-forward the pprof port from your pod:
kubectl port-forward deploy/my-app 6060:6060
Access pprof locally:
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=10
Analyze interactively as usual (top, list).
Useful Links & References
Go pprof docs: https://pkg.go.dev/net/http/pprof
Runtime profiling: https://pkg.go.dev/runtime/pprof
go tool pprof guide: https://go.dev/doc/diagnostics#profiling
Official Go blog post about pprof: https://go.dev/blog/pprof
So, Thanks!
If this article saved you even one late night of guessing and blaming the garbage collector, it already did its job 😄. Thanks for reading!




Top comments (0)