for (var i = 1; i <= 2; i++) {
setTimeout(function() { alert(i) }, 100);
}When using var to declare variables, variables are hoisted to the top of the function scope. During loop execution, although multiple timer callback functions are created, they all share reference to the same i variable. Since JavaScript operates in a single-threaded environment, the loop completes execution before any timer callbacks begin, by which time i has already reached the value 3.
More specifically, when the loop finishes, i equals 3, and only then do all timer callback functions start executing, each accessing the same i variable that now holds the value 3. This explains why both alerts display 3.
This is one of the most common JavaScript closure interview questions.
Code
for (var i = 0; i < 3; i++) {setTimeout(() => console.log(i), 100);}
Output
333
Step 1: Understanding var Scope
var is function-scoped, not block-scoped.
So the loop does not create a new i for each iteration.
Think of it like:
var i;for (i = 0; i < 3; i++) {setTimeout(() => console.log(i), 100);}
There is only one variable i shared by all iterations.
Step 2: What happens during the loop?
Iteration 1
i = 0
A callback is scheduled:
() => console.log(i)
The callback doesn't run immediately.
Iteration 2
i = 1
Another callback is scheduled.
Iteration 3
i = 2
Third callback is scheduled.
Loop ends
After the loop finishes:
i = 3
Now all three callbacks are waiting in the timer queue.
Step 3: Closure
Each callback forms a closure.
A closure remembers the variable, not its value.
All callbacks reference the same i.
Visualization:
Callback 1 ---> iCallback 2 ---> iCallback 3 ---> i
All point to the same variable.
After the loop:
i = 3
So when the callbacks execute:
console.log(i); // 3console.log(i); // 3console.log(i); // 3
Output:
333
Scope Diagram
Global Scope│├── i = 3│├── Callback 1 ----┐├── Callback 2 ----┼──► same i├── Callback 3 ----┘
Because var creates only one variable, all closures share it.
How let Changes Things
for (let i = 0; i < 3; i++) {setTimeout(() => console.log(i), 100);}
Output:
012
Why?
let is block-scoped.
For every iteration JavaScript creates a new binding:
Iteration 1 -> i = 0Iteration 2 -> i = 1Iteration 3 -> i = 2
Closures now capture different variables:
Callback 1 ---> i(0)Callback 2 ---> i(1)Callback 3 ---> i(2)
So the output becomes:
012
How to get 0 1 2 using var
Before let existed, developers used an IIFE (Immediately Invoked Function Expression):
for (var i = 0; i < 3; i++) {(function(j) {setTimeout(() => console.log(j), 100);})(i);}
Output:
012
Here each function call creates a new scope and j stores the current value of i.
Key takeaway
var→ function scope → one shared variable →3 3 3let→ block scope → new variable per iteration →0 1 2Closures capture variables (references to bindings), not snapshots of their values.