Facebook Pixel

Fixing the setTimeout Closure Bug Three Ways in JavaScript

The var loop + setTimeout bug has three fixes. Here is each one with code and explanation.

Fixing the setTimeout Closure Bug Three Ways in JavaScript

The classic var loop + setTimeout bug (logging 3, 3, 3 instead of 0, 1, 2) has three standard fixes. Here is each one.

The Bug

for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // logs 3, 3, 3 `` `var` is function-scoped; all callbacks share one `i`, which is 3 by the time they run. ### Fix 1: Use `let` ```js for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // logs 0, 1, 2 `` `let` is block-scoped. Each iteration gets a fresh binding of `i`. Each callback closes over its own `i`. **Why it works**: `let` creates a new variable per iteration. The closures capture different variables, each holding the value for that iteration. **Best for**: modern code (ES6+). This is the cleanest and most readable fix. ### Fix 2: Use an IIFE ```js for (var i = 0; i < 3; i++) { (function (j) { setTimeout(() => console.log(j), 0); })(i); } // logs 0, 1, 2 `` The IIFE creates a new function scope per iteration. `i` is passed as an argument to `j`. For primitives, arguments are passed by value, so `j` is a copy. **Why it works**: each IIFE call creates a new `j` with the current value of `i`. The closure captures `j`, not `i`. **Best for**: old codebases that do not support `let`. Also useful if you need to capture additional values per iteration. ### Fix 3: Use `setTimeout`'s Extra Arguments ```js for (var i = 0; i < 3; i++) { setTimeout((j) => console.log(j), 0, i); } // logs 0, 1, 2 `` `setTimeout` accepts extra arguments after the delay. These are passed to the callback. `i` is passed as `j` (by value for primitives). **Why it works**: each `setTimeout` call captures the current `i` as the `j` argument. The callback receives `j`, not `i`. **Best for**: when you want a minimal fix without changing the loop structure or adding an IIFE. ### Comparison | Fix | Mechanism | Best For | |---|---|---| | `let` | Fresh binding per iteration | Modern code (cleanest) | | IIFE | New function scope, pass-by-value | Old codebases, extra capture | | `setTimeout` args | Extra args passed to callback | Minimal change | ### Which to Use? - **Modern code**: `let`. It is the cleanest, most readable, and requires no extra nesting. - **Old codebases**: IIFE. It works in pre-ES6 environments. - **Quick fix**: `setTimeout` extra arguments. Minimal change to existing code. ### The Takeaway The `setTimeout` closure bug has three fixes: `let` (fresh binding per iteration, best for modern code), IIFE (new function scope with pass-by-value, best for old codebases), and `setTimeout` extra arguments (minimal change). Use `let` in new code.

Use let (fresh binding per iteration), use an IIFE (new function scope with pass-by-value parameter), or use setTimeout's extra arguments (pass the current value as an argument to the callback).

let. It is the cleanest and most readable. It creates a fresh binding per iteration without extra nesting or arguments. Use it in all ES6+ code.

The IIFE creates a new function scope per iteration and passes i as a parameter (j). Primitives are passed by value, so j is a copy of i at that iteration. The closure captures j, not i.

setTimeout(fn, delay, ...args) passes the extra arguments to the callback. Each call captures the current value of i as an argument. The callback receives j (a copy), not the shared i.

let is block-scoped and creates a fresh binding per iteration. Each callback closes over its own i. var is function-scoped; all callbacks share one i, which is the final value by the time they run.

Ready to master React completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.