Developers are consistently wrong about where their programs spend time. The function that feels expensive is fine; the innocuous helper called in a loop is consuming most of the runtime. A profile settles it, and a flame graph is the most efficient way to read one.
Reading it correctly takes about a minute to learn and prevents a lot of wasted optimization.
What changed in 2026
- Continuous profiling in production became normal. Always-on low-overhead profiling replaced the practice of profiling only when investigating a specific problem.
- Differential graphs got easier. Comparing two profiles to see what changed between releases became a standard workflow rather than a manual exercise.
- Off-CPU profiling spread. Recognition that most latency problems are waiting rather than computing pushed adoption of profiling that captures blocked time.
- Agent-driven analysis appeared. Coding assistants reading profiles and proposing optimizations made profile availability more valuable.
How to read one
| Element |
Meaning |
| Width of a frame |
Proportion of samples in that function and its callees |
| Height |
Call stack depth at that point |
| Vertical position |
Caller below, callee above |
| Horizontal position |
Alphabetical, not chronological |
| Colour |
Usually arbitrary; sometimes encodes category |
The horizontal axis is the thing people get wrong. It looks like a timeline and it is not — frames are sorted alphabetically so identical stacks merge into one wide block. A flame graph tells you where time went, never when.
Width is the only axis that indicates cost. A tall narrow tower is a deep call stack that consumes almost no time; it looks dramatic and means nothing. A wide flat plateau near the top is a function doing a lot of work itself, which is where optimization pays.
The reading procedure is: scan the top for the widest frames, then trace down to see who called them. That is where the time is.
What profiles miss
A CPU profile samples what the processor is executing. A program spending most of its wall-clock time waiting — on a database, an HTTP call, a lock, a disk — appears almost idle in a CPU profile, because it was not on the CPU.
That is the common case for server applications. The request took eight hundred milliseconds and the CPU profile accounts for forty of them; the rest was waiting. Off-CPU profiling, which samples blocked time and what it was blocked on, is what shows the other seven hundred and sixty milliseconds.
Use both. CPU profiling finds computational hot spots; off-CPU profiling finds latency. Most server performance work is the second kind, which is also where distributed tracing helps — OpenTelemetry setup guide covers seeing the waits across services.
Sampling profiles are also statistical. A function appearing in a small number of samples might be noise. Profile long enough that the numbers are stable, and be suspicious of conclusions drawn from a short capture.
Common mistakes
- Reading the horizontal axis as time. It is alphabetical ordering.
- Chasing tall stacks. Depth is not cost; width is.
- CPU profiling a latency problem. Misses waiting entirely.
- Profiling in development. Different data volumes and different hot paths.
- Optimizing before profiling. Intuition is unreliable here.
- Short capture windows. Statistical noise reads as signal.
FAQ
How much overhead does profiling add?
Modern sampling profilers typically add a small single-digit percentage, which is why continuous production profiling is viable.
Can I profile in production?
Yes, and you generally should — production has the data volumes and access patterns that create real bottlenecks.
What is a differential flame graph?
One showing the difference between two profiles, highlighting what got slower or faster. It is the fastest way to find a performance regression between releases.
Does this work for memory?
Allocation flame graphs show where memory is allocated rather than where time is spent, and read the same way. Useful for the leak hunting in finding memory leaks in Node.
Where to go next
For latency across services, read OpenTelemetry setup guide. For memory problems, finding memory leaks in Node, and for load generation, load testing guide — see the performance section of the archive.