Closures and Memory Leaks in JavaScript
Closures keep variables alive, which can cause memory leaks. Here are the patterns and how to prevent them.
Closures and Memory Leaks in JavaScript
Closures keep their lexical environment alive, preventing the garbage collector from freeing closed-over variables. This is useful for data privacy but can cause memory leaks if not managed carefully.
How Closures Cause Memory Leaks
When a closure exists, it holds a reference to its lexical environment. As long as the closure is reachable, the environment (and all its variables) cannot be garbage collected.
Leak Pattern 1: Accumulating Handlers
const handlers = []; function setup(id) { const big = new Array(1_000_000); handlers.push(() => big.length); } for (let i = 0; i < 100; i++) setup(i); // 100 large arrays are alive, each kept by a closure in handlers `` **Fix**: Remove handlers when no longer needed, or do not close over large objects. ### Leak Pattern 2: Forgotten Timers ```js let count = 0; const id = setInterval(() => { count++; console.log(count); }, 1000); // if clearInterval is never called, the timer and count live forever `` **Fix**: `clearInterval(id)` when done. In React, clean up in `useEffect`: ```js useEffect(() => { const id = setInterval(() => { ... }, 1000); return () => clearInterval(id); }, []); `` ### Leak Pattern 3: Detached DOM Elements ```js function setup() { const element = document.getElementById("btn"); element.addEventListener("click", () => { console.log(element.id); // closes over element }); } setup(); // if element is removed from the DOM, the handler keeps it alive in memory `` **Fix**: Remove the event listener when the element is removed: ```js const handler = () => console.log(element.id); element.addEventListener("click", handler); // later: element.removeEventListener("click", handler); `` Or use `WeakRef` for optional references. ### Leak Pattern 4: Closures in Long-Lived Data Structures ```js const cache = {}; function expensive(key) { if (cache[key]) return cache[key]; const result = compute(key); // might be a large object cache[key] = () => result; // closure keeps result alive return cache[key]; } `` If `cache` grows unbounded, each entry keeps a large object alive. **Fix**: Use a `WeakMap` (keys are weakly held) or implement a size limit (LRU cache). ### Leak Pattern 5: Accidental Closures ```js function foo() { const big = new Array(1_000_000); return function () { return 42; // does not use big, but big is still alive? }; } `` Modern engines optimize this: if a closure does not reference `big`, the engine may free `big`. But do not rely on this across all engines. Extract only what you need. ### How to Detect Memory Leaks - **Chrome DevTools**: Memory tab, take heap snapshots, compare them. - **Performance tab**: record memory usage over time, look for growth. - **`performance.memory`**: (Chrome only) check `usedJSHeapSize` over time. ### The Takeaway Closures cause memory leaks by keeping variables alive. Common patterns: accumulating handlers, forgotten timers, detached DOM elements, unbounded caches, and accidental closures. Fix by removing handlers, clearing timers, using `WeakMap`/`WeakRef`, bounding caches, and extracting only what you need. Use DevTools to detect leaks.
A closure holds a reference to its lexical environment. As long as the closure is reachable, the environment (and all its variables) cannot be garbage collected. If closures accumulate or keep large objects alive, memory grows unbounded.
Accumulating handlers in arrays, forgotten timers (setInterval never cleared), event listeners on detached DOM elements, unbounded caches of closures, and accidental closures that keep large objects alive without using them.
Remove event listeners when done, clear timers with clearInterval/clearTimeout, use WeakMap or WeakRef for weak references, bound cache size (LRU), and extract only what you need instead of closing over large objects.
Use Chrome DevTools Memory tab: take heap snapshots, compare them, look for growing retained size. Use the Performance tab to record memory usage over time. Check performance.memory.usedJSHeapSize (Chrome only) for programmatic monitoring.
Some do. If a closure does not reference a variable, the engine may free it even though it is in the same lexical environment. But this is an optimization, not guaranteed across all engines. Do not rely on it; extract only what you need.
Ready to master React completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master React
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

