SKU // 09

JavaScript Let

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:

  1. Click Open Playground and run it. Read the error. Then change let to var and compare
  2. Write a for loop with let i that logs 0 to 4, then try to log i after the loop
  3. Recreate the "accidental let" bug: an outer let total = 0, then let total = 50 inside an if (true) { }. Log total, then fix it
  4. Log a let 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.

// What Is a Block?

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.

// Block Scope

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);
inside the block ReferenceError: message is not defined

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
Ana 5 Ana ReferenceError: bonus is not defined

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);
0 1 2 ReferenceError: i is not defined

// Three Kinds of Scope

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
2 ReferenceError: y is not defined

// Reassign vs Redeclare

These two words sound alike but mean different things:

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
SyntaxError: Identifier 'score' has already been declared

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
}

// Same Name, Different Block

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
2 10

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
2 2
Watch out: the accidental let
Shadowing can also bite you with 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.

// Hoisting and Declare-Before-Use

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;
Volvo undefined

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";
ReferenceError: Cannot access 'carName' before initialization
Why an error is better
It seems stricter, but the error is doing you a favor. It stops on the exact line with the problem and tells you what is wrong. The 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).

// var vs let vs const

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.

Rule of Thumb
Use const by default. Use let when the value must change (counters, running totals, loop variables). Never use var in new code.

// Quick Check

What does this log?

let level = 1;
{
  let level = 5;
}
console.log(level);
Show answer
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);
Show answer
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;
Show answer
Line 3: 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.

// Try It

  1. Type { let secret = 42; } into the console, then secret. Read the error. Repeat with var instead of let and compare
  2. Write a for loop with let i that logs 0 to 4, then try to log i after the loop
  3. Recreate the "accidental let" bug: an outer let total = 0, then let total = 50 inside an if (true) { }. Log total, then fix it
  4. Log a let variable on the line before you declare it. Then try the same with var. Which one would you rather debug?
// Video Walkthroughs
Last updated: Oct 9, 2026, 11:52 PM ET