Loading...
Loading...
Block-scoped declarations that replaced var. The modern way to declare variables.
Module 9 · Modern JavaScript (ES6+)
ES2015 and later additions transformed how JavaScript is written. let and const, template literals, modules, spread/rest, optional chaining, and nullish coalescing are now standard in every modern codebase.
ES6 replaced var with let and const — block-scoped bindings that behave predictably in loops, branches, and modules. Use const by default; reach for let when reassignment is intentional.
ES2015 (ES6) and the releases that followed added the syntax modern JavaScript is written in today. let and const replaced var, template literals replaced string concatenation, and import/export replaced global script tags.
This module covers the ES6+ features you will see in every codebase and job interview. Each chapter shows the old pattern alongside the modern equivalent so you can read legacy code and write idiomatic JavaScript.
Every modern codebase bans var. Block scope fixes classic loop closure bugs, const communicates immutability of the binding, and the temporal dead zone catches use-before-declare mistakes early.
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 blocks
let / const
Block-scoped { }
No hoisting (TDZ)
No redeclaration
var
Function-scoped
Hoisted as undefined
Can redeclare
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.
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 reassign
Think 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.
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;
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 */ }
Common questions about let and const.