What you'll be able to do
By the end of this page you should be able to tell primitives (number, string, boolean, null, undefined, symbol, bigint) from objects/arrays, explain why a = b copies a number but shares an object, and separate rebinding a name from mutating a shared value.
Previous post: spread and rest with objects.
Who this is for
- People surprised that
user2.name = ...changeduser1 - Beginners who think every
=makes a full independent copy - Anyone about to learn shared-state bugs in bigger apps
You can skip this if value vs reference already feels solid. Come back when a helper “secretly” edits the caller’s object.
Primitives copy the value
let a = 10;
let b = a;
b = 20;
console.log(a); // 10
console.log(b); // 20
Scenario A — scores as numbers. Assigning copies the number into b. Changing b does not touch a.
Scenario B — strings. Same idea: let s2 = s1; s2 = "other" leaves s1 alone. (Strings are immutable in practice for beginners — you replace the binding, you do not edit characters in place.)
Objects and arrays share the reference
const user1 = { name: "Asha" };
const user2 = user1;
user2.name = "Ben";
console.log(user1.name); // "Ben"
Scenario A — two variables, one object. Both names point at the same bag of fields.
Scenario B — arrays. const b = a; b.push(3) grows a too — same shared list you met in the arrays chapter.
The assignment copied the pointer, not a deep clone of the contents.
Reassignment vs mutation
let user = { name: "Asha" };
user = { name: "Ben" }; // rebinding — old object may be forgotten
const user = { name: "Asha" };
user.name = "Ben"; // mutation — same object, field changed
Scenario A — const with mutation. Allowed for object fields.
Scenario B — const with reassignment. user = {} throws. That is why people use const for objects they plan to mutate carefully — or spread into a new object instead.
Equality feels different
console.log(10 === 10); // true
console.log({} === {}); // false — two different objects
console.log([] === []); // false
Scenario A — same reference. const a = {}; const b = a; a === b is true.
Scenario B — same-looking contents. Two literals are still two objects. Comparing field-by-field is a different job (often JSON or a helper later).
A tiny practice file
Save as refs.html.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>primitives vs references</title>
</head>
<body>
<button id="num">Copy number</button>
<button id="obj">Share object</button>
<pre id="out"></pre>
<script>
const out = document.getElementById("out");
document.getElementById("num").addEventListener("click", function () {
let a = 10;
let b = a;
b = 20;
out.textContent = "a=" + a + " b=" + b;
});
document.getElementById("obj").addEventListener("click", function () {
const user1 = { name: "Asha" };
const user2 = user1;
user2.name = "Ben";
out.textContent = "user1.name=" + user1.name;
});
</script>
</body>
</html>
Mistakes I see a lot
1. Treating object assignment as a deep copy.
2. Blaming const for mutation bugs. const blocks rebinding, not field changes.
3. Comparing objects with === for “same data.” That checks identity.
4. Mutating arguments inside helpers. Callers keep the surprise — same as impure functions.
5. Forgetting arrays are objects. Push/pop are shared-reference territory.
6. Spreading once and assuming nested safety. Top-level only — previous post.
What to try before the next post
- Copy a number; change the copy; log both.
- Share an object; mutate through the second name.
- Compare
{} === {}and two vars pointing at one object. - Build the tiny HTML file.
Next in this series: object mutation and shared state — practical habits so shared references do not become silent bugs.
Try this next outside the series
Objects and references click faster when you connect them to state, payloads, and database-shaped data.
- MERN MongoDB modeling — see objects turn into real documents and nested structures
- JSON Formatter — inspect deep objects before you mutate or copy them