Why can a loop variable unexpectedly still be available after a loop—or make every delayed callback print the same value? In JavaScript, `let` declares a variable scoped to its containing block, rather than to the containing function as `var` does. A block is the region between `{` and `}`; a free-standing pair of braces can create one, too. section 4.0 section 4.1
A variable that stays inside its block
`if (true) { let message = 'hello'; console.log(message); }` prints `hello`. A following `console.log(message)` throws a reference error: the binding does not extend beyond the `if` block. In `for (let i = 0; i < 5; i++)`, `i` is likewise available in the loop body but not after the loop, even though its declaration appears in the loop clause. section 4.1
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 1);
};
for (let n = 0; n < 5; n++) {
setTimeout(function () {
console.log(n);
}, 1);
};
Compare the two delayed-callback loops. The `var` loop’s callbacks read its shared, function-scoped iterator after the loop finishes, so each prints `5`. The `let` loop gives each iteration its own value, producing `0` through `4`. section 4.0 section 4.1
Block scope can also keep working data out of surrounding code. Declare `data` inside a free-standing block and assign `read` and `write` functions there. Those functions can continue using `data`, while code outside the block cannot reference it directly. With `var`, an immediately invoked function expression (IIFE) can provide a similar boundary; `let` often lets the block itself do that job. section 4.1
In scope does not mean ready to read
`{ console.log(value); let value = 2; }` throws a reference error at the first line. The `let` binding belongs to the entire block, but reading it before its declaration falls in the temporal dead zone. By contrast, reading a `var` binding before its declaration yields `undefined`. An inner `let` also shadows an outer variable with the same name throughout the inner block; an early read does not fall back to the outer value. section 4.2
Use `let` when a variable needs to change but should remain local to a block. Its predeclaration error can expose a mistake that `var` would leave as an `undefined` read. Do not blindly replace every `var` in existing code: changed scope and early-read behavior can introduce errors. section 4.3
(function (namespace) {
var DEFAULT_START = 0;
var DEFAULT_STEP = 1;
var range = function (start, stop, step) {
var arr = [];
if (!step) {
step = DEFAULT_STEP;
}
if (!stop) {
stop = start;
start = DEFAULT_START;
}
if (stop < start) {
(function () {
// reverse values
var tmp = start;
start = stop;
stop = tmp;
}());
}
(function () {
var i;
for (i = start; i < stop; i += step) {
arr.push(i);
}
}());
return arr;
}
namespace.range = range;
}(window.mylib));
Practice by rewriting the range function with `let` so the temporary swap value and loop iterator stay local without their surrounding IIFEs. Check which declarations still need to be visible throughout the function. section 4.4

