- Section
- performance
- Kind
- Editorial
- Reading time
- 3 min
- Updated
- 8 September 2026
Recognise the Pattern of Memory Leaks in Python
Not every rising memory graph signifies a leak. If retained object counts and memory usage keep increasing with repeated identical operations, without ever plateauing, you likely have a memory leak. The other rising pattern—plateauing growth and release—is typical of caches.
How to tell a leak from a cache
The high-level rule is to repeat a work cycle, take two heap snapshots, and inspect the difference. If the retained heap size keeps rising linear increment on each repeated action, without ever flattening, it strongly suggests a leak.
A retained heap size that grows then plateaus points to a cache, potentially one with a leak but in aggregate bounded.
This difference appears in repeated-load heap snapshots. Chrome DevTools shows how to capture a heap snapshot as a baseline, repeat an actions sequence, capture a second, and compare the two.
Any live objects in the difference that match your leaked-profiles are the lead. If none match, the heap size delta indicates allocation size. Anything indicating exponential growth suggests a leak.
The diagnosis routine
First, follow this repetition-and-snapshot sequence:
— Not under any load, capture a heap snapshot to a baseline.
— Repeat the actions users would normally perform. Consider using an architectural stress test of typical user workload.
— Immediately capture a second heap snapshot.
— Compare the two with the devtools Comparison view. Sort the Difference view by Size Delta to highlight the most grown objects. Further narrow down to constructors whose delta is a positive number, since a rising constructor count means live objects.
If the Devtools includes a retainers and dominators graph, examine them next. Retainers show which objects keep leaked objects live, pointing to likely culprits. Dominators are which objects are retainers, so are likely retaining culprits too.
Unfortunately, the devtools have yet to surface the heap size after garbage collection. Without an additional step, a leak detector must treat all allocations live, but that mislabels transient growth patterns. Contributors are welcome to offer suggestions.
What the diff is looking for
Follow these three steps with the repeated-load snapshots in hand.
First, look at constructor objects. A leak adds live objects, so it should appear as positive-delta constructors. If a constructor never decreased, it is live, so is a worker.
Rising heap size also shows where the leaks are coming from. Even if a constructor is stable, its allocation size is rising, suggesting a stable number of objects, each with growing size.
Finally, look at the retainers-and-dominators graphs from the last snapshot. These show parent-child links, exposing what keeps the leaked objects alive. Want to know the leak? Check these, plus constructors growing without bound.
These three parts are the primary leak rule: positive-delta constructors, rising allocation size for stable constructors, and a matching retainers/dominator result.
When growth is not a leak
Not all rising heap size is leak-fodder. Other scenarios explain some, but not steady, continuing growth:
Activity-specific growth, like connection spikes or logs exploding under stress, can look like leaks.
One way is the listener trap, where interfaces hold a cache of all registered listeners, without letting them expire.
Closures also hold capture variables, and these can hang on to memory objects that look like leaks, but are in fact merely still alive.
Even so,, an object-pool leak can look the part: many articles explain how a connection-pool can expand under growth-load, if retries overwhelm valid returns.
Put it all together, and you see that a majority of runoff is not fundamentally a leak. But the difference separates the floods: leaks retain growth, while caches and temps hold to a peak before vanishing.
When the leak is not yours
Finally, what if the leak turns out to be outside your code? In particular, what if it lives in a dependency?
The same repeated-load / snapshot / compare method can help.
One last option: some articles mention wrapping external dependencies, for reasons including leak isolation.
Given the risk of external leaks and leaks-lensing in dependencies, the best practice rule is check any dependency library exhibiting a retained-growth pattern in the debug, regardless of whether your code snaps onto the same pattern. Even a background pattern is a leak if the dependency grows linearly on repeated action.