Understanding JavaScript Block Scope with let and const
Block scope is a fundamental JavaScript mechanism introduced in
ECMAScript 2015 (ES6) that restricts the visibility and accessibility of
variables declared with let and const to the
specific block of code in which they are defined. Unlike variables
declared with var, which are scoped to the containing
function or global execution context, let and
const ensure that variables exist only within the curly
braces ({}) enclosing them. This article explains how block
scopes work, how they handle hoisting and the Temporal Dead Zone, and
how they prevent common scope-related bugs in modern JavaScript
development.
What Defines a Block?
In JavaScript, a block is any code section delimited by a pair of
opening and closing curly braces ({ ... }). Common examples
of blocks include:
- The body of an
if,else if, orelsestatement - Loops such as
for,while, anddo...while switchstatements- Standalone blocks (curly braces placed arbitrarily in code)
Any variable declared inside these braces using let or
const cannot be accessed from outside that block.
{
let insideBlock = "Visible only here";
const alsoInside = 42;
console.log(insideBlock); // Output: Visible only here
}
console.log(insideBlock); // ReferenceError: insideBlock is not definedHow let
and const Function in Block Scope
1. Inaccessibility Outside the Block
When the JavaScript engine enters a block, it allocates memory for block-scoped variables. Once the engine exits the block, those variables are cleaned up and removed from the active lexical environment, making them completely unreachable from the parent scope.
2. Variable Shadowing
A variable declared with let or const
inside an inner block can have the same name as a variable in an outer
block. The inner variable “shadows” the outer one without overwriting
it:
let count = 10;
if (true) {
let count = 20; // Shadows outer 'count'
console.log(count); // Output: 20
}
console.log(count); // Output: 103. No Re-declaration in the Same Scope
Within the same block scope, you cannot re-declare a variable that
was declared with let or const. Doing so
results in a SyntaxError.
let user = "Alice";
let user = "Bob"; // SyntaxError: Identifier 'user' has already been declaredHowever, re-declaring the same variable name in a separate or nested block is valid because each block creates a new lexical scope.
Hoisting and the Temporal Dead Zone (TDZ)
Like var, declarations using let and
const are hoisted to the top of their containing block.
However, they are not initialized with undefined.
The time between entering the scope and the actual line of code where
the variable is declared and initialized is called the Temporal
Dead Zone (TDZ). Accessing a let or
const variable within the TDZ results in a
ReferenceError.
{
// TDZ for 'message' begins
console.log(message); // ReferenceError: Cannot access 'message' before initialization
let message = "Hello"; // TDZ ends; variable is initialized
console.log(message); // Output: Hello
}Block Scope in Loops
One of the most practical benefits of block scoping is its behavior
inside loops. When using let in a for loop, a
new lexical binding is created for every single iteration:
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // Outputs: 0, 1, 2
}, 100);
}If var were used instead, only a single global or
function-scoped i would exist, causing the timeout callback
to output 3 three times.
Difference
Between let and const Inside Blocks
While both create block-scoped identifiers, they differ in re-assignment:
let: Allows the variable’s value to be reassigned later within the same block.const: Enforces a constant reference; the variable must be initialized immediately upon declaration and cannot be reassigned. However, if aconstvariable holds an object or an array, the properties or elements within that reference can still be modified.