Values and binding
const, let, and the values they point at — the foundation everything else in JS sits on.
- Choose between const and let (and avoid var)
- Distinguish primitives from objects, and value-copy from reference-copy
- Explain why const does not mean "the value can't change"
JavaScript binds names to values much like Python does, but with its own rules and a couple of famous traps. Getting binding right early prevents a whole class of confusing bugs — especially the "why did changing this also change that?" variety.
const and let (not var)
Declare with const by default; use let only when you genuinely need to
reassign the name. Avoid var — its scoping rules are a legacy footgun that
modern JavaScript replaced for good reasons (one consequence is hoisting, where var declarations are silently moved to the top of their function scope).
const name = "Ada"; // cannot be reassigned
let count = 0; // can be reassigned
count = count + 1;Reaching for const first is a small habit with a big payoff: a name that can't
be reassigned is one less thing to track when reading code.
const binds the name, not the value
This is the subtlety that trips everyone up. const freezes the binding — the
arrow from name to value — not the value itself:
const user = { name: "Ada" };
user.name = "Grace"; // ✓ allowed — we mutated the object, not the binding
user = {}; // ✗ TypeError — repointing the const bindingSo const protects the arrow, not the thing it points at. To make the object
itself unchangeable you'd need something more (like Object.freeze).
Primitives vs objects
Values come in two camps, and they're copied differently:
- Primitives —
number,string,boolean,null,undefined,symbol,bigint. Copied by value: you get an independent copy. - Objects — including arrays and functions. Copied by reference: you copy the arrow, not the thing it points to.
let a = 1;
let b = a; // b is its own copy
b = 2; // a is still 1
const xs = [1, 2];
const ys = xs; // ys points at the SAME array
ys.push(3); // xs is now [1, 2, 3] too — one array, two namesSee it for real — this runs in your browser:
Making a real copy
When you do want an independent copy of an array, you have to build a brand-new
one. The usual tool is the spread syntax, written as three dots ...:
const xs = [1, 2, 3];
const ys = [...xs]; // a new array containing the same items
ys.push(4);
// xs is still [1, 2, 3]; ys is [1, 2, 3, 4] — they're separate objectsRead [...xs] as: "make a new array [ ], and spread the items of xs
into it, one by one." Two things are happening:
- The square brackets
[ ]create a fresh array — a new object, so a new arrow (the whole point of the previous section). ...xsexpandsxsinto its individual elements (1, 2, 3), which become the contents of that new array — as if you'd typed[1, 2, 3]by hand.
Because ys now points at a different array, pushing to ys can't reach back
and change xs. You copied the value, not just the arrow.
Objects use the same idea with curly braces:
const user = { name: "Ada" };
const copy = { ...user }; // a new object with the same fieldsSee the difference for yourself — change [...xs] to just xs and watch the
original get clobbered too:
Spread makes a shallow copy — it duplicates the top level only. If an array holds other arrays or objects, those inner ones are still shared between the copies. That's fine for most beginner cases; we'll revisit deep copies later.
Equality follows the same rule
Because objects are references, two different objects with identical contents
are not equal — === compares identity, not contents:
{} === {} // false — two different objects
const o = {}; o === o // true — same object
1 === 1 // true — primitives compare by value"Two names, one object" is behind a huge share of beginner bugs. Whenever you
copy an object or array, ask: did I copy the value, or just another arrow to the
same thing? If you need a real copy, copy it explicitly (e.g. [...xs]).
Where to go next
With binding solid, the next big idea in JavaScript is how it handles work that takes time — async and the event loop, in the intermediate track.
Write withItem(arr, item) so it returns a NEW array with item added to the end, leaving the original array untouched. This is the reference-vs-value idea from the lesson in practice.
withItem([1, 2], 3) → [1, 2, 3]