Think of a function like a person going on a trip. When it’s created, it can pack a small backpack of things from the place where it was born — names, numbers, and objects it can still use later.
Even after that place is gone, the function still carries the backpack with it. That’s why it can answer questions long after the outer function has finished. In interviews, this “remembering” behavior is what people mean by a closure.
What a closure really is
A closure is a function plus the lexical environment it was created in. The important part is that JavaScript does not copy values into the function; it keeps a live reference to the surrounding variables it can access.
This matters because it explains why functions can keep working after the outer scope returns. It also explains data privacy: variables inside the outer function are not directly reachable from the outside, but inner functions can still use them.
Closures show up everywhere in JS interview questions because they power patterns like counters, once(), memoize(), and the module pattern. They also create bugs in loops when many callbacks share the same captured variable.
Lexical environment capture
JavaScript resolves variables based on where code is written, not where it is called. That surrounding variable space is the lexical environment.
When an inner function is created, it closes over the environment it needs. If the outer variable changes later, the closure sees the updated value because it holds the variable binding, not a frozen copy.
That’s why this pattern is useful for stateful functions:
the inner function can read and update outer variables
the outer function can return behavior, not just data
the outside world cannot directly touch private variables
function makeCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
Data privacy and the module pattern
Closures let you hide implementation details. If a variable only lives inside an outer function, code outside cannot access it directly, which gives you private state.
That’s the basis of the classic module pattern: expose only the methods you want, and keep the rest internal. Interviewers often want you to explain that this is not class-based privacy; it is scope-based privacy achieved through closures.
The classic trap is var inside loops with asynchronous callbacks. var is function-scoped, so every callback closes over the same single variable, not a fresh one per iteration.
By the time the callback runs, the loop may already be finished, so all callbacks see the final value. This is a closure question because the callback is not copying the loop value; it is sharing the same binding.
let fixes this because it creates a new binding for each iteration. That gives each callback its own captured value.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// prints 3, 3, 3
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 0);
}
// prints 0, 1, 2
Memory implications
A closure can keep its captured variables alive longer than you expect. If an inner function is still reachable, the garbage collector cannot free the closed-over environment.
That is usually fine, but it matters when the captured data is large or when long-lived callbacks hold onto objects accidentally. Interviewers often test whether you know that closures can cause memory retention, not just convenience.
Good practice is to keep captured state small and release references when they are no longer needed. The closure itself is not the leak; it is the continued reachability of the environment.
`once()` and `memoize()` with closures
A common interview use case is building utilities that remember past calls. once() uses closure state to ensure a function runs only one time, while memoize() stores computed results so repeated calls are fast.
Both work because the returned function retains access to internal variables that are hidden from the outside. This is a practical demonstration of private state plus persistent behavior.
These patterns are good interview answers because they show you understand both the mechanism and the purpose of closures: encapsulation and reuse of state across calls.
function once(fn) {
let done = false, result;
return function (...args) {
if (!done) {
done = true;
result = fn(...args);
}
return result;
};
}
function memoize(fn) {
const cache = new Map();
return function (x) {
if (cache.has(x)) return cache.get(x);
const value = fn(x);
cache.set(x, value);
return value;
};
}
A classic interview loop fix
If you need to preserve the loop index for async work, there are two common answers: use let, or create a new scope with a function wrapper.
The key interview point is to explain why the fix works: each callback must close over a distinct binding. Without that, all callbacks share the same variable and observe its final value.
You should also be ready to say that modern JavaScript usually prefers let because it is simpler and clearer than an IIFE workaround.
for (var i = 0; i < 3; i++) {
(function (n) {
setTimeout(() => console.log(n), 0);
})(i);
}
// prints 0, 1, 2
What interviewers try to trap you on
Common mistakes:
Saying a closure is just “a function inside a function.” That is incomplete; the function must also remember its lexical environment.
Saying values are copied into the function. They are not copied; the closure keeps access to the variable binding.
Confusing var with let in loops. The bug happens because callbacks share one binding with var.
Forgetting the memory side. A closure can keep data alive, so it affects retention.
Mixing up privacy with security. Closures provide encapsulation, not true security against all attacks.
The best interview answer always explains mechanism, use case, and tradeoff together.