What are the pitfalls of closures?
By FrontendPro Editorial Team Updated 9/26/2026
Answer
Closures are not dangerous in themselves, but three patterns cause real problems.
1. They keep the whole scope alive
A closure retains its entire enclosing scope, not only the variables it uses:
function setup() {
const hugeData = new Array(1e6).fill("x");
const id = 42;
return () => id; // hugeData is still reachable
}
Engines optimise this in simple cases, but a long-lived closure — an event listener, a timer, a cached callback — can hold megabytes alive indefinitely. Extract only what you need before returning.
2. Listeners and timers that are never released
This is the most common leak in real applications:
function mount(node) {
const state = loadBigState();
window.addEventListener("resize", () => update(node, state));
}
Nothing removes that listener, so node and state survive after the component is gone. Remove listeners on teardown, or pass an AbortController signal.
3. The classic loop bug
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i)); // 3, 3, 3
All three closures share one i. let creates a fresh binding per iteration and fixes it.
Two smaller issues
Closures capture references, not snapshots. If the variable changes later, the closure sees the new value — usually what you want, occasionally a surprise.
And a function created inside a loop or a render is a new function each time. That breaks removeEventListener, and in React it breaks memoisation on child components, which is what useCallback exists to address.
How to find them
Take two heap snapshots in the Memory panel, one before and one after the action you suspect, and compare. Detached DOM nodes and growing closure counts point straight at the cause.
