Question 12 / 31

Why do all three setTimeout callbacks log 3, not 0, 1, 2?

Topic: ClosuresDifficulty: Advanced

var creates one shared binding for the whole loop, so every callback closes over the same final value.

Solve it with the execution visualizerStep through every binding, function call, and console output below.

Predict the output

Predict the output. Does each loop iteration give its callback a private i, or do all three share one?

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100);
}
Reveal the answer and explanation

Expected output

3
3
3

Why this happens

var i is declared once for the entire loop, not once per iteration, so all three callbacks passed to setTimeout close over that exact same i binding.

The loop runs to completion synchronously, incrementing the shared i to 3 and registering all three callbacks in the timer task queue before any of them can run.

Once the synchronous code finishes, the queued callbacks run one by one. Each looks up i in the shared outer scope and finds the same final value, 3.

What this question tests

  • var is function-scoped, not block-scoped
  • closures capture bindings, not snapshots of values
  • queued callbacks run only after synchronous code finishes

Runtime assumptions

The visualizer treats setTimeout as a macrotask that always runs after the current synchronous code completes, matching Node.js, browsers, and other standard JavaScript runtimes. Replacing var with let would give each iteration its own binding and print 0, 1, 2 instead.

Specification references

JavaScript Code EditorDirectly edit or paste custom code
Step 0 of 0
←→StepSpaceRun
Press → (Step) or Space (Run)
Phase 1: Compilation(Hoisting & Memory Setup)
➔
Phase 2: Execution(Line-by-line Evaluation)
Execution Step Breakdown

Press "→" / click "▶" to step line-by-line, or press "Space" / click "▶ Run" for auto-play.