Troubleshooting guide · performance · Published 2026-08-16 · 4 min read
JavaScript memory leaks and how to fix them
Find and fix JavaScript memory leaks in the browser: detached DOM nodes, closure references and un-cleared intervals, using the DevTools heap tools.
- ·What a browser leak is
- ·The common causes
- ·Finding and fixing it
What a browser leak is
A JavaScript memory leak is memory the page keeps holding that it no longer needs, so the browser's usage climbs steadily over time instead of stabilising. Because the browser must eventually garbage-collect unreachable memory, a true leak means something is still reachable that should not be: a reference chain keeps an object, an event listener, or a DOM node alive when the page no longer uses it. The symptoms are a tab that gets slower, and in the worst case a reload or a hang, as the growing heap forces increasingly aggressive garbage collection.
A leak is usually not an error message; it is a growth trend. Fixing it is about finding the reference that keeps the object alive and removing it. This is different from a one-off peak, such as the heap exhaustion from a single large allocation that a process hits and crashes on, which is a spike rather than a slow accumulation.
The common causes
- Detached DOM nodes. An element is removed from the document but a JavaScript variable, closure, or event listener still references it, so its subtree cannot be collected. Detached nodes are one of the most common leaks and show up in the heap snapshot as detached.
- Forgotten event listeners. A listener added to an object that is later discarded, without removal, keeps both the listener and its captured context alive. On single-page apps that re-render views, this accumulates with every navigation.
- Intervals and timeouts that are never cleared. A
setIntervalthat keeps firing after its tab or view is gone holds the callback, and anything the callback closes over, indefinitely. Clear intervals in the matching cleanup path. - Closures that capture too much. A callback that closes over a large object keeps that whole closure scope alive as long as the callback lives, even if the callback only needs a small part of it.
- Global or module-level caches. An object cached at module scope and never pruned grows without bound as more entries are added.
Finding and fixing it
- Record a heap snapshot and the delta. Open DevTools Performance, record memory against a workload, then take two heap snapshots and compare allocations over time. Because a leak is a trend, the tell is a baseline that keeps rising through repeated identical work.
- Look for detached nodes and retained objects. In the heap snapshot, expand detached nodes to see what keeps a removed element alive, and inspect the retaining paths to identify the variable or listener holding it. Trail the reference chain to the root object that should have been released.
- Remove listeners and intervals in the cleanup path. When a view or component is torn down, remove its listeners and clear its intervals, matching every put with a remove:
window.addEventListener("scroll", handler);
// later, on teardown:
window.removeEventListener("scroll", handler);
clearInterval(timer);
- Release closures over large data. When a long-lived callback needs only a field, copy the small value instead of capturing the whole object, so the big object can be collected. Keep caches bounded with a size limit or expiry rather than unbounded.
- Re-measure the trend. Re-run the workload and confirm the heap returns to a stable level instead of climbing. Integrate this as part of the web performance audit, and check it on page tear-down flows rather than only on first load.
Memory pressure also explains interaction that degrades over a session. If heavy main-thread work is the root rather than a reference leak, the off-main-thread and bundle reduction guides address the workload itself. The fix for a genuine leak is nearly always a removed reference, which is why the message to keep in mind is: a page that holds what it no longer needs is a page that will slowly degrade until it cannot.