How Lodash _.merge Recursively Combines Objects
Lodash's _.merge method is a utility designed to deeply
combine properties from one or more source objects into a destination
object. Unlike shallow merge utilities, _.merge inspects
the data types of matching keys, recursively traversing nested objects
and arrays rather than replacing entire child structures. This article
breaks down the exact mechanics of how _.merge walks nested
object trees, resolves conflicting data types, handles arrays, and
mutates target data.
The Core Recursive Traversal Engine
When _.merge(target, ...sources) is invoked, Lodash
iterates over the enumerable own and inherited string/symbol keyed
properties of each source object.
For every key-value pair encountered, the method inspects the destination object's corresponding property:
- Both Values are Plain Objects: If both the target
value and the source value are mergeable objects (such as plain
JavaScript objects),
_.mergedoes not overwrite the property. Instead, it recurses into both values, repeating the merge operation at the deeper hierarchy level. - Value Type Mismatches or Primitives: If either the source or target value is a primitive (such as a string, number, or boolean), or an object that is not safely mergeable (like DOM elements or functions), the source value completely overwrites the target value.
- Missing Keys: If a key exists in the source but does not exist in the target, the property and its nested structure are copied directly over to the target.
Array Handling and Index-Based Merging
A common point of confusion is how _.merge handles
arrays. Instead of concatenating arrays or replacing the target array
with the source array, _.merge treats arrays like
index-keyed objects.
- Elements at matching indices are recursively merged if they are objects.
- Primitive elements at matching indices in the source overwrite the corresponding elements in the target.
- If the source array has more elements than the target array, the extra elements are appended to the target array.
const target = { items: [{ id: 1, name: "Alpha" }, "two"] };
const source = { items: [{ name: "Omega", status: "active" }, "replaced", "three"] };
_.merge(target, source);
// Result:
// {
// items: [
// { id: 1, name: "Omega", status: "active" },
// "replaced",
// "three"
// ]
// }Treatment of
undefined and Non-Plain Objects
_.merge explicitly skips source properties that evaluate
to undefined. If the target object has an existing value
and the corresponding source property is undefined, the
target's original value remains unchanged. However, explicit
null values in the source will overwrite values in
the target.
Complex native objects such as Date and
RegExp instances are copied by value rather than deeply
merged. Custom class instances with non-standard prototypes are
generally overwritten rather than recursively merged to prevent
prototype pollution and unintended side effects.
Target Mutation
_.merge operates by mutating the target
object passed as the first argument, while also returning that mutated
object as the final result. If an immutable operation is required, an
empty object literal should be supplied as the initial target:
const finalObject = _.merge({}, originalObject, updates);By leveraging this recursive algorithm, _.merge allows
complex, deeply nested state configurations and data structures to be
patched cleanly without manually maintaining or rebuilding intermediate
object hierarchies.