“Explain closures” is one of the most common JavaScript interview prompts, yet a memorised definition is rarely enough. The real test is whether you can predict a counter, explain why three timers share one value, and repair the code. All three come from one idea: a function retains access to the lexical environment where it was defined.
Closures begin with lexical scope
A closure is a function bundled with references to its surrounding lexical environment. “Lexical” means the relevant scope is decided by where the function is written in the source, not where another function later calls it.
const label = "outer";
function makePrinter() {
const label = "created here";
return function printLabel() {
console.log(label);
};
}
const print = makePrinter();
print(); // created hereprintLabel was defined inside makePrinter, so it resolves label through that birth scope. Calling it later from another place does not change the lookup.
The subtle point is that a closure captures a binding, not a frozen snapshot of its value. If code with access to the same binding changes it, later closure calls observe the new value. This live-binding rule explains both useful private state and the famous loop trap.
For a wider question map around scope, hoisting, promises, and objects, use the JavaScript interview questions for freshers.
Worked closure: a private counter
Consider a function that creates and returns another function:
function makeCounter() {
let count = 0;
return function () {
return ++count;
};
}
const c1 = makeCounter();
console.log(c1()); // 1
console.log(c1()); // 2
console.log(c1()); // 3The call to makeCounter() creates a fresh count binding with value 0. Although makeCounter then returns, its inner function remains reachable through c1. That function closes over count, so the binding also remains reachable.
Trace the calls precisely:
First call:
++countchanges 0 to 1, then returns 1.Second call: the same binding changes 1 to 2, then returns 2.
Third call: it changes 2 to 3, then returns 3.
Now create another counter:
const c2 = makeCounter();
console.log(c2()); // 1
console.log(c1()); // 4c2 has a new invocation environment and its own count, starting at 0. It does not reset or share c1's state. The two returned functions came from separate calls, so they close over separate bindings.

The var loop trap and two correct fixes
Here is the interview classic:
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}The loop finishes synchronously before the timer callbacks run. var is function-scoped, so all three callbacks close over one shared i binding. After the loop's final increment, that binding contains 3. The callbacks therefore print:
3
3
3Fix one: use a per-iteration let binding
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}For a for loop, let creates a fresh binding for each iteration. The three callbacks close over bindings containing 0, 1, and 2, so the output is 0, 1, 2.
Fix two: capture the current value through an IIFE
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(() => console.log(j), 100);
})(i);
}The immediately invoked function receives the current value of i as argument j. Every invocation creates a new parameter binding, so each timer closes over a different j. This also prints 0, 1, 2.

The crucial explanation is not that a closure “remembers the old value.” With var, it remembers one live binding that later becomes 3. With let or the IIFE, each closure gets a different binding.
What closures buy you in real code
Closures are a state-management tool, not just an output puzzle.
Data privacy and modules
function createBalance() {
let value = 0;
return {
add(amount) { value += amount; },
read() { return value; }
};
}Outside code can call add and read, but it cannot directly reassign value. Both methods share the private binding.
Function factories
const makeAdder = amount => value => value + amount;
const addFive = makeAdder(5);
addFive(7); // 12addFive closes over amount = 5. A factory configures behaviour once and returns a specialised function.
Memoisation
A memoised function closes over a cache. On each call, it checks whether the input already has a result, returns the cached value if present, or computes and stores a new one. The cache survives without becoming a global variable.
Partial application
Partial application fixes some arguments now and supplies the rest later. A logger factory might capture a service name first and accept the message on each later call. This is the same factory pattern applied to multi-argument functions.
The closure memory angle
A reachable closure keeps its captured bindings alive, including objects reachable through those bindings. That is required for the counter to work, but it can retain more memory than intended.
For example, an event handler attached to a long-lived object may close over a large DOM subtree that has otherwise been removed. A permanent cache can retain closures and everything they reference. Remove listeners, clear timers, bound caches, and set obsolete references to null when their lifetime is over.
JavaScript engines can optimise uncaptured values, so the practical question is which objects remain reachable through the closure, not whether every local is always copied into a permanent object.
Three closure output questions, answered
Interviewers rarely stop at the definition. They hand you a snippet and ask what it prints. Predict each answer before you read the explanation under it.
Question 1: does a closure copy the value or follow the binding?
function outer() {
let x = 1;
function inner() {
console.log(x);
}
x = 42;
return inner;
}
outer()(); // ?It prints 42. inner captured the binding x, not the 1 that x held while the function was being created, so the reassignment before the return is what the call observes.
Question 2: what do three functions stored in an array return?
const fns = [];
for (var i = 0; i < 3; i++) {
fns.push(function () {
return i * 2;
});
}
console.log(fns[0](), fns[1](), fns[2]()); // ?It prints 6 6 6. The three functions share the single var binding, which holds 3 once the loop exits, so each one computes 3 * 2. Swap var for let and the same code prints 0 2 4.
Question 3: how much state do two counters share?
function makeTicker() {
let count = 0;
return {
inc() { return ++count; },
read() { return count; }
};
}
const a = makeTicker();
const b = makeTicker();
a.inc();
a.inc();
b.inc();
console.log(a.read(), b.read()); // ?It prints 2 1. Inside one object, inc and read share that call's count binding, so two increments on a are both visible. a and b came from separate calls to makeTicker, so they hold separate bindings.
Closure traps interviewers revisit
A
varloop gives every callback one shared function-scoped binding.A closure follows a live binding; it does not automatically snapshot a value.
Handlers created in one scope may accidentally share state.
A long-lived closure can retain a large object or detached DOM node.
thisis a separate lookup issue. Arrow functions capture lexicalthis, while ordinary functions receivethisfrom how they are called.
React interviews often connect closures to state and effect callbacks. The React interview questions for freshers provide that adjacent practice.
The short version and your next step
A closure is a function plus access to its birth scope. It tracks live bindings, which is why a counter preserves changing state and why a var loop prints 3 three times. Use let for per-iteration bindings or an IIFE to capture each current value.
Work through closure output questions in the Complete JavaScript course. KnowledgeGate carries around 70 JavaScript practice questions on this family, from scope basics to output prediction. For a wider interview route, continue through the Mera Placement Hoga bundle and the Placement Preparation category.




