Modern JavaScript with Let Declaration: Block Scope and ES6 Explained

Modern JavaScript with Let Declaration: Block Scope and ES6 Explained

Understand Block Scope, Temporal Dead Zone, and Why Let Solved Legacy Var Bugs

Author
Nguyen Bao Huy
17:03:00 08/02/2026
4 min read
0 comments

Quick Answer:

Introduced in ECMAScript 2015 (ES6), the let keyword declares mutable variables with block scope ({}). Variables declared with let:

  • Cannot be accessed outside the specific block {} where they are created.
  • Cannot be accidentally re-declared within the same scope.
  • Throw a ReferenceError if accessed before initialization due to the Temporal Dead Zone (TDZ).Use let whenever you need a variable whose value must change over time, such as loop counters or state toggles.

1. Why Was let Introduced? (The Scope Leak Problem)

Before ES6 was released in 2015, JavaScript only offered the var keyword. Variables declared with var had only two types of scope: Global Scope and Function Scope.

Crucially, regular curly brackets {}—such as those inside if statements, switch blocks, and for loops—did not create a new scope for var.

The Bug Caused by var:

javascript
var discount = 10;

if (true) {
  // Developer intended to define a local discount inside this block:
  var discount = 50;
  console.log("Inside block:", discount); // Output: 50
}

// Unexpected: The block leaked and overwrote the outer variable!
console.log("Outside block:", discount); // Output: 50

Because var ignored the {} boundary, it silently modified the outer discount variable, creating hard-to-find bugs in larger applications.

2. How Block Scope Works with let

The let keyword confines a variable strictly to the block {} in which it was declared:

javascript
let basePrice = 100;

if (true) {
  let temporaryFee = 15;
  console.log("Total inside:", basePrice + temporaryFee); // Output: 115
}

// Trying to access temporaryFee outside its block:
console.log(temporaryFee); 
// Uncaught ReferenceError: temporaryFee is not defined

Any pair of curly braces creates an isolated scope for let, including standalone blocks:

javascript
{
  let secretKey = "api_key_123";
  console.log(secretKey); // "api_key_123"
}

// secretKey is completely inaccessible here:
// console.log(secretKey); // ReferenceError

3. Real-World Scenario: Fixing the Classic Loop Bug

One of the most famous JavaScript interview traps involves creating asynchronous callbacks inside a loop.

The Buggy Way with var:

javascript
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log("var index:", i);
  }, 100);
}
// Output after 100ms:
// var index: 3
// var index: 3
// var index: 3

Why this fails: There is only one shared i variable across the entire function. By the time the timer executes, the loop has finished and i equals 3.

The Clean Solution with let:

javascript
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log("let index:", i);
  }, 100);
}
// Output after 100ms:
// let index: 0
// let index: 1
// let index: 2

Why this works: For every single iteration of the loop, JavaScript creates a new block-scoped binding for let i. Each timer callback captures its own unique value of i.

4. No Accidental Re-declarations

In large programs, two developers (or different sections of the same file) might accidentally use the same variable name.

var Fails Silently:

javascript
var activeUser = "Alice";
// Hundreds of lines later...
var activeUser = "Bob"; // Overwrites Alice without any warning!
console.log(activeUser); // "Bob"

let Protects Your Code:

javascript
let activeUser = "Alice";
let activeUser = "Bob"; 
// Uncaught SyntaxError: Identifier 'activeUser' has already been declared

Scope Shadowing (Allowed and Safe)

You can declare a variable with the same name if it lives inside an inner child block. This is known as shadowing, and it will not overwrite the outer variable:

javascript
let score = 10; // Outer variable

{
  let score = 99; // Shadowed local variable
  console.log("Inner score:", score); // 99
}

console.log("Outer score:", score); // 10 (Preserved intact!)

5. Let Hoisting & The Temporal Dead Zone (TDZ)

A common misconception is that let variables are not hoisted. In reality, both var and let are hoisted (recognized by the JavaScript compiler during the initial creation phase).

The difference lies in how they are initialized:

  • var is hoisted and immediately initialized with undefined.
  • let is hoisted, but not initialized. It remains completely unreadable until the execution line reaches its declaration.

The phase between entering the scope and reaching the declaration line is known as the Temporal Dead Zone (TDZ).

javascript
// Example A: Accessing var before declaration
console.log(legacyName); // Returns undefined (confusing and risky)
var legacyName = "Product A";

// Example B: Accessing let before declaration (Inside the TDZ)
console.log(modernName); 
// Uncaught ReferenceError: Cannot access 'modernName' before initialization
let modernName = "Product B";

The TDZ ensures you cannot read uninitialized variables, catching typos and ordering mistakes immediately during development.

Quick Comparison: let vs. var

Featurevar (Legacy)let (Modern Standard)
Scope TypeGlobal or Function scopeBlock scope ({})
Re-declarationAllowed in same scope (Risky)Prohibited (Throws SyntaxError)
Re-assignmentAllowedAllowed
Before DeclarationEvaluates to undefinedThrows ReferenceError (TDZ)
Global Object BindingBinds to window.varNameDoes not bind to window

Summary Checklist

  • Always prefer const by default; switch to let only when you anticipate the value changing (such as counters, filters, or accumulators).
  • Never use var in modern JavaScript applications.
  • Use block scope {} to prevent variables from leaking into surrounding code.
  • Remember that variables declared with let cannot be accessed before the line where they are declared.

First Lesson

You are at the beginning of this curriculum.

Latest Tutorial

More chapters coming soon to this topic.

Author avatar

Nguyen Bao Huy

Lead Fullstack & AI Solutions Engineer

Specializing in Next.js App Router, React 19, TypeScript, and modern design systems. Passionate about creating seamless user experiences.

Discussion

0

No comments yet

Be the first to share your thoughts, question a concept, or provide additional tips!

Leave a Reply

Share your insights, questions, or solutions with the developer community.

Your avatar
0 / 500 characters