What is closure in js?
Closure - a function that keeps access to variables from the scope where it was created, even after that scope has finished executing.
Theory
TL;DR
- Think of it as a backpack: the inner function packs variables from its birth environment and carries them across calls.
- The outer function returns, but its variables stay alive inside the returned function.
- Each closure instance gets its own copy of those variables, so two counters never share state.
- Use it for private mutable state without a class. Skip it for stateless utilities.
Quick Example
function createCounter() {
let count = 0; // packed into the "backpack"
return function() {
count++;
console.log(count);
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2 - count persists between callscreateCounter has already returned, but count is still there. The returned function captured it at creation time.
Key Difference
Global variables solve the same "remember something" problem but pollute the whole namespace. Parameters reset on every call. Closures give you persistent private state tied to one specific function instance. Call createCounter() twice and you get two independent counters, each tracking its own count.
When to Use
- Private counters and state:
createCounter()returns a function with hidden, persistent state. - Event handlers:
makeHandler(id)capturesidso the callback always knows which element triggered it. - Module pattern:
createLogger(level)filters logs by a captured level, no class needed. - React hooks:
useEffectanduseCallbackuse closures to capture state and props between renders.
Avoid closures when the function is stateless. Just pass arguments directly.
How JavaScript Handles This
V8 and other engines allocate a context object when the inner function is created. Variables from the outer scope are stored there by reference, not copied by value. When the inner function runs later, it looks them up through a context chain (lexical scoping). That context stays in memory until the closure itself becomes unreachable.
Common Mistakes
Var in loops - the classic trap:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // prints 3, 3, 3
}
// Fix with let - each iteration gets its own binding:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // prints 0, 1, 2
}With var, all three callbacks share one i. By the time they run, the loop has finished and i is 3. I've seen this exact pattern in older jQuery click-binding code where every button handler logged the same index.
Thinking outer variables die after return:
function makeAdder(x) {
return y => x + y; // x is captured, not dead
}
const add5 = makeAdder(5);
console.log(add5(3)); // 8 - x is still alivex lives as long as add5 does.
Memory leak from DOM listeners:
function attach(el) {
const bigData = new Array(1000000).fill('x'); // captured by closure
el.addEventListener('click', () => console.log(bigData.length));
// bigData cannot be freed until el is removed from DOM
}The closure holds a reference to bigData, blocking garbage collection. Use only what you need inside the handler, or remove the listener when done.
Real-World Usage
- React:
useCallbackanduseEffectcapture state and props for stable, up-to-date handlers. - Lodash:
_.debounce(fn, ms)returns a closure tracking the timestamp of the last call. - Express: middleware factories like
auth(userId)captureuserIdfor the request handler scope. - Node.js:
EventEmitter.on('event', handler)wherehandlercloses over listener-specific state.
Follow-Up Questions
Q: What happens to memory when a closure holds a large object?
A: The object stays in memory as long as the closure is reachable. If that closure is attached to a DOM element that never gets removed, you have a leak. Chrome DevTools heap snapshots show which closures are keeping objects alive.
Q: What is the difference between closure scope and block scope?
A: Block scope (let, const) creates a new binding per block or loop iteration. Closure scope spans the entire lifetime of the function and persists across multiple calls to the returned inner function.
Q: How do you implement private fields without ES2022 class fields?
A: Use a closure inside the constructor: class Counter { constructor() { let count = 0; this.inc = () => ++count; } }. Each instance gets its own count through its own closure.
Q: Why does useEffect in React sometimes read stale state?
A: The effect captures variables at the time it runs. If state changes after that, the closure still holds the old value. Add the variable to the dependency array to get a fresh closure, or use useRef for values you need to read without re-running the effect.
Examples
Basic: Private State with a Counter
function createCounter() {
let count = 0;
return {
increment() { count++; },
decrement() { count--; },
value() { return count; }
};
}
const c = createCounter();
c.increment();
c.increment();
c.decrement();
console.log(c.value()); // 1
// count is not accessible from outside:
console.log(c.count); // undefinedThree methods share one count. Nothing outside createCounter can read or modify it directly. This is the module pattern that existed before ES modules were standardized.
Intermediate: Event Handler with Captured Context
function setupButtons(labels) {
labels.forEach((label, index) => {
const button = document.createElement('button');
button.textContent = label;
// Each callback closes over its own label and index
button.addEventListener('click', () => {
console.log(`Clicked: ${label} at position ${index}`);
});
document.body.appendChild(button);
});
}
setupButtons(['Home', 'About', 'Contact']);
// Clicking "About" logs: Clicked: About at position 1Because forEach gives each callback its own label and index bindings, every handler correctly knows which button it belongs to. This is the real-world scenario where the var-in-a-loop bug used to cause problems before let arrived.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.