Hone logo
Hone
Problems

Deep Freeze: Immutable Objects in JavaScript

JavaScript objects are mutable by default, meaning their properties can be changed after creation. This can lead to unexpected side effects in complex applications. A deep freeze aims to prevent any modification to an object, including its nested properties, ensuring true immutability. This challenge asks you to implement a deepFreeze function that recursively freezes all properties of an object.

Problem Description

Your task is to implement a JavaScript function called deepFreeze that takes a single argument: an object (obj). This function should recursively freeze the input object and all of its own enumerable properties, including nested objects and arrays.

Key Requirements:

  1. Recursive Freezing: The function must recursively freeze all properties of the input object. If a property is itself an object or an array, it should also be deeply frozen.
  2. Own Properties Only: The function should only freeze the object's own enumerable properties. Inherited properties should not be affected.
  3. Return Value: The function should return the frozen object. This is primarily for chaining or convenience, as the freezing happens in-place.
  4. No Modification: After calling deepFreeze, the original object and all its nested structures should be completely immutable. Attempting to change them should result in an error or no effect (depending on strict mode).

Expected Behavior:

  • Calling Object.isFrozen(obj) on the object and its nested objects/arrays should return true.
  • Attempting to add, delete, or modify properties of the frozen object (or its nested structures) should fail. In strict mode, this will throw a TypeError.

Edge Cases to Consider:

  • Non-object inputs: What happens if null, undefined, primitives (strings, numbers, booleans, symbols) are passed?
  • Empty objects/arrays: The function should handle these gracefully.
  • Circular references: While Object.freeze itself handles circular references without infinite loops, your implementation should be mindful of this. If your recursive approach isn't careful, it could lead to stack overflow.

Examples

Example 1:

const obj1 = {
  a: 1,
  b: {
    c: 2,
    d: [3, 4]
  }
};

const frozenObj1 = deepFreeze(obj1);

console.log(Object.isFrozen(frozenObj1)); // Output: true
console.log(Object.isFrozen(frozenObj1.b)); // Output: true
console.log(Object.isFrozen(frozenObj1.b.d)); // Output: true

// Attempting to modify (will throw TypeError in strict mode or fail silently otherwise)
// frozenObj1.a = 5;
// frozenObj1.b.c = 10;
// frozenObj1.b.d.push(5);

Explanation: The obj1 is deeply frozen. All its properties (a, b) and nested properties (b.c, b.d) are now immutable. The output confirms that the original object and its nested structures are frozen.

Example 2:

const arr1 = [1, { name: "test" }, [2, 3]];
const frozenArr1 = deepFreeze(arr1);

console.log(Object.isFrozen(frozenArr1)); // Output: true
console.log(Object.isFrozen(frozenArr1[1])); // Output: true
console.log(Object.isFrozen(frozenArr1[2])); // Output: true

// Attempting to modify
// frozenArr1[0] = 100;
// frozenArr1[1].name = "modified";
// frozenArr1[2].push(4);

Explanation: An array is treated as an object. The array arr1 and its elements (including the nested object and array) are deeply frozen.

Example 3:

const obj3 = {};
const frozenObj3 = deepFreeze(obj3);
console.log(Object.isFrozen(frozenObj3)); // Output: true

const nullInput = null;
const frozenNull = deepFreeze(nullInput); // Should likely return null without error

const primitiveInput = 5;
const frozenPrimitive = deepFreeze(primitiveInput); // Should likely return 5 without error

Explanation: Handles empty objects and non-object inputs gracefully. Non-object inputs should be returned as-is without attempting to freeze them.

Constraints

  • The function must work with standard JavaScript objects, arrays, and their primitives.
  • The solution should be efficient enough for reasonably sized objects and their nested structures. Avoid brute-force iteration that could lead to excessive time complexity.
  • The function must use Object.freeze() or Object.seal() (though freeze is generally preferred for full immutability). Object.freeze() is the most direct way to achieve immutability.
  • The function should handle circular references correctly without causing infinite loops or stack overflows.

Notes

  • Consider using Object.getOwnPropertyNames() or Object.getOwnPropertySymbols() along with Object.getOwnPropertyDescriptor() to iterate over own properties.
  • Remember to check if a property's value is an object or an array before recursively calling deepFreeze on it.
  • Object.freeze() makes objects immutable:
    • No new properties can be added.
    • Existing properties cannot be removed.
    • Existing properties cannot be changed.
    • The enumerability, configurability, and writability of existing properties cannot be changed.
  • Object.freeze() recursively freezes the top-level object but not its nested objects. This is precisely what your deepFreeze function needs to accomplish.
  • Think about how to avoid re-processing already frozen objects or objects within a circular reference.
Loading editor...
javascript