Hone logo
Hone
Problems

Deep Cloning JavaScript Objects with Circular References

When copying JavaScript objects, a simple shallow copy often suffices. However, for complex data structures or when you need an entirely independent copy, a deep clone is necessary. This challenge focuses on the intricacies of deep cloning, specifically addressing the common pitfall of circular references, which can cause infinite loops and stack overflows with naive cloning approaches. Mastering this will equip you with a robust method for handling complex object copying scenarios.

Problem Description

Your task is to implement a function deepClone(obj) that creates a deep, independent copy of any JavaScript object. This function should correctly handle primitive types, arrays, and plain objects, as well as objects containing circular references. A circular reference occurs when an object property refers back to the object itself, or to another object that eventually refers back to the original object.

Key Requirements:

  • Handle Primitives: Copy primitive types (strings, numbers, booleans, null, undefined, symbols, bigints) by value.
  • Handle Arrays: Create new arrays and deep-clone their elements.
  • Handle Plain Objects: Create new objects and deep-clone their properties.
  • Handle Circular References: Detect and correctly reconstruct circular references in the cloned object, preventing infinite recursion.
  • Maintain Structure: The cloned object should have the same structure and data as the original object.
  • Independence: The cloned object and its nested structures should be entirely separate from the original. Modifications to the clone should not affect the original, and vice-versa.
  • Support for null: If the input is null, the output should also be null.

Expected Behavior:

The deepClone(obj) function should return a new object that is a perfect replica of obj, including all nested structures and correctly handled circular references.

Edge Cases:

  • Input is null.
  • Input is a primitive value.
  • Input is an empty object or array.
  • Input contains deeply nested objects and arrays.
  • Input contains circular references within nested structures.

Examples

Example 1:

Input: {
  name: "Alice",
  age: 30,
  address: {
    street: "123 Main St",
    city: "Anytown"
  },
  hobbies: ["reading", "hiking"]
}

Output: {
  name: "Alice",
  age: 30,
  address: {
    street: "123 Main St",
    city: "Anytown"
  },
  hobbies: ["reading", "hiking"]
}

Explanation: A straightforward deep clone of an object with nested structures and an array. The output is a new object with new nested objects and a new array.

Example 2:

Input: {
  value: 1,
  self: null // Will be updated to refer to the cloned object itself
}
const original = { value: 1 };
original.self = original;

Output: (a new object that looks like) {
  value: 1,
  self: (a reference to the new cloned object)
}

Explanation: This demonstrates a simple circular reference where an object's property points back to itself. The `deepClone` function must detect this and ensure the cloned `self` property points to the newly created cloned object, not the original.

Example 3:

Input: {
  data: {
    items: [1, 2, 3]
  },
  config: {}
}
const original = { data: { items: [1, 2, 3] }, config: {} };
original.data.items.push(original.data); // Circular reference in the array
original.config.parent = original;      // Circular reference to the main object

Output: (a new object that looks like) {
  data: {
    items: [1, 2, 3, (a reference to the cloned data.items array)]
  },
  config: {
    parent: (a reference to the cloned object)
  }
}

Explanation: This example features more complex circular references involving both an array and an object property pointing back to higher levels of the structure. The `deepClone` must correctly resolve these to maintain the object's integrity.

Constraints

  • The input obj can be any valid JavaScript value, including null, primitives, arrays, and plain objects.
  • The function should not mutate the original object.
  • The cloning process should be reasonably efficient for typical object sizes, avoiding excessive overhead. For extremely large or deeply nested structures, performance may be a consideration, but the primary goal is correctness.

Notes

  • Consider how you will keep track of objects that have already been cloned during the traversal of the original object. This is crucial for handling circular references.
  • Think about the difference between plain objects (created with {} or new Object()) and other object types (like Date, RegExp, functions, etc.). For this challenge, focus on cloning plain objects and arrays. You don't need to deep clone instances of Date, RegExp, etc., but they should be copied correctly (e.g., Date objects should create new Date instances with the same time value).
  • A Map or WeakMap is often a good choice for tracking visited objects and their corresponding clones.
Loading editor...
javascript