Lesson 11 of 80
Three ways to declare a variable, and they disagree on the two things that matter most: what scope they live in, and whether you can touch them before their line runs.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 2
A `for (var i = 0; ...)` loop schedules three `setTimeout` callbacks that each log `i`. Why do they all log the same final value?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
var, let, and const all create variables, but they disagree on scope (where the variable is visible) and re-declaration/reassignment rules. Get the scope difference wrong and you get the classic loop bug; get reassignment wrong and the engine just throws.
| | Scope | Re-declare? | Reassign? | Hoisted into |
|---|---|---|---|---|
| var | function | yes | yes | undefined |
| let | block | no | yes | TDZ |
| const | block | no | no | TDZ |
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// logs: 3, 3, 3 — one shared `i`, function-scoped to the whole loop
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 0);
}
// logs: 0, 1, 2 — a fresh block-scoped `j` per iteration
var is scoped to the enclosing function (or global, if there is none) — a for loop's braces don't create a new scope for it, so all three callbacks close over the same i. let is scoped to the loop's block, and the spec gives each iteration its own fresh binding, so each callback closes over a different j.
for (var i...) — var is function-scoped. There's only ONE i for the entire loop.
var in a loop plus an async callback is exactly the scenario where "why do they all log the same value" comes from. let's per-iteration binding fixes it without a manual IIFE.const freezes the binding, not the value. Objects and arrays declared with const are still fully mutable; only reassigning the variable itself throws.let/const in the same block is a SyntaxError, not a silent overwrite — var allows it (and silently ignores the redundant declaration)."Why does a var loop log the same number three times, and how does let fix it?" is one of the most common JS fundamentals questions — it directly tests whether you understand scope, not just syntax preference.