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-bindingMutation 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:
- 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.freezeor a deep comparison at every step - a different order of cost. - Aligned with the most common intent.
consttypically catches accidental re-assignments, not accidental mutations - someone overwrites your variable in a loop or in anif-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.)