ESC

Type to search the knowledge base.

var vs let vs const

Scope, hoisting, TDZ, and reassignment — why const/let replaced var and how temporal dead zone shows up in bugs.

beginner3 min read
  • 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:

  1. const everywhere
  2. let when reassignment is required (loop counters, accumulators)
  3. var only 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 guides