ComparisonJavaScript

let vs const

let vs const in JavaScript: which to reach for by default, and what const actually protects against.

Last updated

The short answer

Default to const; reach for let only when you know the binding will be reassigned — a loop counter, an accumulator, a value that genuinely changes. This isn't just style — an accidental reassignment throws a TypeError instead of silently overwriting, your linter and TypeScript flag it before you ever run the code, and a codebase that's mostly const makes the few lets stand out as the places state actually changes.

Dimensionletconst
ReassignmentAllowedThrows a TypeError
MutabilityValue can be reassignedBinding is fixed, contents can still mutate
ScopeBlock-scopedBlock-scoped
HoistingHoisted, temporal dead zone appliesHoisted, temporal dead zone applies
Common useLoop counters, accumulators, reassigned stateEverything else — the default choice

Choose let when

  • →A classic for-loop counter that needs to be reassigned on each iteration.
  • →An accumulator or running value built up across a loop or callback — a running total, a found-item flag.
  • →A variable that's genuinely conditionally assigned later — declared once, set in one of several branches.

Choose const when

  • →The default for everything — function results, imported values, object and array bindings you won't reassign.
  • →You want an accidental reassignment to fail loudly — a TypeError at runtime, and a linter warning long before that — rather than a silent overwrite three functions later.
  • →You're declaring an object or array whose contents may change (push, mutate a property) but whose reference never will.

The catch nobody mentions

const doesn't make anything immutable — it only prevents the variable from being reassigned to a different value. `const arr = []; arr.push(1);` is perfectly legal, and so is `const obj = {}; obj.name = 'x';`. For actual immutability you need Object.freeze() (shallow) or a library that enforces it deeply — const alone won't stop a nested property from changing.

Common mistakes

  • const total = 0; total = total + 5;

    Reassigning a const throws TypeError: Assignment to constant variable. It is a runtime error rather than a syntax one, so the file parses and the failure happens when that line executes.

    let total = 0; total = total + 5;
  • const config = { debug: false }; config.debug = true;

    This is legal and surprises people who read const as immutable. The binding cannot be reassigned; the object it points at can be mutated freely. Object.freeze is what prevents the second.

  • var count = 0;

    var is function-scoped and hoisted, so it leaks out of blocks and can be read before its assignment as undefined. There is no case in new code where it is preferable to let or const.

Questions

Does const make a value immutable?

No, only the binding. A const array can be pushed to and a const object can have properties changed — what you cannot do is point the name at something else. Object.freeze gives shallow immutability of the value itself.

Should I default to const?

Yes. Start with const and change to let only when reassignment is genuinely needed. The result is that let becomes a signal in review — it says this value changes, which is worth noticing.

What is the temporal dead zone?

The gap between entering a scope and the let or const declaration being evaluated. Accessing the variable in that window throws ReferenceError rather than returning undefined, which is a deliberate improvement over var's silent hoisting.

Which should a loop variable be?

let for a counting loop, since it is reassigned each iteration. For-of and for-in bind a fresh variable per iteration, so const works there — and is preferable, because it prevents accidental reassignment inside the body.

Is there a performance difference?

No meaningful one; modern engines optimise all three similarly. Choose on what the code communicates about intent, which is the only axis where the difference is real.

Related in Comparisons