Introduced in ES2015, let was designed to fix one precise flaw: a variable that leaked out of the place where it was written. The word is short, the change runs deep.
In modern code there are only two declarations left to know. let is the one to reach for when the value is going to change, and only then.
Definition
let declares a reassignable variable whose life ends at the closing brace of the block containing it. A block, here, is any pair of braces: the body of a function, of an if, of a loop, or even a pair standing alone in the code.
let cart = 0;
cart = 49; // reassignment: allowed
console.log(cart); // 49What is not allowed is redeclaring the same name in the same block. A second let cart line does not merely fail at runtime: it stops the whole file from loading, with a message saying the name has already been declared. That is good news, since a duplicate like this is always a mistake.
One variable per block, one per turn
Two identical names in two different blocks are two different variables, entirely unaware of each other.
let counter = 0;
{
let counter = 10; // a different variable, in a different block
counter += 1;
}
console.log(counter); // 0The most useful consequence shows up in loops. With let, the counter of a for is recreated on every turn, so a function created during the walk keeps the value of the turn that made it. With var, they would all have shared a single counter.
const outputs = [];
for (let i = 0; i < 3; i++) outputs.push(() => i);
console.log(outputs[0](), outputs[1](), outputs[2]()); // 0 1 2The temporal dead zone
A name declared with let is known to the engine as soon as the block is entered, but it stays unusable until its declaration line. That interval has a name, the temporal dead zone, and any read inside it fails.
console.log(rate); // ReferenceError: Cannot access 'rate' before initialization
let rate = 0.2;The message is specific, and that is the whole point: it tells a variable read too early apart from a variable that does not exist at all. With var, the same line would have printed undefined and the program would have carried on with an empty value.
The word "temporal" matters. The dead zone is not about position in the file but about order of execution: a function written above the declaration can read the variable perfectly well, as long as it is called afterwards.
Frequently asked questions
Should let or const be the default?
const by default, let only when the value has to be replaced. This is not about performance, the two are equivalent there: it is about reading. Seeing let tells a reviewer that the value is going to move, and that signal vanishes if it is used everywhere.
Can a let be declared without a value?
Yes, and the variable then holds undefined until its first assignment. That is the usual pattern when the value depends on a condition that has not been evaluated yet. Note that a const has no such freedom: it demands a value on its own line.
Does let work in older browsers?
Every maintained browser has supported it for years, and the question no longer comes up in practice. On a project that must reach very old environments, a transpiler rewrites it back to var, adding whatever functions are needed to preserve block scoping.