Suggest an editImprove this articleRefine the answer for “The var and closures pitfall in a loop”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`var` is function scoped, not block scoped, so the whole loop shares a single `i` and every callback closes over that same variable. By the time the asynchronous callbacks run, the loop has finished and `i` holds its final value, which is why you get `3, 3, 3` instead of `0, 1, 2`. The simplest fix is `let`: a `for (let i = ...)` loop creates a fresh binding on every iteration. If you cannot change `var`, freeze the value with an IIFE, a function factory, array methods (`forEach`), or the third argument of `setTimeout`.** ```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:** a closure captures the variable itself, not a snapshot of its value, so you need a separate binding per iteration.Shown above the full answer for quick recall.Answer (EN)Image**The classic trap: in a loop declared with `var`, every callback sees the same `i`, because `var` is function scoped.** By the time those callbacks run, the loop has already finished and `i` holds its final value, so instead of the expected `0, 1, 2` the console prints `3` three times. ## Theory ### TL;DR - `var` is function scoped: the entire loop shares exactly one variable. - A closure captures the variable, not the value it had when the function was created. - An asynchronous callback reads that variable after the loop is over, when it holds the final value. - `let` in the `for` header creates a fresh binding per iteration and the problem disappears. - If `var` has to stay, freeze the value with an IIFE, a factory, `forEach`, or a `setTimeout` argument. ### Quick example ```javascript for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // 3, 3, 3 ``` ### Why it happens The `var i` declaration is hoisted to the top of the function (or to the global scope), so there is a single variable shared by all three iterations. Each callback passed to `setTimeout` closes over a reference to that one variable instead of copying its value. Then the event loop kicks in: even with a `0` delay the callback goes into the macrotask queue and runs only after the synchronous code, that is, the whole loop, has completed. At that moment `i < 3` is already false because `i === 3`. All three callbacks read the same slot and print `3`. ### Fix 1: `let` or `const` In ES6 a `for (let i = ...)` header gets a separate lexical binding on every iteration: the engine copies the current value into a new variable before the next step. Each callback then closes over its own variable: ```javascript for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); // 0, 1, 2 } ``` This is the simplest and safest route, and it is exactly why modern code does not use `var` in loops. For a `for...of` loop whose element is never reassigned, `const` works just as well. ### Fix 2: freeze the value by hand If you really do need `var`, the current iteration's value has to be moved into its own variable. An **IIFE** (immediately invoked function expression) creates a new scope and freezes the current `i`: ```javascript for (var i = 0; i < 3; i++) { (function(iCopy) { setTimeout(() => console.log(iCopy), 0); })(i); } ``` A **function factory** does the same thing more readably: the parameter `x` lives in the private environment of each created function: ```javascript function makeLogger(x) { return () => console.log(x); } for (var i = 0; i < 3; i++) { setTimeout(makeLogger(i), 0); } ``` **Array methods** (`forEach`, `map`) remove the question entirely: the callback parameter is already a separate binding per element: ```javascript [0, 1, 2].forEach(i => setTimeout(() => console.log(i), 0)); ``` **The third argument of `setTimeout`** passes the value straight into the callback (supported in browsers and in Node.js): ```javascript for (var i = 0; i < 3; i++) { setTimeout(x => console.log(x), 0, i); } ``` ### Short checklist - By default use `let` or `const` in loops, it is the simplest and safest path. - If `var` is unavoidable, freeze the value with an IIFE, a factory, or an explicit argument. - Remember: closures capture the variable, not its instantaneous value, and with `var` there is only one variable for the whole loop. ### Common mistakes - **Blaming `setTimeout`.** The timer is irrelevant: the same thing happens with any deferred call, event handler or promise callback. - **Reading a `0` delay as "immediately".** The callback still goes to the queue and runs after the synchronous code. - **Replacing `var` with `let` only inside the loop body.** The per-iteration binding comes from `let` in the `for` header, not from some variable declared deeper in the block (though a copy inside the block works too). - **Using `var` in `for...in` and `for...of` with callbacks.** The trap is identical, even though the values look "fresh". - **Confusing this with `this`.** The problem here is purely variable scope; the call site context has nothing to do with it.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.