Lodash pullAllBy with Deeply Nested Objects

The _.pullAllBy method in Lodash is designed to mutate an original array by removing all elements that match items in a secondary array based on a resolved criterion. When working with arrays containing deeply nested objects, _.pullAllBy relies on an iteratee—either a string path or a custom function—to traverse nested properties and extract comparison keys. This article explains how property resolution works, how equality is evaluated, and how to effectively remove nested objects from an array using this method.

How _.pullAllBy Resolves Nested Properties

The signature of _.pullAllBy accepts three arguments: the target array to mutate, the array of values to exclude, and the iteratee used to produce the comparison value:

_.pullAllBy(array, values, [iteratee=_.identity])

When dealing with deeply nested objects, Lodash allows you to pass a dot-notation string or an array path as the iteratee. Lodash internally invokes _.property, allowing it to safely navigate deep object graphs.

const users = [
  { id: 1, profile: { meta: { code: 'A1' } } },
  { id: 2, profile: { meta: { code: 'B2' } } },
  { id: 3, profile: { meta: { code: 'C3' } } }
];

const toRemove = [
  { profile: { meta: { code: 'A1' } } },
  { profile: { meta: { code: 'C3' } } }
];

_.pullAllBy(users, toRemove, 'profile.meta.code');

// users is now: [{ id: 2, profile: { meta: { code: 'B2' } } }]

In this case, Lodash resolves 'profile.meta.code' on every element in users and every element in toRemove. It extracts the primitive string values ('A1', 'B2', 'C3') and filters the target collection accordingly.

Using Custom Iteratee Functions for Dynamic Traversal

If the nested structure contains optional paths, array indices, or requires computed logic before comparison, a callback function can be used as the iteratee:

const inventory = [
  { sku: 'X', details: { specs: [{ weight: 10 }] } },
  { sku: 'Y', details: { specs: [{ weight: 20 }] } }
];

const pullList = [
  { details: { specs: [{ weight: 10 }] } }
];

_.pullAllBy(inventory, pullList, (item) => item.details?.specs?.[0]?.weight);

// inventory is now: [{ sku: 'Y', details: { specs: [{ weight: 20 }] } }]

Equality Evaluation Mechanism

_.pullAllBy does not perform deep recursive object equality checking on the items themselves. Instead, it computes the iteratee value for each item in both arrays and compares the results using the SameValueZero algorithm (similar to strict equality ===, but treating NaN as equal to NaN).

For example, if the iteratee resolves to profile.meta and returns an object reference { code: 'A1' }, two distinct objects with the same properties will not match unless they share the same memory reference. To match deeply nested objects reliably, ensure the iteratee points directly to a primitive identifier or unique property.

Handling Missing Nested Paths

If an object lacks the specified path, Lodash’s property accessor safely returns undefined without throwing an error. Both the target item and the removal candidate will resolve to undefined. Consequently, any object in the primary array missing the nested path will be removed if any item in the exclusion list also resolves to undefined. To prevent unintentional removals, ensure the dataset is normalized or validate paths inside a custom iteratee function.