Ranjan Marathe Blog

TIL: Mutability vs. Immutability in JavaScript

Understanding the Problem of Mutability

In JavaScript, objects are mutable by default. When you copy an object, you’re often just copying the reference, not the actual data. This leads to situations where changes to one object inadvertently affect another.

Example: Mutating Shared Data

const account = {
  owner: "Bob",
  balance: { amount: 100 }
};

const clonedAccount = account;  // Not a copy, just a reference!
clonedAccount.balance.amount = 50;

console.log(account.balance.amount);  // 50 (Oops! Unexpected mutation)

How Immutability Solves This

Immutability ensures that data cannot be directly changed. Instead, when modifications are needed, a new object is created.

Example: Immutable Copy

const account = {
  owner: "Bob",
  balance: { amount: 100 }
};

const newAccount = structuredClone(account);
newAccount.balance.amount = 50;

console.log(account.balance.amount);  // 100 (No mutation!)

Techniques to Achieve Immutability

1. Shallow Copy (Doesn’t Prevent Deep Mutation)

const shallowCopy = { ...account };
shallowCopy.balance.amount = 50;
console.log(account.balance.amount);  // 50 (Oops, shallow copy!)

2. Deep Copy with structuredClone

const deepCopy = structuredClone(account);
deepCopy.balance.amount = 50;
console.log(account.balance.amount);  // 100 (Deep copy prevents mutation)

Real-World Scenario: Loan Records

Imagine managing a loan system where modifying one user’s loan record could accidentally change others.

const defaultLoan = {
  amount: 1000,
  history: { lastPayment: "01-01-2024" }
};

function updateLoan(userLoan) {
  const loan = structuredClone(defaultLoan);
  return { ...loan, ...userLoan };
}

const updatedLoan = updateLoan({ amount: 750 });
console.log(updatedLoan.amount);  // 750
console.log(defaultLoan.amount);  // 1000 (Unchanged)

Why Immutability Matters

Prevents Unintended Side Effects

Supports Functional Programming


Common Pitfalls with Cloning

  1. Non-Serializable Values:
    const obj = { compute: () => 42 };
    structuredClone(obj);  // DataCloneError
    
    • Why: Functions and DOM elements cannot be cloned.
    • Fix: Exclude or handle non-serializable properties before cloning.
  2. Circular References:
    const obj = {};
    obj.self = obj;
    const copy = structuredClone(obj);
    console.log(copy.self === copy);  // true
    
    • Benefit: structuredClone can handle circular references, unlike JSON.stringify.
  3. Performance Costs:
    • Deep cloning large objects can be expensive. Use shallow copies for simple data structures.

Hands-On Practice


When to Use Immutability

When NOT to Use Immutability


Conclusion

Mutability introduces hidden bugs. Embracing immutability through techniques like structuredClone leads to cleaner, safer, and more predictable code. Understanding when to use deep vs. shallow copies can prevent unnecessary headaches and ensure data integrity across your application.