100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
JavaScript

let, const and Block Scope in JavaScript

How let and const introduced block scoping, the temporal dead zone, and why const does not make objects immutable.

ES6+ FeaturesBeginner9 min readJul 8, 2026
Analogies

1. Introduction

Before ES6 (2015), JavaScript only had var, which is function-scoped and gets hoisted with an initial value of undefined. This caused confusing bugs, especially inside loops and conditional blocks. ES6 introduced let and const, two new ways to declare variables that are scoped to the nearest enclosing block ({ }) rather than the nearest function. let is used for variables that will be reassigned, while const is used for bindings that should never be reassigned after their initial value is set.

🏏

Cricket analogy: Like an old scoring rule where a substitute fielder's status leaked across the whole match (function-scoped var) causing confusion, until a new rule confined a fielder's temporary role to just the current over (block-scoped let), with a permanent captain designation (const) never reassigned mid-match.

2. Syntax

javascript
// var: function-scoped, hoisted, can be redeclared
var x = 1;

// let: block-scoped, can be reassigned, cannot be redeclared in same scope
let y = 2;
y = 3;

// const: block-scoped, must be initialized, cannot be reassigned
const z = 4;

if (true) {
  let blockScoped = 'inside';
  const alsoBlockScoped = 'inside';
}
// blockScoped and alsoBlockScoped are NOT accessible here

3. Explanation

Both let and const are confined to the block in which they are declared: an if block, a for loop body, or any pair of curly braces. This is a huge improvement over var, which leaks out of blocks into the surrounding function. A classic example is a for loop with var i: after the loop ends, i still exists in the outer scope, and closures created inside the loop all share the same i. Using let i instead gives each iteration its own fresh binding, which is exactly what most developers expect.

🏏

Cricket analogy: Like a substitute fielder's temporary assignment (var i) still lingering in the dugout roster after the over ends, so every replay reference points to the same final fielder — a fresh per-over assignment (let i) gives each over's replay its own correct fielder.

const prevents *reassignment* of the binding, not mutation of the value it points to. If const arr = [], you cannot do arr = [1, 2] (that reassigns the binding), but you absolutely can do arr.push(1) (that mutates the object the binding points to) because the array reference itself never changes. The same applies to objects: const obj = {} allows obj.name = 'Ada' but not obj = { name: 'Ada' }.

🏏

Cricket analogy: Like a permanently assigned squad number (const) that can never be reassigned to a different player, but the player wearing it can still change their batting stance freely — const arr locks the binding, not the array's contents like arr.push(1).

Gotcha — const does not freeze objects: const arr = []; arr.push(10); console.log(arr); // [10] is completely legal. To make an object truly immutable you need Object.freeze(obj), which prevents adding, removing, or changing properties (in non-strict mode it fails silently; in strict mode it throws).

Gotcha — the Temporal Dead Zone (TDZ): unlike var, let and const declarations ARE hoisted to the top of their block, but they are not initialized until the declaration line executes. Accessing them before that point throws a ReferenceError instead of returning undefined. console.log(a); let a = 5; throws ReferenceError: Cannot access 'a' before initialization, whereas the equivalent with var would silently log undefined.

4. Example

javascript
const funcs = [];

for (let i = 0; i < 3; i++) {
  funcs.push(() => console.log('let i =', i));
}
funcs.forEach(fn => fn());

const config = { retries: 3 };
config.retries = 5;       // mutation: allowed
config.timeout = 1000;    // adding a property: allowed
console.log(config);

try {
  config = { retries: 1 }; // reassignment: not allowed
} catch (err) {
  console.log(err instanceof TypeError, err.message);
}

5. Output

text
let i = 0
let i = 1
let i = 2
{ retries: 5, timeout: 1000 }
true Assignment to constant variable.

6. Key Takeaways

  • let and const are block-scoped ({ }); var is function-scoped and leaks out of blocks.
  • Each loop iteration with let i creates a fresh binding, so closures capture the correct value.
  • const locks the binding, not the value — arrays/objects declared with const can still be mutated.
  • Reassigning a const binding throws a TypeError: Assignment to constant variable.
  • let and const are hoisted but stay uninitialized in the Temporal Dead Zone until their declaration runs.
  • Prefer const by default, and switch to let only when a variable genuinely needs reassignment.

Practice what you learned

Was this page helpful?

Topics covered

#JavaScript#JavaScriptProgrammingStudyNotes#Programming#LetConstAndBlockScopeInJavaScript#Let#Const#Block#Scope#StudyNotes#SkillVeris