Functional Programming
Immutable Data Structures (Object.freeze, Immer)
Master immutability in JavaScript. Learn to freeze objects, compare shallow and deep freezes, and use the Immer library to simplify state updates.
1. Introduction
Immutability is a core principle of functional programming where data cannot be modified after it is created. JavaScript provides native methods like Object.freeze() for shallow immutability, while libraries like Immer simplify deep state updates using a copy-on-write proxy mechanism.
2. Why It Matters
Mutating state directly (like state.user.logged = true) can cause bugs in reactive frameworks (like React or Redux), which rely on reference comparisons to detect changes. Immutability ensures that updates generate new object references, allowing frameworks to detect changes and trigger UI updates reliably.
3. Real-World Analogy
Think of a Legal Contract Review:
- State Mutation (Scribbling on original document): You change a clause in the contract by crossing out words on the original page. The original reference is modified, and you cannot verify what the original contract looked like.
- Object.freeze (Laminating the page): You laminate the contract. No one can write on the sheet anymore. However, if there is a binder pocket containing additional sheets (nested objects), they can still write on those sheets. This is shallow freezing.
- Immer (The Draft Copy Assistant): An assistant hands you a photocopy draft (the draft state). You scribble, write comments, and modify the draft copy as much as you want. Once you finish, the assistant collects the draft copy, compiles the changes, and prints a brand-new, clean copy containing your updates, leaving the original contract untouched.
4. Native Immutability: Object.freeze
Object.freeze() makes an object immutable by preventing properties from being added, deleted, or modified:
5. Deep Freezing Objects
To freeze an object completely, you must write a recursive function that freezes every nested property:
6. Immutability with Immer
Writing immutable updates manually for deeply nested objects can be tedious. The Immer library allows you to write updates by modifying a temporary "draft" state, creating the final immutable copy automatically:
7. Common Mistakes
- Assuming Object.freeze() deep-freezes objects automatically:
Object.freeze()is shallow. If the object contains nested objects or arrays, they can still be mutated, which can lead to unexpected state changes.
8. Quick Quiz
Q1: What happens if you try to mutate a property of a frozen object in strict mode?
A) It fails silently, preserving the original value
B) It throws a TypeError exception immediately
Answer: B — In strict mode, attempting to modify a property on a frozen object throws a TypeError.
9. Scenario-Based Challenge
The Nested State Modifier Refactor:
An application tracks nested preferences: { user: { theme: "light" } }. You want to update the theme to "dark" immutably. Write the update using the spread operator first, then write the same update using Immer's produce helper.
10. Debugging Exercise
Explain why this nested configuration mutation succeeds, and how to fix it:
const config = { server: { port: 8080 } };Object.freeze(config);
// Bug: nested object is mutated successfully! config.server.port = 9000; console.log(config.server.port); // logs 9000! Why?
View Solution
Diagnosis: Object.freeze() only freezes the top-level properties of an object. The nested server object remains mutable.
Fix: Use a recursive deepFreeze() function to freeze all nested objects, or freeze the nested object explicitly:
Object.freeze(config);
Object.freeze(config.server); // Freeze nested object explicitly
config.server.port = 9000; // Blocked!
11. Interview Questions
🟢 Q1: Compare Object.freeze() and Object.seal() and list their differences.
Answer: Both methods make objects immutable but have different restrictions:
• Object.seal(): Prevents adding or deleting properties, but allows modifying existing properties.
• Object.freeze(): Prevents adding, deleting, and modifying properties. It is the most restrictive native immutability method in JavaScript.
12. Production Considerations
- • Immer Proxy Overhead: Immer uses Proxy objects to intercept updates, which introduces a small performance overhead. While negligible for most web applications, avoid using Immer inside high-frequency loops in performance-critical code.