Creating Closures Inside Loops in JavaScript

Why var breaks closures inside loops, why let fixes it, and how to replicate the fix in older code with an IIFE.

By DevStudio Online Team · Published September 3, 2026

A closure is a function that remembers the variables from the scope it was created in, even after that scope has finished running. Loops are where this trips people up most often, because var and let behave completely differently once a closure is involved.

The classic bug

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}
// logs: 3, 3, 3

You'd expect 0, 1, 2. Instead you get 3 three times. That's because var is function-scoped, not block-scoped — there's only one i for the entire loop, shared by every callback. By the time the timeouts fire, the loop has already finished and i is 3.

Fix 1: use let

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}
// logs: 0, 1, 2

let is block-scoped — the JavaScript engine creates a new binding of i for every iteration. Each closure captures its own copy, not a shared one. This is almost always the right fix in modern code.

Fix 2: an IIFE, for older code you can't touch

If you're stuck maintaining var-based code (or just want to understand what let is doing under the hood), wrap the loop body in an Immediately Invoked Function Expression to force a new scope per iteration:

for (var i = 0; i < 3; i++) {
  (function (capturedI) {
    setTimeout(() => console.log(capturedI), 0)
  })(i)
}
// logs: 0, 1, 2

Each call to the IIFE creates a fresh function scope with its own capturedI parameter, which is exactly what let does automatically.

The rule of thumb

If a closure inside a loop is capturing a loop variable, reach for let. Reach for the IIFE pattern only when you're reading or fixing legacy var-based code that can't be rewritten wholesale.

Related tool

JS Playground — try this live

More JavaScript guides