Suggest an editImprove this articleRefine the answer for “A loop with var and setTimeout”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Because of two things at once: `var` is function scoped, so the whole loop shares a single `i`, and `setTimeout` runs later, once the loop has already finished.** All the callbacks close over that same variable, go into the macrotask queue and only run after the current JavaScript stack empties, by which point `i` already holds its final value `3`. The fixes: swap `var` for `let`, which creates a fresh binding per iteration, or capture the current value with an IIFE, with the third argument of `setTimeout`, or with `bind`. ```javascript for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); // 3, 3, 3 } for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); // 0, 1, 2 } ``` **Key point:** `var` gives one shared variable for the whole loop, and the callback reads it only after the loop is over.Shown above the full answer for quick recall.Answer (EN)Image**A loop with `var` and `setTimeout` prints the same number three times because of two things at once: `var` creates a single variable for the whole loop, and the timer callbacks run only after the loop has finished.** Both causes have to coincide: on their own, neither asynchrony nor `var` produces this behaviour. ## Theory ### TL;DR - `var` is **function scoped**: the loop creates **one** `i` for the entire function, not a new one per iteration. - Every `setTimeout` callback closes over **the same** variable. - `setTimeout` runs **later**: the callbacks go into the macrotask queue. - That queue is processed only once the current JavaScript stack has emptied. - By the time the first timer fires, the loop is over and `i` holds its **final value** (`3`, for instance). - The fixes are `let`, an IIFE, the third argument of `setTimeout`, or `bind`. ### Quick example The symptom: ```javascript for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // 3 // 3 // 3 ``` ### Cause 1: `var` is function scoped In `for (var i = 0; i < 3; i++) { ... }` a **single** `i` is created for the whole function (or for the global scope), not a new one per iteration. All three arrow functions passed to `setTimeout` close not over "the value of `i` at creation time" but over the **binding itself** of that one variable. So once the loop ends, all three callbacks read the same memory slot, and it holds `3`. ### Cause 2: `setTimeout` runs later `setTimeout` callbacks do not run immediately, not even with a delay of `0`. They go into the **macrotask queue** and only start **after the current JavaScript stack has emptied**, that is, after the synchronous code (the whole loop included) has finished. The order is: 1. The loop runs to completion, calling `setTimeout` three times and scheduling three callbacks. 2. The loop ends and `i` becomes `3`. 3. The stack empties, the event loop pulls the callbacks off the queue and runs them one by one. 4. Each callback reads the current value of `i`, which is already `3`. ### How to fix it **Option 1: `let` (block scope)** ```javascript for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); // 0, 1, 2 } ``` The specification requires a `for` loop with `let` to create a **fresh binding on every iteration** and copy the previous value into it. Each callback then closes over its own copy. **Option 2: an IIFE, a closure that receives the current `i`** ```javascript for (var i = 0; i < 3; i++) { (function (j) { setTimeout(() => console.log(j), 0); // 0, 1, 2 })(i); } ``` Calling a function creates a new scope, and the parameter `j` receives a **copy** of `i` as it was at call time. **Option 3: pass it as an argument to `setTimeout`** ```javascript for (var i = 0; i < 3; i++) { setTimeout(j => console.log(j), 0, i); // 0, 1, 2 } ``` The third and later arguments of `setTimeout` are forwarded to the callback, and their values are captured when the timer is scheduled. **Option 4: `bind`** ```javascript for (var i = 0; i < 3; i++) { setTimeout(console.log.bind(null, i), 0); // 0, 1, 2 } ``` `bind` returns a new function with the argument fixed up front, so it copies the value immediately as well. ### Summary | Option | How it captures the value | | --- | --- | | `let` | A fresh binding on every loop iteration | | IIFE | A new scope on every function call | | `setTimeout` argument | The value is handed to the callback at scheduling time | | `bind` | The argument is baked into a new function straight away | > In short: `var` gives one "shared" variable for the whole loop, and `setTimeout` fires once the loop is already over. Use `let`, or isolate the value with a closure or with arguments. ### Common mistakes - Blaming the delay and switching to `setTimeout(..., 100)`. The delay is irrelevant: the callback still runs after the loop. - Believing a closure copies a variable's value. A closure keeps a reference to the binding, not a snapshot of the value. - Replacing `var` with `let` only on a declaration inside the loop body rather than in the `for` header. The per-iteration binding comes from `let` in the header. - Using `var` with `forEach` and concluding the problem "went away by itself". It went away because `forEach` calls a function per element, and that is already a separate scope. - Confusing this effect with hoisting. Hoisting explains why `i` is visible before its declaration, not why every callback sees `3`.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.