diff --git a/coding-exercise/deep-merge-nested-objects/README.md b/coding-exercise/deep-merge-nested-objects/README.md new file mode 100644 index 00000000..253be514 --- /dev/null +++ b/coding-exercise/deep-merge-nested-objects/README.md @@ -0,0 +1,62 @@ +Deep-Merge Two Nested Objects (with Circular References) +Challenge: +Implement a deep-merge function that combines two nested JavaScript objects while safely handling circular references. The function must be immutable and able to merge objects, arrays, and nested structures. + +Problem Description: +Deep merging means recursively combining the properties of two objects. When both inputs contain nested objects, arrays, or shared references, this becomes more complex. + +Your task is to: +Merge all properties from both objects +Recursively merge nested objects +Merge arrays by index +Avoid infinite recursion when circular references exist +Return a new, immutable object +Ensure values from the second object override the first when conflicts occur +Real-World Use Case +State management libraries (Redux, Zustand, Immer) +Complex configuration merging +Deep cloning with overrides +Normalizing API responses +Merging schema definitions + +Example: +Input +const a = { + x: 1, + y: { z: 2 } +}; +a.self = a; // circular reference + +const b = { + y: { k: 20 }, + m: 100 +}; +b.loop = b; // circular reference + +const result = deepMerge(a, b); + +Output +{ + x: 1, + y: { z: 2, k: 20 }, + m: 100, + self: [circular], + loop: [circular] +} + +Requirements: +Must deep-merge nested objects and arrays +Must detect and safely handle circular references +Must not mutate inputs +When both objects contain the same key: +If both values are objects → recursively merge +If both are arrays → merge by index +Otherwise → value from second object overrides +Must create a brand-new object as the output + +Key Concepts: +Recursion +WeakMap to track visited nodes +Object and array cloning +Circular reference detection +Immutability principles \ No newline at end of file diff --git a/coding-exercise/deep-merge-nested-objects/question.md b/coding-exercise/deep-merge-nested-objects/question.md new file mode 100644 index 00000000..fd414103 --- /dev/null +++ b/coding-exercise/deep-merge-nested-objects/question.md @@ -0,0 +1,51 @@ +# Exercise: Deep-Merge Two Nested Objects (with Circular References) + +## Problem Statement + +Write a function **`deepMerge(objA, objB)`** that deeply merges two JavaScript objects **without mutating** either of them. +The function must correctly merge nested structures and safely handle circular references. + +--- + +## Rules + +1. If a key exists only in one object → copy that value. +2. If a key exists in both objects: + - If both values are **plain objects**, recursively merge them. + - If both values are **arrays**, merge by index (element-wise). + - Otherwise, the value from **`objB`** overrides the value from **`objA`**. +3. Handle **circular references** using a tracking mechanism like **WeakMap**. +4. Always return a **new**, immutable object. +5. Must preserve structure even when circular references appear on both inputs. + +--- + +## Example + +```js +const a = { + x: { y: 1 }, + arr: [1, 2] +}; +a.self = a; // circular + +const b = { + x: { z: 2 }, + arr: [3], + extra: true +}; +b.loop = b; // circular + +const result = deepMerge(a, b); + +/* +Expected structure: + +{ + x: { y: 1, z: 2 }, + arr: [1, 3], // merged by index + extra: true, + self: , + loop: +} +*/ diff --git a/coding-exercise/deep-merge-nested-objects/solution.js b/coding-exercise/deep-merge-nested-objects/solution.js new file mode 100644 index 00000000..3362907a --- /dev/null +++ b/coding-exercise/deep-merge-nested-objects/solution.js @@ -0,0 +1,50 @@ +/** + * Deeply merges two objects while safely handling circular references. + * Returns a new object without mutating the originals. + * + * @param {Object|Array} obj1 + * @param {Object|Array} obj2 + * @returns {Object|Array} + */ +function deepMerge(obj1, obj2, visited = new WeakMap()) { + // If obj2 is primitive, return it directly + if (obj2 === null || typeof obj2 !== "object") { + return obj2; + } + + // Detect circular reference + if (visited.has(obj2)) { + return visited.get(obj2); + } + + // Determine output type + const output = Array.isArray(obj1) ? [...obj1] : + Array.isArray(obj2) ? [...obj2] : + { ...obj1 }; + + visited.set(obj2, output); + + for (const key in obj2) { + if (!obj2.hasOwnProperty(key)) continue; + + const val1 = obj1 ? obj1[key] : undefined; + const val2 = obj2[key]; + + if (val1 !== undefined && typeof val1 === "object" && typeof val2 === "object") { + output[key] = deepMerge(val1, val2, visited); + } else { + output[key] = val2; + } + } + + return output; +} + +// Example usage +const a = { x: 1, y: { z: 2 } }; +a.self = a; + +const b = { y: { k: 20 }, m: 100 }; +b.loop = b; + +console.log(deepMerge(a, b));