Lodash xorWith with Complex Object Arrays
This article explains how the Lodash utility library’s
_.xorWith function handles symmetric differences between
arrays of complex objects. You will learn how _.xorWith
operates under the hood, how it overcomes the limitations of
reference-based comparisons using custom comparator functions, and how
to implement it effectively using deep equality and custom property
matching.
In JavaScript, objects are compared by reference rather than by
value. Standard difference methods like Lodash's _.xor use
SameValueZero equality, meaning two separate objects with identical keys
and values are considered distinct because they point to different
memory addresses. _.xorWith solves this problem by
accepting an array of target arrays followed by a comparator function
that defines what makes two elements equal.
The function computes the symmetric difference (the values that are
present in one array or the other, but not both) by iteratively
comparing elements across the provided arrays using the comparator. If
the comparator evaluates to true for a pair of items, both
are treated as matching and excluded from the resulting array. Any
object that does not find a match based on the comparator is
preserved.
Basic Syntax and Parameters
_.xorWith([arrays], [comparator])[arrays]: The arrays to inspect.[comparator]: The function invoked per element to compare values ((arrVal, othVal) => boolean).
Deep Equality with
_.isEqual
When working with complex objects that contain nested properties, the
most common approach is pairing _.xorWith with Lodash’s
deep comparison method, _.isEqual.
const _ = require('lodash');
const listA = [
{ id: 1, profile: { name: 'Alice', role: 'Admin' } },
{ id: 2, profile: { name: 'Bob', role: 'User' } }
];
const listB = [
{ id: 2, profile: { name: 'Bob', role: 'User' } },
{ id: 3, profile: { name: 'Charlie', role: 'User' } }
];
const result = _.xorWith(listA, listB, _.isEqual);In this scenario, result contains:
[
{ id: 1, profile: { name: 'Alice', role: 'Admin' } },
{ id: 3, profile: { name: 'Charlie', role: 'User' } }
]The object representing "Bob" is present in both arrays with
identical nested properties. _.isEqual evaluates the values
deeply, identifies them as equivalent, and _.xorWith
filters them out.
Custom Property Comparators
You do not need to evaluate the entire object structure if only specific attributes determine uniqueness. You can provide an inline function to compare specific keys, such as unique identifiers:
const currentUsers = [
{ id: 101, status: 'active', lastLogin: '2023-01-01' },
{ id: 102, status: 'pending', lastLogin: '2023-02-01' }
];
const updatedUsers = [
{ id: 101, status: 'active', lastLogin: '2023-05-15' },
{ id: 103, status: 'active', lastLogin: '2023-05-20' }
];
// Compare only by the 'id' field
const idDifference = _.xorWith(currentUsers, updatedUsers, (a, b) => a.id === b.id);Even though the object with id: 101 has differing
lastLogin values, the custom comparator treats them as
identical based on a.id === b.id, excluding it from the
output. Only the objects with IDs 102 and 103
are returned.
Performance Considerations
_.xorWith operates with an approximate time complexity
of \(O(n \times m)\), where \(n\) and \(m\) are the lengths of the arrays being
compared, as it evaluates candidate pairs against the comparator. When
dealing with deep equality checks via _.isEqual on massive
datasets or deeply nested structures, this can introduce performance
overhead. In high-throughput environments with large datasets, mapping
objects to unique primitive keys (such as an ID string) using a
Map or Set may provide faster alternatives.
For typical application state management and data synchronization,
_.xorWith provides a clean, declarative solution for
complex array operations.