let and const in Loops: The Closure Fix
let in a for loop fixes the classic closure bug. Here is why and how it works per iteration.
let and const in Loops: The Closure Fix
The classic var + setTimeout + closure bug is one of the most famous JS interview questions. let fixes it. Here is why.
The Bug With var
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // logs: 3, 3, 3 `` `var` is function-scoped. There is **one** `i` shared by all iterations. All three callbacks close over the same `i`. By the time they run, the loop has finished and `i` is 3. ### The Fix With `let` ```js for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // logs: 0, 1, 2 `` `let` is block-scoped. The spec says each iteration gets a **fresh binding** of `i`. Each callback closes over its own `i`, which holds the value for that iteration. ### How `let` Creates a Fresh Binding Conceptually, the engine does something like: ```js // pseudocode for let in a loop let _i = 0; while (_i < 3) { let i = _i; // fresh copy for this iteration setTimeout(() => console.log(i), 0); _i++; } `` Each iteration copies the current value into a new `i`. The closure captures that specific `i`. ### `const` in `for...of` and `for...in` ```js for (const item of [1, 2, 3]) { setTimeout(() => console.log(item), 0); } // logs: 1, 2, 3 `` `const` works here because each iteration is a fresh binding. `item` does not change within an iteration, so `const` is appropriate. ### `const` in a Classic `for` Loop ```js for (const i = 0; i < 3; i++) {} // TypeError: Assignment to constant variable `` `const` does not work for the classic `for` loop because `i++` reassigns `i`. Use `let` for the counter. ### The IIFE Fix (Pre-ES6) Before `let`, the fix was an IIFE to capture the current value: ```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 scope per iteration, capturing `i` as `j`. ### The Takeaway `let` in a `for` loop creates a fresh binding per iteration, fixing the classic closure bug. Use `let` for classic `for` loop counters. Use `const` for `for...of` and `for...in` (fresh binding per iteration, no reassignment needed).
Because let creates a fresh binding of the loop variable per iteration. Each callback closes over its own copy of i, which holds the value for that iteration. With var, all callbacks share one i, which is the final value by the time they run.
No. const i = 0; i++; throws TypeError because i++ reassigns i, and const does not allow reassignment. Use let for classic for loop counters.
Yes. Each iteration of for...of creates a fresh binding, and the loop variable does not change within an iteration. const is appropriate and preferred for for...of and for...in.
With an IIFE (immediately invoked function expression) that captured the current value of i as a parameter. Each iteration created a new function scope, so each callback closed over its own copy.
The spec says each iteration of a let-based for loop gets a new copy of the loop variable. Conceptually, the engine copies the current value into a new i at the start of each iteration, so each closure captures a distinct i.
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.
Master React
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

