HardPro challengeJavaScriptTypeScript

Deep Merge with Recursive Types

TypeScriptTypesObjects

deepMerge(target, source) merges two objects recursively. It's a common utility for merging configs, themes, or default values with overrides.

Rules

  • Primitive source values override target values
  • Arrays from source replace (not concat) target arrays
  • Object values are merged recursively
  • Keys in target not in source are preserved
  • No mutation — return a new object
deepMerge(
  { a: 1, b: { x: 10, y: 20 }, c: [1, 2] },
  { b: { y: 99, z: 30 }, c: [7, 8, 9] }
)
// → { a: 1, b: { x: 10, y: 99, z: 30 }, c: [7, 8, 9] }
//          ^ preserved  ^ overridden  ^ new key       ^ array replaced

TypeScript type

The recursive DeepMerge<A, B> utility type produces the correct output type:

type DeepMerge<A, B> = {
  [K in keyof A | keyof B]:
    K extends keyof B
      ? K extends keyof A
          ? A[K] extends object ? DeepMerge<A[K], B[K]> : B[K]
          : B[K]
      : K extends keyof A ? A[K] : never;
};

Sample tests

Test #1merge disjoint keys
Input: [{"a":1},{"b":2}]
Output: {"a":1,"b":2}
Test #2source overrides shared primitive key
Input: [{"a":1,"b":2},{"b":99,"c":3}]
Output: {"a":1,"b":99,"c":3}
Test #3nested object is merged recursively
Input: [{"a":{"x":1,"y":2}},{"a":{"y":99,"z":3}}]
Output: {"a":{"x":1,"y":99,"z":3}}
Test #4source array replaces target array
Input: [{"tags":[1,2]},{"tags":[7,8,9]}]
Output: {"tags":[7,8,9]}
Test #5combined: preserve, override, add, replace array
Input: [{"a":1,"b":{"x":10,"y":20},"c":[1,2]},{"b":{"y":99,"z":30},"c":[7,8,9]}]
Output: {"a":1,"b":{"x":10,"y":99,"z":30},"c":[7,8,9]}