Immutable JavaScript Objects using the Proxy Object
JavaScript objects are mutable by default.
While this is convenient, it often leads to bugs when unexpected changes are made to data structures that were supposed to remain stable.
👉 What if we could enforce immutability natively, without external libraries?
That’s where the Proxy object comes in.
🔍 Why Immutability Matters
Immutability brings several advantages:
- Predictability: Your data cannot be accidentally mutated.
- Debugging made easier: No "hidden" state changes.
- Functional programming alignment: Works well with React, Redux, and other state-driven approaches.
Traditional immutability requires libraries like Immutable.js
or manual Object.freeze
.
But Object.freeze
is shallow — it only prevents changes at the top level, not nested properties.
This is where Proxy
shines ✨.
🛠 Using Proxy for Immutability
The Proxy
object allows us to intercept operations performed on an object.
We can "trap" mutations and prevent them.
function immutable(obj) { return new Proxy(obj, { set(target, prop, value) { throw new Error(`❌ Cannot modify property "${prop}". Object is immutable.`); }, deleteProperty(target, prop) { throw new Error(`❌ Cannot delete property "${prop}". Object is immutable.`); }, defineProperty(target, prop, descriptor) { throw new Error(`❌ Cannot redefine property "${prop}". Object is immutable.`); } }); }
✅ Example in Action
const user = immutable({ name: "Rudy", role: "Frontend Engineer", skills: ["Angular", "TypeScript"] }); console.log(user.name); // Rudy user.name = "John"; // ❌ Error: Cannot modify property "name". Object is immutable. delete user.role; // ❌ Error: Cannot delete property "role". Object is immutable.
🔁 Deep Immutability
The above works for top-level properties.
But what if we want nested objects to also be immutable?
We can recursively wrap them in a proxy:
function deepImmutable(obj) { if (obj !== null && typeof obj === "object") { return new Proxy(obj, { get(target, prop) { return deepImmutable(target[prop]); }, set() { throw new Error("❌ Cannot modify immutable object."); }, deleteProperty() { throw new Error("❌ Cannot delete property."); } }); } return obj; }
Usage:
const config = deepImmutable({ app: { theme: "dark", version: "1.0" } }); console.log(config.app.theme); // dark config.app.theme = "light"; // ❌ Error: Cannot modify immutable object.
🚀 When to Use This
- State management: Prevent accidental mutations in apps using React/Redux/NgRx.
- Shared data: When multiple developers handle the same structure, immutability avoids conflicts.
- APIs: When returning objects, ensuring immutability avoids consumers breaking data contracts.
⚖️ Trade-offs
- Slight performance overhead due to Proxy wrapping.
- May be unnecessary for small-scale projects where discipline is enough.
- Proxies are not supported in older environments (but work in all modern browsers and Node.js).
🎯 Conclusion
Immutability is a powerful concept that reduces bugs and makes reasoning about your code much easier.
With Proxy, JavaScript gives us a native way to enforce immutability without third-party libraries.
Next time you want bulletproof objects, try wrapping them with a Proxy — and make your state safer by design.
Top comments (0)