Skip to content

Latest commit

 

History

History
90 lines (65 loc) · 2.03 KB

File metadata and controls

90 lines (65 loc) · 2.03 KB

SetTimeout + Closure Interview Question

The Interview Question

for (var i = 1; i <= 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, i * 1000);
}

Expected by beginners:

1
2
3

Actual output:

4
4
4

Why This Happens

  1. var is function-scoped, not block-scoped.
  2. The loop runs completely before the first setTimeout executes (because setTimeout is asynchronous).
  3. All the callbacks share the same i variable in the same lexical environment.
  4. By the time the timeouts run, i is 4 (loop has finished).

The Closure Connection

A closure is formed when the callback function inside setTimeout remembers the i variable from the outer scope.

In this case, all three callbacks close over the same variable i, not a separate copy for each iteration.

Fixing It

Solution 1: Use let (block scope)

for (let i = 1; i <= 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, i * 1000);
}
  • let creates a new i for each iteration because it is block-scoped.

Output:

1
2
3

Solution 2: Use an IIFE (Immediately Invoked Function Expression)

for (var i = 1; i <= 3; i++) {
  (function (x) {
    setTimeout(function () {
      console.log(x);
    }, x * 1000);
  })(i);
}
  • The IIFE captures the current value of i and passes it as a parameter (x), creating a new lexical environment for each loop.

Solution 3: Use setTimeout with .bind()

for (var i = 1; i <= 3; i++) {
  setTimeout(console.log.bind(null, i), i * 1000);
}
  • bind creates a new function with i fixed as an argument.

Interview Soundbite

"In the setTimeout + closure problem, var creates a single shared variable across iterations, so by the time the async callbacks run, the loop has already finished and the variable has the final value. This happens because all callbacks close over the same lexical environment. The fix is to create a new lexical scope for each iteration using let, IIFE, or .bind()."