Suggest an editImprove this articleRefine the answer for “Differences between var, let and const”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`var`, `let`, and `const`** - three ways to declare variables in JavaScript with different scope and reassignment rules. ```javascript var x = 1; // function-scoped, hoists as undefined, redeclaration allowed let y = 2; // block-scoped, TDZ until declaration, no redeclaration const z = 3; // block-scoped, TDZ, no reassignment (object mutation allowed) ``` **Key rule:** use `const` by default, `let` when reassignment is needed, avoid `var`.Shown above the full answer for quick recall.Answer (EN)Image**`var`, `let`, and `const`** - the three keywords for declaring variables in JavaScript, each with different scoping rules, hoisting behavior, and constraints on reassignment. ## Theory ### TL;DR - `var` is function-scoped; `let` and `const` are block-scoped - All three hoist, but `var` initializes to `undefined` while `let`/`const` stay in the temporal dead zone (TDZ) until the declaration line - `const` blocks reassignment; `var` and `let` allow it - Default choice: `const`. Use `let` only when reassignment is needed. Avoid `var` in new code - Analogy: `var` is a whiteboard in the hallway (visible from anywhere in the function); `let`/`const` are notes pinned inside a specific room (visible only in that block) ### Quick example ```javascript console.log(varX); // undefined (hoisted, initialized to undefined) var varX = 1; console.log(letX); // ReferenceError: Cannot access 'letX' before initialization let letX = 2; for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); // logs 3, 3, 3 (shared var) } for (let j = 0; j < 3; j++) { setTimeout(() => console.log(j), 0); // logs 0, 1, 2 (fresh binding per iteration) } ``` `var` in a loop shares one `i` across all iterations. `let` creates a fresh binding per iteration, so each closure captures its own value. ### Key difference `var` ignores `if`, `for`, and `while` blocks completely. It belongs to the nearest function or to global scope. `let` and `const` respect curly braces, so a variable declared inside a block stays there. That one rule explains most bugs that come from `var` leaking into outer scopes. ### When to use - Config values, API keys, imported modules → `const` - Loop counters that need incrementing, mutable locals → `let` - Any value that gets reassigned → `let` - Any value that never changes → `const` (including objects and arrays) - Maintaining existing legacy codebases only → `var` ### Comparison table | Feature | `var` | `let` | `const` | |---|---|---|---| | Scope | Function / global | Block | Block | | Hoisting | Yes, initializes to `undefined` | Yes, but stays in TDZ | Yes, but stays in TDZ | | Redeclaration | Allowed | Not allowed | Not allowed | | Reassignment | Allowed | Allowed | Not allowed | | Initialization required | No | No | Yes | | When to use | Legacy code only | Counters, mutable vars | Default choice | ### How the compiler handles this In V8, the engine creates a `LexicalEnvironment` per scope during compilation. `var` bindings go into the function's `VariableEnvironment` and are immediately set to `undefined`, which is why reading a `var` before its declaration line gives `undefined` instead of an error. `let` and `const` also enter the `LexicalEnvironment` at compile time, but their binding sits in an uninitialized state. Reading them before the declaration line triggers a `ReferenceError`. That window between entering scope and the declaration line is the TDZ. ### Common mistakes **Mistake 1: `var` in loops with async callbacks** ```javascript for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); // logs 3, 3, 3 } ``` All callbacks share one `i` because `var` is function-scoped. By the time they fire, the loop is done and `i` is 3. This is a classic interview trap. Fix: use `let`. **Mistake 2: Thinking `const` means immutable** ```javascript const arr = [1, 2]; arr.push(3); // works fine, arr is now [1, 2, 3] arr = [4, 5]; // TypeError: Assignment to constant variable ``` `const` prevents reassignment of the variable binding itself, not mutation of the value it holds. For a truly immutable array or object, use `Object.freeze()`. **Mistake 3: Reading `let` or `const` before the declaration** ```javascript console.log(x); // ReferenceError, not undefined let x = 5; ``` This does not silently return `undefined` like `var` would. The variable is in TDZ. The engine knows it exists but refuses access until the declaration line runs. ### Real-world usage - **React**: `const` for hooks (`const [count, setCount] = useState(0)`), component definitions, all imports - **Node.js / Express**: `const app = express()`, `const router = express.Router()` - **Redux**: `const FETCH_USER = 'FETCH_USER'` for action type constants - **Loops with mutable counters**: `for (let i = 0; i < arr.length; i++)` ### Follow-up questions **Q:** What is the temporal dead zone? **A:** The TDZ is the period between a `let` or `const` variable entering scope (at compile time) and reaching its declaration line. Any access in that window throws a `ReferenceError`. `var` has no TDZ because it initializes to `undefined` right away. **Q:** Can you mutate an object declared with `const`? **A:** Yes. `const obj = {}; obj.name = 'Alice'` works fine. `const` blocks reassignment only: `obj = {}` throws a TypeError. Use `Object.freeze(obj)` if you need the object itself to be immutable. **Q:** Why does `for (var i = 0; ...)` with `setTimeout` always log the final value of `i`? **A:** All iterations share one `i` because `var` is function-scoped. Closures capture a reference to that shared variable, not a snapshot. When the callbacks fire, the loop is already done and `i` is at its final value. Switching to `let` gives each iteration its own binding. **Q:** Does top-level `var` attach to `window` in Node.js? **A:** No. Browsers attach top-level `var` to `window`. In Node.js every file runs inside a module wrapper function, so top-level `var` stays local to that module. **Q:** Walk through what V8 does when `let` is shadowed in a nested block. **A:** V8 creates a new `LexicalEnvironment` for each block during compilation. The outer `let` binding goes into the outer env. When execution enters the inner block, V8 creates a second `LexicalEnvironment` chained to the outer one. The inner `let` goes there and shadows the outer binding. Variable lookup walks up the chain, so inner code finds its own binding first. Both bindings start in TDZ until their declaration lines execute. ## Examples ### Scope leak: var vs let ```javascript function checkScope() { if (true) { var x = 10; // leaks out to function scope let y = 20; // stays inside this block } console.log(x); // 10, var leaked out console.log(y); // ReferenceError, let did not leak } checkScope(); ``` `var` does not respect the `if` block boundary. `let` does. One variable escapes the block, the other stays put. That difference is the entire reason `let` exists. ### React component with proper declarations ```javascript function UserList({ users }) { const [filteredUsers, setFilteredUsers] = useState(users); return ( <ul> {filteredUsers.map(user => { const userId = user.id; // fresh const per iteration block, never reassigned return <li key={userId}>{user.name}</li>; })} </ul> ); } ``` Everything here is `const` because nothing gets reassigned. `setFilteredUsers` is the function you call to schedule a state update, not a value you overwrite directly. `userId` is a new block-scoped constant on every iteration.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.