let was added to JavaScript in ES6 (2015) to fix problems with the older var keyword. It is the standard way to declare a variable whose value will change. Understanding let really means understanding scope: where in your code a variable can be seen and used.
Three rules make let different from var:
let to var and comparefor loop with let i that logs 0 to 4, then try to log i after the looplet total = 0, then let total = 50 inside an if (true) { }. Log total, then fix itlet variable on the line before you declare it. Then try the same with var. Which one would you rather debug?This lesson takes each rule in turn and shows the bug it prevents.
A block is any code wrapped in curly braces { }. You have not written many yet, but you will soon use them everywhere: every if, every loop, and every function has one.
{
// a plain block
}
if (score > 10) {
// the if block
}
for (let i = 0; i < 3; i++) {
// the loop block
}
Blocks can sit inside other blocks. Think of each block as a room: the braces are the walls.
A variable declared with let lives inside the block where it was declared. Outside those braces it does not exist:
{
let message = "inside the block";
console.log(message);
}
console.log(message);
Visibility only works one way. Code inside a block can see variables from outside it, but code outside cannot see in. Like a room with one-way glass: you can look out, nobody can look in.
let player = "Ana"; // outside any block
{
let bonus = 5; // inside the block
console.log(player, bonus); // inside can see out
}
console.log(player); // fine
console.log(bonus); // outside cannot see in
Loops are where this pays off. The counter i belongs to the loop and disappears when the loop ends, so it cannot clash with some other i later in your code:
for (let i = 0; i < 3; i++) {
console.log(i);
}
console.log(i);
Before 2015 JavaScript only had two kinds of scope: global and function. let and const added the third: block.
| Scope | Declared where? | Visible where? |
|---|---|---|
| Global | Outside every block and function | Everywhere in the script |
| Function | Inside a function | Only inside that function |
| Block | Inside any { } (let / const only) |
Only inside that block |
Function scope applies to all three keywords. Functions get their own lessons later; for now, just know that anything declared inside one stays inside it, even var:
function setup() {
var x = 1;
let y = 2;
const z = 3;
}
// x, y, and z can NOT be used here
Block scope is where var and let split. var ignores the braces of an if, a loop, or a plain block and leaks out into the surrounding code:
{
var x = 2;
}
console.log(x); // var escaped the block
{
let y = 2;
}
console.log(y); // let did not
These two words sound alike but mean different things:
score = 20; (no keyword)let score = 20; (keyword again)let allows reassigning as often as you like, but it refuses to redeclare in the same scope:
let score = 10;
score = 20; // reassign: fine
let score = 30; // redeclare: error
Notice this is a SyntaxError: JavaScript refuses to run the script at all, so the mistake is caught before any code runs. var has no such check, and a second var silently wipes out the first:
var x = 2;
var x = 3; // no error, x is now 3
In a short example that looks harmless. In a 500-line script where two different parts both happen to use a variable called total, it is a bug that gives no warning at all.
Here are the exact rules, including what happens when you mix the two keywords:
var x = 2; // Allowed
let x = 3; // Not allowed: x already exists here
{
let x = 2; // Allowed
let x = 3; // Not allowed: same block
}
{
let x = 2; // Allowed
var x = 3; // Not allowed: var would clash with the let
}
Declaring the same name with let in a different block is allowed, because each block is its own room:
let x = 2; // Allowed
{
let x = 3; // Allowed: a separate x inside this block
}
{
let x = 4; // Allowed: another separate x
}
When an inner block declares a variable with the same name as an outer one, the inner one hides the outer one until the block ends. This is called shadowing. With let, the outer variable is untouched:
let x = 10;
{
let x = 2;
console.log(x); // the inner x
}
console.log(x); // the outer x, unchanged
The same code with var gives a different answer. Because var ignores the block, the "inner" x is actually the same variable as the outer one, and it gets overwritten:
var x = 10;
{
var x = 2;
console.log(x);
}
console.log(x); // the outer x was overwritten
let. If you mean to update an outer variable but type let in front of it, you create a new inner variable instead, and the outer one never changes:
let total = 0;
if (true) {
let total = 50; // oops: a NEW total, only inside the if
}
console.log(total); // 0
Drop the keyword (total = 50;) to update the outer variable, and the log shows 50. Rule: let only goes on the line where a variable is born.
Before running your code, JavaScript scans it and notes every variable declaration. This is called hoisting, as if the declarations were lifted to the top. var and let are both hoisted, but they behave very differently if you use them too early.
A var is hoisted and set to undefined, so you can use it before its declaration line without any error:
carName = "Volvo";
var carName;
console.log(carName);
console.log(price); // used before the line below
var price = 9;
That second log is the problem: no error, just a quiet undefined that can travel a long way through your program before something breaks.
A let is hoisted but not initialized. From the top of its block until its declaration line, the variable exists but is off-limits. That stretch of code is called the temporal dead zone (TDZ). Touching the variable there throws an error:
carName = "Saab";
let carName = "Volvo";
var version keeps running with a wrong value. The fix is also simple: declare your variables at the top of the block, before you use them. Hoisting gets a full lesson later (JS Hoisting).
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function or global | Block { } |
Block { } |
| Reassign | Yes | Yes | No |
| Redeclare | Yes (silently) | No | No |
| Used before declared | undefined |
ReferenceError | ReferenceError |
Every row where var differs is a place where let turns a silent bug into a loud error. That is the whole reason modern JavaScript recommends avoiding var. The JS var/let/const lesson later revisits this with more advanced cases.
const by default. Use let when the value must change (counters, running totals, loop variables). Never use var in new code.
What does this log?
let level = 1;
{
let level = 5;
}
console.log(level);
1. The level inside the block is a separate variable that disappears when the block ends. The outer one was never touched.What does this log?
let level = 1;
{
level = 5;
}
console.log(level);
5. No keyword means reassignment. Code inside a block can see and change variables from outside it.Which line causes the error, and what kind?
let lives = 3;
lives = 2;
let lives = 1;
SyntaxError: Identifier 'lives' has already been declared. Line 2 is fine because it reassigns. Line 3 tries to redeclare. Because it is a SyntaxError, none of the script runs, not even line 1.{ let secret = 42; } into the console, then secret. Read the error. Repeat with var instead of let and comparefor loop with let i that logs 0 to 4, then try to log i after the looplet total = 0, then let total = 50 inside an if (true) { }. Log total, then fix itlet variable on the line before you declare it. Then try the same with var. Which one would you rather debug?