JS Fundamentals #16.1 var vs let inside loop and closures
1. Lexical Environment (LE) Basics
A lexical environment is a container where JS stores variable bindings and a reference to its outer environment.
New LE creation is triggered by:
Function execution → each function call gets a new LE.
Block scope with
let/const→ each{}with block-scoped variables creates a new LE.
vardoes NOT trigger a new LE for blocks; it is function-scoped.
2. Loops and Lexical Environments
Using var:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
var iis function-scoped.The for loop does NOT create a new LE per iteration.
All iterations share the same
ibinding in the function/global LE.When
setTimeoutcallbacks execute, they all refer to this singlei, which has the final loop value.
Result with var: All callbacks print the same value (e.g., 3 if the loop ended at 3).
Using let:
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
let iis block-scoped.ES6 specification creates a new lexical environment for each iteration of the loop.
- Each iteration’s
iis a new binding.
- Each iteration’s
Each
setTimeoutcallback closes over its iteration’s LE, so it remembers the correct value ofi.
Result with let: Callbacks print 0, 1, 2 respectively.
3. How setTimeout interacts with LEs
When the callback function is created, the JS engine attaches a hidden
[[Environment]]pointing to the LE where it was created.When the callback executes later (asynchronously), JS looks up variables via the scope chain starting from that
[[Environment]].Key difference:
With
var: single shared LE → all callbacks see the samei.With
let: separate LE per iteration → callbacks see the correcti.
✅ Summary Table
| Feature | var | let |
| Scope | Function/global | Block |
| LE creation per iteration | ❌ no | ✅ yes |
| Loop iteration variable | Shared binding | New binding per iteration |
| Closure behavior in async callbacks | All callbacks share same value | Each callback remembers its own value |
Key Takeaway:
The creation of a new lexical environment is triggered by block-scoped variables (
let/const). In loops, this ensures each iteration has a fresh binding, allowing closures likesetTimeoutcallbacks to “remember” the correct variable value.vardoesn’t do this because it’s function-scoped, so all iterations share the same binding.