Closures are the most tested topic in JS interviews — and the output questions around them are deceptively simple until you understand exactly when a variable's value is captured. Ten questions covering the classic loop problem, stale references, and private state.
Q1 — The classic var loop
Loading editor...
Show answer
Output:
3
3
3
Why: All three setTimeout callbacks close over the same i variable — there is only one i because var is function-scoped. By the time the callbacks run (after the loop finishes), i is 3. Each callback logs the current value of i, which is 3 for all of them.
Q2 — Fix with let
Loading editor...
Show answer
Output:
0
1
2
Why: let in a for loop creates a new binding for each iteration. Each callback closes over its own copy of i — not a shared variable. When the callbacks run, each one reads its own frozen value (0, 1, 2).
Q3 — Fix with IIFE
Loading editor...
Show answer
Output:
0
1
2
Why: The IIFE is called immediately on each iteration, passing the current value of i as j. Each callback closes over its own j parameter (a new variable in each IIFE invocation), not the shared i. This was the standard pre-let workaround.
Q4 — Stale closure reference
Loading editor...
Show answer
Output:
2
3
Why: Both increment and getCount close over the same count variable in the outer makeCounter scope — they share live access to it, not a snapshot. snap is just another reference to the same getCount function. Calling snap() after a third increment() still reads the current value of count, which is now 3.
Q5 — Closure over a reassigned variable
Loading editor...
Show answer
Output:
updated
Why: read closes over the variable value itself, not the string "original". When value is reassigned to "updated" before read() is called, the closure sees the new value. Closures capture variables, not values — this is the root cause of the classic loop problem too.
Q6 — Each call gets its own scope
Loading editor...
Show answer
Output:
8
13
false
Why: Each call to makeAdder creates a brand new execution context with its own x binding. add5 closes over x = 5; add10 closes over x = 10. They are entirely independent function objects — add5 === add10 is false.
Q7 — Closure in an object method
Loading editor...
Show answer
Output:
42
84
Why: Both reveal and double are closures over the same secret variable. double mutates it; reveal reads it. Since they share the same lexical scope, changes made by double are immediately visible to reveal. secret is private — it can't be accessed directly from outside createObj.
Q8 — Loop with array push
Loading editor...
Show answer
Output:
3
3
3
Why: Same root cause as Q1 — all three arrow functions close over the single var i. The loop runs to completion (i becomes 3) before any function is called. All three calls return 3. Change var to let and you get 0, 1, 2.
Q9 — Closure lifespan
Loading editor...
Show answer
Output:
11
12
11
Why: Each call to outer() creates an independent closure environment with its own x. fn1 and fn2 do not share x. fn1() called twice increments its own x from 10 to 11, then 12. fn2() starts its own x fresh at 10, increments to 11.
Q10 — Immediately-returned closure
Loading editor...
Show answer
Output:
1
2
3
Why: The IIFE runs once, creating a count variable and returning the inner function. result holds that inner function. Each call to result() increments and returns count. Because the IIFE has already run, there's no way to reset count from outside — it's private state managed by the closure. This is the module pattern.
Key Rules
| Pattern | What the closure captures |
|---|---|
var in a loop | One shared variable — all callbacks see the final value |
let in a loop | A new binding per iteration — each callback sees its own value |
| IIFE workaround | Parameter of the IIFE is a new variable — same effect as let |
| Closure over outer var | The variable itself, not a snapshot — reassignment is visible |
| Multiple closures, same scope | All share the same live variable |
| Multiple calls to outer fn | Each call creates an independent scope |
Go Deeper
- JS Foundations #3 — Closures & Lexical Scope — how closures work at the memory level
- Output Quiz #1 — Scope, Hoisting & the TDZ — the previous quiz
- Output Quiz #3 — The Event Loop & Task Ordering — the next quiz
