Make Object Immutable

Updated on 17 June, 2025
Make Object Immutable header image

Problem Statement

The task is to create a function that receives an object obj, which is a valid JSON object or an array, and returns a new immutable version of this object. Once an object is defined as immutable, any attempt to modify its properties (for objects) or elements (for arrays), as well as calls to methods that typically mutate arrays, should result in specific error messages. The error messages should clearly indicate the type of modification attempt:

  • Modifying an object property will yield Error Modifying: ${key}.
  • Modifying an array element will yield Error Modifying Index: ${index}.
  • Using mutating array methods (like push, pop, etc.) will yield Error Calling Method: ${methodName}.

This immutable object ensures that the original structure and values remain unchanged after its creation, throwing errors when any change attempts are detected.

Examples

Example 1

Input:

obj = { "x": 5 }
fn = (obj) => {
  obj.x = 5;
  return obj.x;
}

Output:

{ "value": null, "error": "Error Modifying: x" }

Explanation:

Attempting to modify a key on an object results in an error, even if setting the same value.

Example 2

Input:

obj = [1, 2, 3]
fn = (arr) => {
  arr[1] = {};
  return arr[2];
}

Output:

{ "value": null, "error": "Error Modifying Index: 1" }

Explanation:

Attempting to modify an array element throws an error.

Example 3

Input:

obj = { "arr": [1, 2, 3] }
fn = (obj) => {
  obj.arr.push(4);
  return 42;
}

Output:

{ "value": null, "error": "Error Calling Method: push" }

Explanation:

Calling a mutating array method throws an error.

Example 4

Input:

obj = { "x": 2, "y": 2 }
fn = (obj) => {
  return Object.keys(obj);
}

Output:

{ "value": ["x", "y"], "error": null }

Explanation:

No mutation occurs, so the function returns normally.

Constraints

  • obj is a valid JSON object or array
  • 2 <= JSON.stringify(obj).length <= 10^5

Approach and Intuition

To solve this problem:

  1. Clone the Original Object: Use JSON.parse(JSON.stringify(obj)) to create a deep copy of the input. This avoids mutation of the original object.

  2. Wrap with a Proxy: Use Proxy to trap and intercept operations like property setting, deletion, and method calls.

  3. Intercept Property Modification:

    • For object properties, throw Error Modifying: ${key} if a set operation is attempted.
    • For array elements, throw Error Modifying Index: ${index}.
  4. Override Mutating Array Methods: Block methods such as push, pop, shift, unshift, splice, sort, and reverse. Each should throw Error Calling Method: ${methodName} when invoked.

  5. Apply Recursively: Ensure immutability is enforced deeply by applying the proxy recursively to all nested objects and arrays.

  6. Result Handling: Execute the provided function fn with the proxied immutable object and capture its return value or intercept thrown errors to format the output accordingly.

This structured proxy-based approach ensures strict immutability and clear error messaging as per the problem requirements.

Solutions

  • JavaScript
js
var createImmutableObject = function(inputObject) {
  const mutatingMethods = new Set(['pop', 'push', 'shift', 'unshift', 'splice', 'sort', 'reverse']);
    
  const proxyHandler = {
    set: function(tgt, key) {
      throw Array.isArray(tgt) ? `Error Modifying Index: `{key}` : `Error Modifying: `{key}`;
    },
    
    get: function(tgt, key) {
      const value = tgt[key];
      const isAllowed = key === 'prototype' || value === null || (
        typeof value !== 'object' && typeof value !== 'function'
      );
    
      return isAllowed ? value : new Proxy(value, this);
    },
    
    apply: function(target, context, args) {
      if (mutatingMethods.has(target.name)) {
        throw `Error Calling Method: ${target.name}`;
      }
      return Reflect.apply(target, context, args);
    }
  }
    
  return new Proxy(inputObject, proxyHandler);
};

Create an immutable object in JavaScript by defining a function named createImmutableObject. This function wraps an input object using a Proxy to intercept and control interactions with the object's properties. Follow these details to grasp the workings of the code:

  • Define a Set called mutatingMethods containing method names that typically modify an object, such as 'pop', 'push', 'shift', 'unshift', 'splice', 'sort', and 'reverse'.
  • Construct a proxyHandler object to manage access and modifications to the target object:
    • set: This trap prevents any changes to properties of the target object. It throws an error when an attempt is made to set a property, with specific error messages depending on whether the target is an array or not.
    • get: This trap retrieves properties from the target. If the property value is not an object or function (or is null), it returns the value directly. Otherwise, it returns a new Proxy of the value, using the same handler to ensure nested objects are also immutable.
    • apply: If a function that belongs to the mutatingMethods set is called, this trap throws an error preventing the operation. Otherwise, it allows the function to execute normally using Reflect.apply.
  • Finally, the createImmutableObject function returns a new Proxy created with the input object and the defined proxyHandler, effectively making the input object immutable.

Use this implementation to safeguard objects against unwanted modifications, ensuring data integrity and consistency where mutations could lead to bugs or unexpected behavior.

Comments

No comments yet.