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 = ... changed user1
  • 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

  1. Copy a number; change the copy; log both.
  2. Share an object; mutate through the second name.
  3. Compare {} === {} and two vars pointing at one object.
  4. 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.