Skip to main content

Command Palette

Search for a command to run...

Understanding Primitive vs. Reference Types in JavaScript: Why Does My Object Change?

Published
7 min readView as Markdown
R

I am learning tech skills and sharing my journey here to make others life a little error free

As a budding JavaScript developer, I recently stumbled upon a fascinating (and slightly confusing!) behavior while experimenting with strings and objects. I wrote a simple piece of code to copy a string and an object, modified the copies, and expected the originals to stay unchanged. To my surprise, the string behaved as expected, but the object threw me a curveball! This experience led me to dive deep into Primitive Types and Reference Types in JavaScript, and I’m excited to share what I learned in this blog. Whether you're a beginner or brushing up on fundamentals, this post will help you understand why strings and objects behave differently when copied and modified.

Let’s break it down with code, explanations, and practical tips to make this concept crystal clear!

The Code That Sparked Curiosity

Here’s the code I was working with:

// String Example
let firstname = "rahul";
let newname = firstname;
console.log(firstname); // "rahul"
console.log(newname);   // "rahul"
newname = "mishra";
console.log("again printing");
console.log(firstname); // "rahul"
console.log(newname);   // "mishra"

// Object Example
const p1 = {
  name: "rahul",
};
const p2 = p1;
console.log("before any modification/copying");
console.log(p1); // { name: "rahul" }
console.log(p2); // { name: "rahul" }
p2.name = "mishra hai ab ";
console.log("now printing both after editing in p2");
console.log(p1); // { name: "mishra hai ab " }
console.log(p2); // { name: "mishra hai ab " }

What I Observed

  • String Example:

    • Initially, both firstname and newname were "rahul".

    • After changing newname to "mishra", firstname remained "rahul". The original string was unaffected.

  • Object Example:

    • Initially, both p1 and p2 were { name: "rahul" }.

    • After changing p2.name to "mishra hai ab ", both p1 and p2 showed { name: "mishra hai ab " }. The original object changed!

The Big Question

Why does modifying the copied string (newname) not affect the original (firstname), but modifying the copied object (p2) changes the original (p1)? This behavior puzzled me, and the answer lies in how JavaScript handles Primitive Types and Reference Types.
Primitive Types: Strings and Independent Copies

What are Primitive Types?

Primitive types in JavaScript are simple, immutable data types:

  • string (e.g., "rahul")

  • number (e.g., 42)

  • boolean (e.g., true)

  • null

  • undefined

  • symbol

  • bigint

How They Work

Primitive types are pass-by-value. When you copy a primitive value from one variable to another, JavaScript creates a new copy of the value in memory. Each variable points to its own memory location, making them independent.

In the String Example

Let’s revisit the code:

let firstname = "rahul";
let newname = firstname;
newname = "mishra";

Here’s what happens in memory:

  1. firstname = "rahul":

    • A string "rahul" is stored in memory (location A).

    • firstname points to location A.

  2. newname = firstname:

    • A new copy of "rahul" is created in memory (location B).

    • newname points to location B.

  3. newname = "mishra":

    • A new string "mishra" is created in memory (location C).

    • newname now points to location C.

    • firstname still points to "rahul" at location A.

Memory Visualization:

Initial:
firstname ----> "rahul" (location A)
newname   ----> "rahul" (location B)

After newname = "mishra":
firstname ----> "rahul" (location A)
newname   ----> "mishra" (location C)

Why No Change in Original?

  • Strings are immutable: You can’t modify a string in place; you can only assign a new string.

  • Copying a string creates an independent copy, so changes to newname don’t affect firstname.


Reference Types: Objects and Shared References

What are Reference Types?

Reference types in JavaScript are complex data types:

  • Objects (e.g., { name: "rahul" })

  • Arrays (e.g., [1, 2, 3])

  • Functions

How They Work

Reference types are pass-by-reference. When you copy a reference type, JavaScript only copies the reference (a pointer to the memory location), not the actual data. Both variables point to the same object in memory, so changes made through one variable affect all variables pointing to that object.

In the Object Example

Let’s analyze:

const p1 = { name: "rahul" };
const p2 = p1;
p2.name = "mishra hai ab ";

Here’s what happens in memory:

  1. p1 = { name: "rahul" }:

    • An object { name: "rahul" } is created in memory (location X).

    • p1 holds a reference to location X.

  2. p2 = p1:

    • No new object is created.

    • p2 is assigned the same reference to location X.

    • Both p1 and p2 point to the same object.

  3. p2.name = "mishra hai ab ":

    • The object at location X is modified (its name property changes).

    • Since p1 and p2 both point to location X, both reflect the updated object.

Memory Visualization:

Initial:
p1 ----> { name: "rahul" } (location X)
p2 ----> { name: "rahul" } (location X)

After p2.name = "mishra hai ab ":
p1 ----> { name: "mishra hai ab " } (location X)
p2 ----> { name: "mishra hai ab " } (location X)

Why the Original Changed?

  • Objects are mutable: You can change their properties (e.g., p2.name).

  • Copying an object copies the reference, not the data, so both variables manipulate the same object.

  • Any change via p2 directly updates the shared object, affecting p1.


Why Did const Allow Changes?

You might wonder: I used const p1, so why did p1 change? Here’s the deal:

  • const prevents reassignment of the variable (e.g., p1 = {} would throw an error).

  • It does not prevent modification of the object’s properties, as objects are mutable.

  • In the code, p2.name = "mishra hai ab " modifies the object’s property, which is allowed.

Example:

const obj = { key: "value" };
obj.key = "new"; // Allowed
obj = {}; // Error: Assignment to constant variable

How to Prevent Changes to the Original Object?

If you want to modify p2 without affecting p1, you need to create a copy of the object. Here are two ways to do it:

1. Shallow Copy

Creates a new object with copied top-level properties.

  • Spread Operator (...):

    javascript

      const p1 = { name: "rahul" };
      const p2 = { ...p1 }; // Shallow copy
      p2.name = "mishra hai ab ";
      console.log(p1); // { name: "rahul" } (unchanged)
      console.log(p2); // { name: "mishra hai ab " }
    
  • Object.assign:

      const p2 = Object.assign({}, p1);
    
  • Note: Only copies top-level properties. Nested objects still share references.

2. Deep Copy

Copies the entire object, including nested objects.

  • JSON.parse(JSON.stringify()):

    javascript

      const p1 = { name: "rahul", info: { age: 25 } };
      const p2 = JSON.parse(JSON.stringify(p1));
      p2.info.age = 30;
      console.log(p1); // { name: "rahul", info: { age: 25 } }
      console.log(p2); // { name: "rahul", info: { age: 30 } }
    
  • structuredClone (modern):

      const p2 = structuredClone(p1);
    
  • Use Case: Needed for objects with nested structures.


Key Takeaways

  1. Primitive Types (Strings):

    • Copied by value, creating independent copies.

    • Immutable: Changes create new values, leaving originals untouched.

    • Example: Modifying newname didn’t affect firstname.

  2. Reference Types (Objects):

    • Copied by reference, sharing the same memory location.

    • Mutable: Changes to properties affect all variables pointing to the object.

    • Example: Modifying p2.name changed p1.name.

  3. Copying Objects:

    • Use shallow copy (..., Object.assign) for simple objects.

    • Use deep copy (structuredClone, JSON.parse(JSON.stringify())) for nested objects.

  4. Debugging Tip:

    • Check if variables share references: console.log(p1 === p2) (true means same object).

    • Use copies to isolate changes.

  5. Real-World Relevance:

    • Critical for state management (e.g., React: setState({ ...state, key: value })).

    • Prevents bugs in APIs, data cloning, and shared data scenarios.


Experiments to Try

To solidify your understanding, try these:

  1. String Experiment:

     let a = "hello";
     let b = a;
     b = "world";
     console.log(a); // "hello"
    
  2. Object with Shallow Copy:

     const obj1 = { key: "value" };
     const obj2 = { ...obj1 };
     obj2.key = "changed";
     console.log(obj1); // { key: "value" }
    
  3. Reference Check:

     const x = { a: 1 };
     const y = x;
     const z = { ...x };
     console.log(x === y); // true
     console.log(x === z); // false
    

Wrapping Up

This exploration of Primitive vs. Reference Types was a game-changer for me as a JavaScript learner. Understanding why strings remain independent while objects share references helped me avoid sneaky bugs and write safer code. Whether you’re building a React app, working with APIs, or just experimenting, mastering this concept is essential for confident coding.

Have you encountered similar surprises in JavaScript? Share your experiences in the comments, or try the experiments above and let me know how it goes! If you found this post helpful, give it a like, share it with your dev friends, and follow for more JavaScript insights. Let’s keep learning and coding together!

Happy coding, and see you in the next blog!