Closures in Loops
Closures created inside loops can capture different bindings depending on whether the loop uses `let`, `const`, or `var`. The key interview distinction is that `let` and `const` provide per-iteration bindings, while `var` uses one function-scoped binding shared by the callbacks.
A closure is a function together with access to variables from its surrounding lexical scope. When a loop creates callbacks that run later, those callbacks retain access to the variables they captured even after the loop has finished.
With `let` or `const` in a `for` loop, JavaScript creates a distinct binding for each iteration. A callback created during one iteration therefore observes that iteration's value when it later executes.
With `var`, the loop variable is function-scoped rather than block-scoped. The iterations reuse the same binding, so callbacks created in the loop can all observe the final value after the loop completes.
This difference commonly appears with event handlers, timers, promises, and other asynchronous callbacks. A practical fix for legacy `var` code is to use `let` or `const`, or explicitly create a separate scope when that is appropriate.
Key points
- `let` and `const` in a `for` loop provide per-iteration bindings for closures.
- A `var` loop variable is function-scoped and can be shared by callbacks from every iteration.
- Closures capture bindings, not simply a frozen copy of the current value.
- The issue is especially visible when callbacks execute after the loop has completed.
- Prefer `let` or `const` for modern JavaScript loop variables.
Interview questions
How would you handle a legacy loop that must use `var` but needs independent callback values?
One option is to create a new function scope for each iteration and pass the current value as an argument. An immediately invoked function expression was a common pre-ES6 technique for doing this. In modern code, replacing `var` with `let` is generally simpler when the surrounding constraints allow it.
What happens when you create callbacks inside a `for` loop using `var`?
The callbacks share the same function-scoped loop variable. If they execute after the loop finishes, they generally all observe the final value assigned to that variable. This happens because the closures retain access to one shared binding rather than receiving independent copies.
Why does replacing `var` with `let` usually fix a loop-closure bug?
`let` is block-scoped and, for the loop variable of a `for` loop, JavaScript provides a distinct binding for each iteration. Each callback therefore retains access to the binding associated with its own iteration. When the callbacks execute later, they can read different values.
Do closures capture values or variables?
Conceptually, a closure retains access to lexical bindings from its surrounding scope rather than automatically taking a frozen snapshot of every value. If the binding is changed later, a closure can observe that change when it reads the binding. In a loop, whether callbacks share a binding or receive distinct iteration bindings determines what they observe.
Why is this issue important in frontend applications?
Frontend code frequently creates callbacks in loops for event listeners, timers, promises, and other deferred work. If those callbacks share an unintended binding, user interactions or asynchronous results can be associated with the wrong iteration. Understanding lexical scoping makes the bug predictable and helps distinguish a closure problem from unrelated event or scheduling issues.