In early JavaScript (ES5 and earlier), variable scoping was exclusively determined by functions. With the arrival of ECMAScript 2015 (ES6), block scoping was introduced via let and const. Understanding how block scopes, lexical environments, and the Temporal Dead Zone (TDZ) function under the hood is fundamental to intermediate JavaScript mastery.
1. Scoping Evolution: Function Scope vs. Block Scope
A block in JavaScript is defined as any pair of matching curly braces { ... }:
Inside if / else statements
Inside for, while, and do...while loops
Inside standalone blocks { ... }
JavaScript
1// Function Scope (Legacy var):
2functiontestVar(){
3if(true){
4var greeting ="Hello from var";
5}
6 console.log(greeting);// "Hello from var" - Leaked outside the if block!
7}
8
9// Block Scope (Modern let & const):
10functiontestLetConst(){
11if(true){
12let message ="Hello from let";
13const pi =3.14159;
14}
15 console.log(message);// ReferenceError: message is not defined!
16 console.log(pi);// ReferenceError: pi is not defined!
17}
2. The Classic Closure in Loops Problem
Consider this classic frontend interview challenge:
JavaScript
1// ❌ BROKEN WITH VAR:
2for(var i =0; i <3; i++){
3setTimeout(()=>{
4 console.log(i);
5},100);
6}
7// Outputs: 3, 3, 3! (Because var i is shared across the entire function scope!)
Why let Solves This Elegantly:
In JavaScript engines, let inside a for loop header creates a brand new lexical binding for every single iteration:
JavaScript
1// ✅ FIXED WITH LET:
2for(let i =0; i <3; i++){
3setTimeout(()=>{
4 console.log(i);
5},100);
6}
7// Outputs: 0, 1, 2! (Each timeout closure captures its own private block-scoped `i`!)
3. The Temporal Dead Zone (TDZ) Deep Dive
Many developers mistakenly believe that let and const are not hoisted. They ARE hoisted, but unlike var (which is initialized with undefined), let and const remain uninitialized in the Temporal Dead Zone (TDZ):
JavaScript
1// Proof of TDZ hoisting:
2let value ="outer";
3
4functiondemonstrateTDZ(){
5// If 'let value' was not hoisted, this would log "outer".
6// Because it IS hoisted to this block scope, accessing it before initialization throws!
7 console.log(value);// ReferenceError: Cannot access 'value' before initialization!
8let value ="inner";
9}
10
11demonstrateTDZ();
4. Shadowing Variables
Variable shadowing occurs when a variable declared within an inner block scope has the same identifier as a variable in an outer scope:
JavaScript
1const userRole ="viewer";// Outer scope
2
3functioncheckAccess(){
4if(true){
5const userRole ="admin";// Shadows outer userRole inside this block!