var vs let vs const
Scope, hoisting, TDZ, and reassignment — why const/let replaced var and how temporal dead zone shows up in bugs.
- javascript
- var-vs
Three ways to bind names; only two belong in modern code. Interviews still ask var vs let vs const because scope and TDZ explain real bugs (especially in loops and this/closure questions).
Quick matrix
var |
let |
const |
|
|---|---|---|---|
| Scope | function | block {} |
block |
| Hoisted | yes, init undefined |
yes, TDZ until init | yes, TDZ until init |
| Reassign | yes | yes | no |
| Redeclare same scope | yes | no | no |
function f() {
if (true) {
var a = 1;
let b = 2;
}
console.log(a); // 1
// console.log(b); // ReferenceError
}
const is not immutable
const user = { name: 'Ada' };
user.name = 'Grace'; // OK
// user = {}; // TypeError
const arr = [1];
arr.push(2); // OK
const freezes the binding, not the value. Use Object.freeze (shallow) or immutable patterns for deep constancy.
Temporal Dead Zone (TDZ)
console.log(x); // undefined
var x = 1;
// console.log(y); // ReferenceError — TDZ
let y = 2;
From the start of the block until the let/const line runs, access throws. That’s the TDZ — not “doesn’t hoist,” but “hoists without initializing.”
Loop closures: the classic
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 3 3 3 — one function-scoped i
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 0 1 2 — new binding per iteration
Prefer const by default
Style that scales:
consteverywhereletwhen reassignment is required (loop counters, accumulators)varonly when maintaining ancient code or rare function-scope tricks
Module and block structure make function-scoped var surprises unnecessary.
Global object pollution
var g = 1; // browser sloppy script: property of global object
let h = 2; // binding in script scope, not window.h
Another reason modules + let/const are safer.
Interview answer (out loud)
“var is function-scoped and hoisted as undefined; let and const are block-scoped with a TDZ. const prevents reassignment but not mutation. I default to const, use let for reassignment, and avoid var. The for-loop setTimeout question is about per-iteration bindings with let.”
Redeclaration and switch
switch (x) {
case 1:
let y = 2; // y scoped to whole switch block
break;
case 2:
// let y = 3; // SyntaxError if same block
break;
}
Wrap case bodies in {} when declaring let/const per case.
Destructuring with const
const { a, b } = obj;
// a and b are const bindings
Common pattern: const for destructured props even when the source object mutates later — bindings already captured values (for primitives) or references (for objects).
Further reading
Related
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.