A JavaScript memory leak isn't a language bug โ it's unintentionally keeping a reference alive to something that should have been garbage collected. Recognizing the handful of common patterns that cause this is essential for long-running, production-grade applications.
1Memory Leaks | JavaScript Tutorial - In-Depth Guide Part 1
A memory leak in JavaScript almost always means something is still REACHABLE that the program logically doesn't need anymore โ the garbage collector can only free memory it can prove is unreachable.
// The garbage collector CANNOT free this,
// because something (the array below) still references it:
const leakedObjects = [];
function createLeak() {
leakedObjects.push({ big: new Array(1000000) });
}Unintended Retention
2Memory Leaks | JavaScript Tutorial - In-Depth Guide Part 2
The single most common leak: adding an event listener to a long-lived object (like window or document) without ever removing it, especially inside a component that gets created and destroyed repeatedly.
// Leaks if this component is created/destroyed repeatedly
// without removing the listener:
function setupComponent() {
window.addEventListener('resize', handleResize);
// missing: window.removeEventListener('resize', handleResize) on cleanup
}Forgotten Event Listeners
3Memory Leaks | JavaScript Tutorial - In-Depth Guide Part 3
An uncleared setInterval keeps its callback (and everything it closes over) alive forever, since the timer itself holds a reference to the function.
function startPolling() {
const largeCache = new Map();
setInterval(() => {
updateCache(largeCache); // largeCache is kept alive forever
}, 5000);
// missing: a way to clearInterval() when polling should stop
}Uncleared Timers
4Memory Leaks | JavaScript Tutorial - In-Depth Guide Part 4
'Detached DOM nodes' are elements removed from the visible page but still referenced by JavaScript (in a variable, cache, or closure), preventing them โ and their entire subtree โ from being garbage collected.
let cachedElement = document.querySelector('.modal');
cachedElement.remove(); // removed from the page, but still referenced!
// cachedElement = null; // this line would actually free itDetached DOM Nodes
5Memory Leaks | JavaScript Tutorial - In-Depth Guide Part 5
An unbounded, ever-growing cache (like a plain Map used for memoization with no eviction strategy) is a slow-motion memory leak in any long-running process.
// Unbounded โ grows forever:
const cache = new Map();
// Bounded โ evicts least-recently-used entries:
const cache = new LRUCache({ max: 500 });Unbounded Caches
6Step-by-Step Breakdown
A memory leak in JavaScript almost always means something is still REACHABLE that the program logically doesn't need anymore โ the garbage collector can only free memory it can prove is unreachable.
Checkpoint: Can the garbage collector free memory for an object that is still reachable through some reference, even if the program logically no longer needs it?
- โYes, it can detect and free logically unused objects
- โNo, reachability (not logical need) is what determines collection
The single most common leak: adding an event listener to a long-lived object (like window or document) without ever removing it, especially inside a component that gets created and destroyed repeatedly.
An uncleared setInterval keeps its callback (and everything it closes over) alive forever, since the timer itself holds a reference to the function.
'Detached DOM nodes' are elements removed from the visible page but still referenced by JavaScript (in a variable, cache, or closure), preventing them โ and their entire subtree โ from being garbage collected.
Checkpoint: If a DOM element is removed from the page with .remove() but a JavaScript variable still references it, is it garbage collected?
- โYes, removing it from the page is enough
- โNo, the lingering reference keeps it in memory
An unbounded, ever-growing cache (like a plain Map used for memoization with no eviction strategy) is a slow-motion memory leak in any long-running process.
Next, we'll explore 'Garbage Collection'.
Level Up ๐
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Memory Leaks Can Degrade Assistive Technology Responsiveness Over Long Sessions
A gradually growing memory footprint from unremoved listeners or unbounded caches can slow down an entire page over a long session, including the responsiveness of screen readers and other assistive technology interacting with an increasingly sluggish tab.
SEO Implications
- 1
Memory Leaks Degrade Long-Session Performance Metrics
While not a direct ranking factor, a page that becomes progressively slower and less responsive during a long user session due to memory leaks can hurt engagement metrics and perceived quality, which correlate with search performance indirectly.
Best Practices
Always Pair Every addEventListener with a Matching removeEventListener
This is the single most common source of memory leaks in long-running single-page applications, especially for components created and destroyed repeatedly.
Bound Long-Lived Caches with an Eviction Policy
An LRU cache (or similar) with a fixed size limit prevents a memoization or caching layer from growing unbounded over the lifetime of a long-running process.
Frequent Bugs
A single-page app's memory usage grows steadily every time the user navigates between pages, because each page's components add event listeners that are never removed on navigation away.
Ensure every component's cleanup logic removes any event listeners, timers, or observers it created, mirroring exactly what was set up.
A cached reference to a removed DOM element is kept around (e.g. for later reuse), preventing it from being garbage collected even though it will never actually be reused.
Explicitly null out cached DOM references once they are truly no longer needed, or avoid caching them in the first place if reuse is not actually planned.
Real-World Examples
Diagnosing a Steadily-Growing Memory Usage in a Long-Running Dashboard
A dashboard app left open for hours showed steadily increasing memory usage in browser dev tools, eventually causing the tab to become sluggish.
// Found via a heap snapshot comparison: an interval polling for updates
// was never cleared when the dashboard's data source changed:
useEffect(() => {
const id = setInterval(fetchUpdates, 5000);
return () => clearInterval(id); // the missing cleanup that fixed the leak
}, [dataSource]);