How Lodash pullAllWith Handles Deep Comparisons
Lodash's _.pullAllWith method removes specified values
from an array by filtering out elements that match according to a custom
comparator function. Unlike methods that rely on standard strict
equality (===), _.pullAllWith enables deep
comparisons of complex nested objects and arrays when paired with a deep
equality comparator like _.isEqual. This article explains
how the method processes these comparisons, how it mutates the source
array, and how to implement it correctly.
The Limitation of Reference Equality
Standard JavaScript equality operators and Lodash methods like
_.pull or _.pullAll compare objects by
reference, not by structural value. Two distinct objects with identical
keys and values evaluate as unequal because they occupy different
locations in memory:
{ id: 1 } === { id: 1 }; // falseBecause of this behavior, standard removal methods fail when you attempt to pull an object out of an array using a separate object instance with the same properties.
Enabling Deep Equality with a Comparator
_.pullAllWith accepts three arguments: the array to
modify, the array of values to remove, and a comparator function:
_.pullAllWith(array, values, [comparator]);The comparator function takes two arguments:
comparator(arrVal, othVal). It compares an element from the
target array with an element from the values array, returning
true if they should be considered a match.
To perform deep comparisons, you supply a deep equality function as
the comparator. Lodash provides _.isEqual, which performs
an exhaustive structural check across nested objects, arrays, maps,
sets, and primitive wrappers:
const _ = require('lodash');
const users = [
{ id: 1, profile: { role: 'admin' } },
{ id: 2, profile: { role: 'editor' } },
{ id: 3, profile: { role: 'viewer' } }
];
const toRemove = [
{ id: 2, profile: { role: 'editor' } }
];
_.pullAllWith(users, toRemove, _.isEqual);
console.log(users);
// Output:
// [
// { id: 1, profile: { role: 'admin' } },
// { id: 3, profile: { role: 'viewer' } }
// ]Internal Execution and Mutation
When _.pullAllWith executes, it iterates through the
elements of the primary array. For each item, it checks against every
entry in the removal array using the supplied comparator. If
_.isEqual(arrVal, othVal) returns true, the
element in array is flagged for removal.
Key operational characteristics include:
- In-Place Mutation:
_.pullAllWithmodifies the original array directly rather than returning a new copy. - Recursive Verification: When
_.isEqualis used, the comparison traverses every level of the object hierarchy, comparing properties and nested values regardless of reference differences. - Partial or Custom Logic: You are not limited to
_.isEqual. If deep comparison is only needed on specific properties, you can supply a custom function (such as(a, b) => _.isEqual(a.nestedData, b.nestedData)) to optimize comparison performance.