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).
- Primitives: If the iteratee resolves to a primitive value (e.g., string, number, boolean), the comparison succeeds if the values match.
- Objects: If the iteratee resolves to an intermediate nested object rather than a primitive, comparison is performed by reference, not by structure.
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.