Skip to content

What const protects in JavaScript

A common saying is that const means „immutable". In reality it only protects assignment to the name, not the value itself - that's why mutating an array declared with const works, but trying to assign it something new throws.

A common association: const means „immutable”. In practice the keyword does something else, and the difference shows up at the first array:

const teachers = ["Jan", "Nowak"];
teachers[1] = "Adam"; // OK - mutating the contents
console.log(teachers); // ["Jan", "Adam"]

teachers = ["Others"]; // TypeError - re-binding

Mutation works, re-binding doesn’t. It isn’t a bug or an edge case - that’s how const is designed.

#Binding vs value

const says: „this name will point to the same thing for the entire block”. This connection between an identifier and a value is called a binding - and it’s precisely the binding that const blocks from changing.

It does not block changing the contents of that thing. An array, an object, a class instance - anything mutable - can be changed freely, as long as the identifier still points to the same object in memory.

For primitive types (number, string, boolean, null, undefined, symbol, bigint) this distinction disappears. You can’t modify a number or a string - the only thing you can do is assign a new value to the name. And that is exactly what const doesn’t allow.

#Why it was designed this way

Two reasons from the ES2015 era:

  1. Cheap to enforce. Checking „does this name still point to the same thing” is cheap and statically verifiable. Checking „has the contents not changed” would require Object.freeze or a deep comparison at every step - a different order of cost.
  2. Aligned with the most common intent. const typically catches accidental re-assignments, not accidental mutations - someone overwrites your variable in a loop or in an if-branch. It solves that problem head-on, without trying to regulate mutation.

If your logic needs real immutability, there are separate tools for that: Object.freeze (it freezes the object’s direct fields - nested objects can still be changed), immutability tools (Immer, Immutable.js), readonly in TypeScript. Each of them watches over something different than const.

#Closing thought

const doesn’t promise that the value won’t change. It promises that the name still points to the same place. Those are two different things, and they start to matter the moment you declare your first array with const. Keep const as your default - it protects bindings more cheaply and more clearly than let - but if your logic requires immutability, reach for a tool that promises exactly that.

(For the choice between let and var - the scope of the name, not the binding itself - I cover that separately in the note on let and var.)

← Back to notes