`var`, `let`, and `const` in loops
What are the differences between `var`, `let`, and `const` in scope, hoisting, and reassignment? What will delayed callbacks capture when you use each declaration in a loop?
what they're testing: The interviewer is probing whether you can reason about lexical bindings and predict asynchronous code without relying on memorized output.
var ignores ordinary blocks and is scoped to the containing function, module, or global script; let and const are block-scoped. All three declarations are set up before execution reaches them, but only a var binding is initialized to undefined at that point. Accessing let or const before its declaration throws a ReferenceError. That stretch of code is the temporal dead zone.
Both var and let allow reassignment. An ordinary const declaration needs an initializer, and its binding cannot be reassigned. The object behind a const can still be mutated, though. In modern code, const is a useful default when the binding will not change, while let signals expected reassignment.
Delayed callbacks close over bindings rather than taking a frozen copy of each value. In for (var i = 0; i < 3; i++), every callback shares one i, so callbacks that run after the loop read 3. A let counter gets a new binding for each iteration, giving those callbacks 0, 1, and 2. const cannot be the incremented counter, but for (const item of items) creates and initializes a fresh item binding on every iteration, so each callback sees its own item.
Why it lands
what makes the answer strong
- Explains hoisting through initialization and the temporal dead zone.
- Separates a constant binding from an immutable value.
- Uses the binding model to predict delayed callback output.
- Covers the practical `const` pattern with `for...of`.
Where people slip
answers that sound right but are wrong
- Hoisting moves the declaration and assigned value to the top, so all three can be read before their declaration.
- `let` and `const` are not hoisted at all; the name is simply out of scope before its declaration.
- A `const` object cannot be changed because `const` makes the value immutable.
- Each callback copies the loop variable's current value when it is created, regardless of how the variable was declared.
If they push further
How would you fix the `var` callback loop without changing it to `let`?
Create a new function scope for each iteration and pass the current value into it, traditionally with an IIFE. Iteration helpers such as `forEach` also give each callback its own parameter binding.
Does `const` make an object immutable?
No. It prevents reassignment of the binding, but the object's properties can still change. `Object.freeze` can make one object shallowly immutable; nested objects need to be frozen separately or handled with another immutable design.
Why do the `setTimeout` callbacks run after the loop finishes?
The loop runs synchronously on the current call stack. Timer callbacks become eligible later and cannot run until that stack is clear, so the shared `var` binding has already reached its final value.
Sources
- MDN: var ↗developer.mozilla.org
- MDN: let ↗developer.mozilla.org
- MDN: const ↗developer.mozilla.org
- MDN: Closures ↗developer.mozilla.org
- MDN: Window.setTimeout() ↗developer.mozilla.org
Now answer it yourself.
Reading a strong answer is easy. Producing one under pressure is the skill the interview tests. Gapmap grades your answer against the same bar an interviewer would.
beta
The interviewer part is in the works.
The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.