Lodash Array Intersection with Objects Edge Cases

Intersecting arrays of objects in Lodash is a common operation that frequently introduces subtle bugs if developers rely on default behaviors. While Lodash provides methods like _.intersection, _.intersectionBy, and _.intersectionWith, comparing non-primitive types introduces complexities surrounding reference equality, deep structural equivalence, property path collisions, duplicate retention, and performance trade-offs. This article covers the specific edge cases encountered when performing object array intersections using Lodash and how to handle them correctly.

Reference Equality Failures in _.intersection

The default _.intersection method uses the SameValueZero comparison algorithm. Because JavaScript objects are compared by reference rather than by value, two distinct objects with identical structures and properties are not considered equal.

const array1 = [{ id: 1 }];
const array2 = [{ id: 1 }];

_.intersection(array1, array2); 
// Returns: []

The method only matches elements if they point to the exact same memory reference. To compare objects by structural value, developers must use _.intersectionWith alongside a comparator like _.isEqual.

Unintended Merging from Missing Keys in _.intersectionBy

When using _.intersectionBy with an iteratee shorthand (such as a string property path), objects lacking that property evaluate to undefined. If multiple objects in either array lack the target property, Lodash treats their keys as identical.

const listA = [{ id: 1 }, { name: 'Alpha' }];
const listB = [{ id: 2 }, { name: 'Beta' }];

_.intersectionBy(listA, listB, 'id');
// Returns: [{ name: 'Alpha' }]

In this scenario, both { name: 'Alpha' } and { name: 'Beta' } yield undefined for the 'id' property. Because undefined === undefined, Lodash treats them as matching elements and retains the item from the first array.

First-Occurrence Bias and Property Discarding

When intersections match objects based on a subset of properties (such as using _.intersectionBy with an id), the resulting array only includes the instance found in the first array. Any unique metadata stored exclusively on the matching object in the second array is omitted.

const currentUsers = [{ id: 10, role: 'admin' }];
const incomingUsers = [{ id: 10, role: 'editor', lastLogin: '2025-01-01' }];

const result = _.intersectionBy(currentUsers, incomingUsers, 'id');
// Returns: [{ id: 10, role: 'admin' }]

If the intention is to merge matching objects rather than merely filter the first array, _.intersection variants are insufficient on their own and require a subsequent merge strategy.

Automatic Deduplication

Lodash intersection methods inherently produce unique arrays. If the first array contains duplicate objects that match criteria in subsequent arrays, only the first occurrence is kept in the output.

const primary = [{ id: 1 }, { id: 1 }];
const secondary = [{ id: 1 }];

_.intersectionBy(primary, secondary, 'id');
// Returns: [{ id: 1 }] (length of 1, not 2)

If the business logic requires preserving duplicate instances from the source collection, standard array filtering via Array.prototype.filter should be used instead of Lodash intersection utilities.

Mutation of Shared References

Lodash returns a new array containing references to the original objects; it does not clone matched objects. Modifying an object in the intersected output directly mutates the object in the source collections.

const original = [{ id: 1, status: 'pending' }];
const filterList = [{ id: 1 }];

const matched = _.intersectionBy(original, filterList, 'id');
matched[0].status = 'complete';

console.log(original[0].status); 
// Outputs: 'complete'

Deep cloning must be implemented explicitly if the intersected results need to be manipulated independently from the source data.

Performance Degradation with _.intersectionWith

While _.intersectionBy typically maps elements to a Set-like lookup for linear \(O(n + m)\) complexity, _.intersectionWith compares each element across arrays using the custom comparator function. Using expensive comparators like _.isEqual across large arrays leads to \(O(n \times m)\) computational complexity, which can cause significant performance bottlenecks when processing large datasets or deeply nested object structures.