How Lodash _.merge Handles Nested Arrays

This article explores the deep reconciliation strategies employed by Lodash’s _.merge method when resolving conflicts between nested array structures. Unlike shallow merging or naive array concatenation, _.merge treats arrays using an index-based recursive alignment model. Below, we break down the specific rules Lodash applies to conflicting indices, how it handles varying array lengths and primitive-versus-object values, and how developers can override these defaults using custom reconcilers.

Index-Based Key Alignment

The foundational strategy of _.merge is treating arrays as objects with sequential numeric keys (0, 1, 2, ...). When encountering an array in both the target and the source at the same path, Lodash does not perform a list concatenation or a mathematical union. Instead, it pairs elements by their matching index:

This strategy ensures that the position of an element dictates its merge counterpart, mirroring the behavior of named keys in standard JavaScript objects.

Value Resolution: Primitives vs. Objects

At each index position, Lodash determines how to reconcile the conflicting elements based on their data types:

  1. Primitive Overwrite: If the element at a given index in the source is a primitive (string, number, boolean, null, etc.), it replaces the value at that same index in the target.
  2. Object and Array Recursion: If the elements at the matching index in both the target and source are plain objects or arrays, _.merge descends into them recursively. For nested objects, keys are merged deeply; for nested arrays, the index-matching strategy repeats.
  3. Type Mismatch: If the target element is a primitive and the source element is an object or array (or vice versa), the source element completely replaces the target element, converting the type at that index.

Array Sizing and Residual Elements

When the target and source arrays differ in length, _.merge applies an expansion strategy without truncating existing data:

Deep Reconciliation Example

Consider a scenario where arrays contain nested objects:

const target = {
  users: [
    { id: 1, preferences: { theme: 'light', alerts: true } },
    { id: 2, name: 'Alice' }
  ]
};

const source = {
  users: [
    { preferences: { theme: 'dark' } },
    { id: 2, name: 'Alice', role: 'admin' },
    { id: 3, name: 'Bob' }
  ]
};

When passed to _.merge(target, source), Lodash performs the following steps:

Customizing Array Strategies via _.mergeWith

Because the default index-keyed strategy can lead to accidental mutations when developers expect array concatenation or deduplication, Lodash provides _.mergeWith. This variant allows the injection of a custom reconciler function:

import _ from 'lodash';

function customizer(objValue, srcValue) {
  if (_.isArray(objValue)) {
    return objValue.concat(srcValue);
  }
}

_.mergeWith(target, source, customizer);

By intercepting the reconciliation pipeline before the recursive index mapping occurs, developers can implement alternative strategies, such as set unions, unique element filtering, or complete array replacements.