let and const Block Scope in JavaScript
Block Scope Basics
Block scope defines the accessibility of variables within a block of code (enclosed by curly braces {}
). Before ES6, JavaScript only had function scope and global scope, but let
and const
introduced true block scoping.
var vs let vs const
var
- Function-scoped or globally-scoped
- Hoisted (declaration moved to the top)
- Can be redeclared
- Can be updated
function varExample() {
var x = 1;
if (true) {
var x = 2; // Same variable, redeclared
console.log(x); // 2
}
console.log(x); // 2 (value changed)
}
let
- Block-scoped
- Hoisted but not initialized (Temporal Dead Zone)
- Cannot be redeclared in same scope
- Can be updated
function letExample() {
let x = 1;
if (true) {
let x = 2; // Different variable, scoped to the block
console.log(x); // 2
}
console.log(x); // 1 (unchanged)
}
const
- Block-scoped
- Hoisted but not initialized (Temporal Dead Zone)
- Cannot be redeclared in same scope
- Cannot be reassigned
- Object properties can still be modified
function constExample() {
const x = 1;
if (true) {
const x = 2; // Different variable, scoped to the block
console.log(x); // 2
}
console.log(x); // 1 (unchanged)
// x = 3; // TypeError: Assignment to constant variable
}
Block Scope Examples
let in Blocks
// Block scope with let
{
let blockScoped = 'I am block-scoped';
console.log(blockScoped); // 'I am block-scoped'
}
// console.log(blockScoped); // ReferenceError: blockScoped is not defined
// let in if statements
if (true) {
let ifScoped = 'Only available in this if block';
console.log(ifScoped); // 'Only available in this if block'
}
// console.log(ifScoped); // ReferenceError: ifScoped is not defined
// let in for loops
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
// console.log(i); // ReferenceError: i is not defined
const in Blocks
// Block scope with const
{
const PI = 3.14159;
console.log(PI); // 3.14159
}
// console.log(PI); // ReferenceError: PI is not defined
// const with objects
{
const user = { name: 'John', age: 30 };
console.log(user.name); // 'John'
// Can modify properties
user.name = 'Jane';
console.log(user.name); // 'Jane'
// Cannot reassign the variable
// user = { name: 'Bob' }; // TypeError: Assignment to constant variable
}
Temporal Dead Zone (TDZ)
The TDZ is the period between entering a scope where a variable is declared and the actual declaration.
// Temporal Dead Zone demonstration
function tdz() {
// TDZ for x starts here
console.log(y); // undefined (var is hoisted with initialization)
// console.log(x); // ReferenceError: Cannot access 'x' before initialization
var y = 1; // y is hoisted
let x = 2; // TDZ ends for x
console.log(x); // 2
console.log(y); // 1
}
Function Scope vs Block Scope
// Function scope with var
function functionScope() {
var functionScoped = 'I am function-scoped';
if (true) {
var alsoFunctionScoped = 'I am also function-scoped';
}
console.log(functionScoped); // 'I am function-scoped'
console.log(alsoFunctionScoped); // 'I am also function-scoped'
}
// Block scope with let/const
function blockScope() {
let blockScoped = 'I am block-scoped';
if (true) {
let onlyInIf = 'I am only available in this if block';
const alsoOnlyInIf = 'I am also only in this if block';
console.log(blockScoped); // 'I am block-scoped' (accessible from parent scope)
console.log(onlyInIf); // 'I am only available in this if block'
console.log(alsoOnlyInIf); // 'I am also only in this if block'
}
console.log(blockScoped); // 'I am block-scoped'
// console.log(onlyInIf); // ReferenceError: onlyInIf is not defined
// console.log(alsoOnlyInIf); // ReferenceError: alsoOnlyInIf is not defined
}
Loop Scope with let
// var in loops (problematic)
function varLoop() {
const actions = [];
for (var i = 0; i < 3; i++) {
actions.push(function() {
console.log(i);
});
}
actions.forEach(action => action());
// Logs: 3, 3, 3 (all functions reference the same variable)
}
// let in loops (correct)
function letLoop() {
const actions = [];
for (let i = 0; i < 3; i++) {
actions.push(function() {
console.log(i);
});
}
actions.forEach(action => action());
// Logs: 0, 1, 2 (each iteration gets its own i)
}
Global Variables with let and const
// var creates properties on the global object
var globalVar = 'I am a global var';
console.log(window.globalVar); // 'I am a global var' (in browsers)
// let and const do not create properties on the global object
let globalLet = 'I am a global let';
const globalConst = 'I am a global const';
console.log(window.globalLet); // undefined
console.log(window.globalConst); // undefined
Redeclaration Rules
// var can be redeclared
var x = 1;
var x = 2; // Valid
console.log(x); // 2
// let cannot be redeclared in the same scope
let y = 1;
// let y = 2; // SyntaxError: Identifier 'y' has already been declared
// const cannot be redeclared in the same scope
const z = 1;
// const z = 2; // SyntaxError: Identifier 'z' has already been declared
// Different scopes allow redeclaration
function scopeTest() {
let scopedVar = 'outer';
console.log(scopedVar); // 'outer'
if (true) {
let scopedVar = 'inner'; // Valid - different scope
console.log(scopedVar); // 'inner'
}
console.log(scopedVar); // 'outer'
}
Best Practices
When to Use let
- For variables that will be reassigned
- Loop counters and iterators
- Variables that change over time
let count = 0;
count++; // Valid
let currentUser;
if (isLoggedIn) {
currentUser = getUser();
}
When to Use const
- For values that should not be reassigned
- Most variable declarations (prefer const by default)
- Object and array declarations (even if their contents will change)
const API_URL = 'https://api.example.com';
const MAX_ITEMS = 100;
const user = { name: 'John' };
user.name = 'Jane'; // Valid - modifying properties
const numbers = [1, 2, 3];
numbers.push(4); // Valid - modifying array
// numbers = [5, 6]; // Invalid - reassigning variable
Avoiding var
In modern JavaScript, it’s generally recommended to avoid var
and use let
and const
instead:
var
doesn’t respect block scopevar
can lead to unexpected behavior due to hoistingvar
allows redeclaration, which can lead to bugs
Common Patterns
Immediate Block Scope
// Create a temporary scope
{
const temporary = performHeavyComputation();
processSomething(temporary);
// temporary is not accessible outside this block
}
// No memory leak or pollution of outer scope
Switch Case Blocks
switch (value) {
case 1: {
// Block scope for case 1
const result = 'one';
console.log(result);
break;
}
case 2: {
// Different block scope for case 2
const result = 'two'; // No conflict with case 1's result
console.log(result);
break;
}
}
Module Pattern with Block Scope
const counter = (function() {
// Private variables in module scope
let count = 0;
return {
increment() {
return ++count;
},
decrement() {
return --count;
},
getCount() {
return count;
}
};
})();
console.log(counter.getCount()); // 0
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
// console.log(count); // ReferenceError: count is not defined
Interview Tips
- Explain the difference between function scope and block scope
- Describe the Temporal Dead Zone and how it affects
let
andconst
- Explain why
let
is preferred overvar
in modern JavaScript - Discuss when to use
const
vslet
- Explain how
let
in loops creates a new binding for each iteration - Describe how
const
prevents reassignment but not mutation of objects - Explain the behavior of
let
,const
, andvar
in the global scope
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.