AlgoInsight

Q&A

let vs. var vs. const in JavaScript

By AlgoInsight Team2026-06-18arraysjavascript

Overview

var is the legacy declaration keyword - its function scope and hoisting to undefined cause subtle bugs (the classic "loop variable leak," the "why is it undefined" mystery). ES6 introduced let and const with block scoping and the Temporal Dead Zone (TDZ) to make these bugs both visible and avoidable.

graph TD
    A[Declare a variable] --> B[const: block scope, no reassign - default choice]
    A --> C[let: block scope, can reassign]
    A --> D[var: function scope, hoisted - avoid]
    B --> E["Use when value won't change"]
    C --> F["Use for counters, changing refs"]
    D --> G["Legacy; block-scope bugs"]
    style B fill:#1e1b4b,color:#fff

Scope

var - function scope

function example() {
  if (true) {
    var x = 10;       // declared in if block, but...
  }
  console.log(x);     // 10 - var leaks out of the block!
}

var is scoped to the enclosing function (or global if at the top level). It escapes if, for, and any other block - a major source of bugs.

let and const - block scope

function example() {
  if (true) {
    let y = 20;
    const z = 30;
  }
  console.log(y);   // ❌ ReferenceError - block-scoped, not visible here
}

Block scope is what most programmers intuitively expect.

Hoisting

All three are hoisted (the name is registered at compile time), but differently:

console.log(a);   // undefined  - var hoisted, initialized to undefined
console.log(b);   // ❌ ReferenceError - let in TDZ
console.log(c);   // ❌ ReferenceError - const in TDZ

var a = 1;
let b = 2;
const c = 3;

The TDZ is intentional - it turns the silent undefined bug of var into a loud, catchable error.

Re-assignment and Re-declaration

var a = 1;
var a = 2;    // OK - var can be re-declared in the same scope
a = 3;        // OK - var can be reassigned

let b = 1;
let b = 2;    // ❌ SyntaxError - no re-declaration in same scope
b = 3;        // OK - let can be reassigned

const c = 1;
const c = 2;  // ❌ SyntaxError
c = 3;        // ❌ TypeError - const cannot be reassigned

const Does NOT Mean Immutable

const prevents reassignment of the binding - it doesn't freeze the object or array:

const person = { name: "Ada" };
person.name = "Linus";   // ✓ - mutating the object is allowed
person = {};              // ❌ TypeError - can't reassign person itself

const arr = [1, 2, 3];
arr.push(4);              // ✓ - mutating the array is fine
arr = [];                 // ❌ TypeError

For deep immutability, use Object.freeze() (shallow) or libraries.

The Classic var Loop Bug

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Prints: 3 3 3  (var leaks - all closures share the same i)

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Prints: 0 1 2  (let creates a new binding per iteration)

This is the canonical example of why var in loops causes bugs. let creates a new scoped i for each iteration.

Summary Table

Feature var let const
Scope Function Block Block
Hoisting Yes (as undefined) Yes (TDZ) Yes (TDZ)
Re-declarable Yes No No
Re-assignable Yes Yes No
Use today ❌ Avoid ✓ When re-assigning ✓ Default choice

Why This Matters in Interviews

This is one of the most common JavaScript questions. Hit all three scope differences, the var-hoisting-to-undefined vs TDZ behavior of let/const, the loop-closure classic bug, and that const doesn't mean immutable (objects/arrays are still mutable). The recommendation - const by default, let when needed, never var - signals a modern JS mindset.

Common Pitfalls

References

More answers