let and const
Block-scoped declarations that replaced var. The modern way to declare variables.
Block Scope
let and const are scoped to the nearest {} block.
// Block = any code inside { }
{
let x = 10;
const y = 20;
console.log(x, y); // 10, 20
}
console.log(x); // ❌ ReferenceError: x is not defined
console.log(y); // ❌ ReferenceError: y is not defined
// Blocks in if, for, while, etc:
if (true) {
let secret = "hidden";
}
console.log(secret); // ❌ Not accessible outside
// Compared to var (function-scoped):
if (true) {
var leaked = "visible";
}
console.log(leaked); // "visible" 😬 — var ignores blockslet / const
Block-scoped { }
No hoisting (TDZ)
No redeclaration
var
Function-scoped
Hoisted as undefined
Can redeclare
let vs var
Why let fixed the problems with var.
// Problem 1: Loop variable leaks
for (var i = 0; i < 3; i++) {
// ...
}
console.log(i); // 3 (leaked!)
for (let j = 0; j < 3; j++) {
// ...
}
console.log(j); // ❌ ReferenceError (contained!)var leaks loop variables into the outer scope. let keeps them contained in the loop block.
1 / 3
const Rules
const prevents reassignment, not mutation.
// const = cannot reassign the binding
const PI = 3.14159;
PI = 3; // ❌ TypeError: Assignment to constant variable
// Must initialize when declaring:
const x; // ❌ SyntaxError: Missing initializer
// ⚠️ Objects and arrays CAN be mutated:
const user = { name: "Alice" };
user.name = "Bob"; // ✓ OK (mutating property)
user.age = 25; // ✓ OK (adding property)
user = { name: "Eve" }; // ❌ Cannot reassign
const nums = [1, 2, 3];
nums.push(4); // ✓ OK (mutating array)
nums[0] = 99; // ✓ OK (mutating element)
nums = [5, 6, 7]; // ❌ Cannot reassignThink of const as:
"The variable name always points to the same thing" — not "the thing is frozen." Use Object.freeze() if you need true immutability.
Temporal Dead Zone
let and const exist but can't be accessed before declaration.
// The Temporal Dead Zone (TDZ):
// From block start → declaration line = TDZ
console.log(x); // ❌ ReferenceError (in TDZ)
let x = 5; // TDZ ends here
// Compare with var:
console.log(y); // undefined (hoisted, no TDZ)
var y = 5;
// TDZ in practice:
function example() {
// ---- TDZ for 'value' starts ----
console.log(value); // ❌ ReferenceError
if (true) {
// ---- TDZ for 'inner' starts ----
let inner = "hello";
// ---- TDZ for 'inner' ends ----
}
let value = 42;
// ---- TDZ for 'value' ends ----
console.log(value); // 42
}// TDZ also applies to const: const fn = () => console.log(myConst); // fn() here → ReferenceError (TDZ) const myConst = "ready"; fn(); // ✓ "ready" (called after declaration) // typeof is NOT safe in TDZ: typeof undeclaredVar; // "undefined" (safe) typeof myLet; // ❌ ReferenceError let myLet = 1;
Best Practices
Modern JavaScript convention.
// Rule: Use const by default, let when you need to reassign.
// Never use var.
// ✓ const for values that won't be reassigned:
const API_URL = "https://api.example.com";
const users = []; // will push, but not reassign
const config = { debug: true }; // will mutate, not reassign
const handleClick = () => { /* ... */ };
// ✓ let for values that change:
let count = 0;
count++;
let current = items[0];
for (const item of items) {
if (item.score > current.score) {
current = item;
}
}
// ✓ const in for...of and for...in:
for (const item of array) { /* new binding each iteration */ }
for (const key in object) { /* new binding each iteration */ }
// ✓ let in classic for loops:
for (let i = 0; i < 10; i++) { /* i changes */ }FAQ
Common questions about let and const.