Memory Profiling Basics
Heap snapshots, allocation timelines, detached DOM nodes, and practical leak hunts in SPAs.
- browser
- memory
- devtools
- leaks
- performance
Memory bugs feel like “the tab gets slow after an hour.” CPU profiles won’t show a classic leak; you need the Memory panel: heap snapshots, allocation instrumentation, and an eye for detached DOM nodes retained by closures.
Docs: Chrome Memory panel, MDN memory management.
Symptoms vs causes
| Symptom | Possible cause |
|---|---|
| Heap climbs with SPA navigations | Listeners/caches not cleared |
| Detached nodes retain MB | React roots, maps of elements, closures |
| Sudden spikes | Large arrays, images, JSON |
| GC thrash / long pauses | Allocation churn |
Heap snapshot workflow
- Open Memory → Heap snapshot.
- Interact (open modal, navigate route, close).
- Take another snapshot.
- Compare delta / filter Detached.
// Classic leak: listener survives unmount
useEffect(() => {
const onResize = () => { /* ... */ };
window.addEventListener('resize', onResize);
// missing: return () => window.removeEventListener('resize', onResize);
}, []);
Detached DOM nodes
Nodes removed from the document but still referenced from JS:
const cache = [];
function mount() {
const el = document.createElement('div');
document.body.append(el);
cache.push(el); // retains after remove
el.remove();
}
Frameworks: storing DOM nodes or fiber-era refs in global stores is a red flag.
Allocation timeline
Record Allocation instrumentation on timeline while reproducing. Spikes that never drop after GC suggest retained growth; sawtooth that returns to baseline is normal churn.
Retainers view
When you find a suspicious object, Retainers shows why GC can’t collect it — which array, which closure, which module-level Map. That’s the actual fix target.
SPA checklist
- Remove listeners / observers / ResizeObserver on unmount.
- Clear timers and intervals.
- Abort
fetchor ignore stale results. - Close WebSockets.
- Bound caches (
Mapwith max size, not infinite). - Revoke
URL.createObjectURLafter use.
const url = URL.createObjectURL(blob);
img.src = url;
// later
URL.revokeObjectURL(url);
Not every growth is a leak
BFCache, browser caches, and memoization grow deliberately. Confirm with three snapshots and user-flow reproduce. Force GC (DevTools trash icon) between comparisons carefully — understanding it still doesn’t run in users’ machines the same way.
Interview out-loud
“I take heap snapshots before and after a flow, look for detached DOM and growing retainers, then fix listeners and unbounded caches. Allocation timelines show churn vs true retention. Always clean up effects on unmount in SPAs.”
Framework-specific leak hunts
- Global event buses without unsubscribe
- React Query / SWR caches grown without bounds
- Chart instances not
dispose()’d - Map/WebGL contexts left alive on route change
Take a snapshot, interact 10 times, snapshot again — retained size should not climb linearly every cycle.
Related
- Page lifecycle events
- Browser DevTools Performance panel
- Service workers overview
- Virtual lists performance
Further depth
Teams often under-invest in this topic until an incident or CWV regression. Schedule a one-hour drill: reproduce the failure mode in DevTools, list the top three mitigations for your stack, and file tickets with owners. Revisit after the next major feature that touches networking, rendering, auth, or third parties — those are the moments regressions land. Keep primary documentation links in the runbook so on-call is not searching chat history at 2am.
Concrete artifacts to leave behind: a short architecture note, a CI assertion or header snapshot, and a dashboard panel (lab or field) that would have caught the last bug. Teaching the rest of the team the mental model matters as much as the one-line fix.
Further reading
Related guides
- Browser DevTools Network PanelRead waterfalls, timing phases, headers, throttling, and initiator chains in the Network panel like a production debugger.
- Browser DevTools Performance PanelRecord main-thread timelines: long tasks, style/layout/paint, frames, and how to turn flame charts into INP fixes.
- Throttling CPU and NetworkDevTools CPU and network throttling: when lab numbers lie, custom profiles, and reproducing field pain.
- BFCache Back Forward CacheHow the back/forward cache freezes pages for instant history nav, what blocks it, and how to restore state safely.
- Browser Networking 101DNS, TCP/TLS, HTTP/1.1 vs H2/H3, connection reuse, and what frontend code can actually influence.