Custom Comparator in Lodash uniqWith Explained
In the Lodash JavaScript library, _.uniqWith produces a
duplicate-free version of an array by relying on a custom comparator
function to determine element equality. This article explains the
fundamental role of this comparator, how it bypasses standard
reference-based equality checks, its execution mechanics, and practical
scenarios for deduplicating complex data structures like nested arrays
and objects.
The Purpose of the Custom Comparator
Standard deduplication methods, such as JavaScript's native
Set or Lodash's base _.uniq method, rely on
the SameValueZero comparison algorithm. While this works
reliably for primitive data types like numbers and strings, it fails
when dealing with reference types. In JavaScript, two distinct objects
with identical properties are not equal by reference:
{ id: 1 } === { id: 1 } // falseThe custom comparator in _.uniqWith solves this
limitation. It is a user-defined function that provides the logic for
determining whether two items represent a duplicate, shifting the
comparison from reference identity to value or semantic equivalence.
How the Comparator Works
The comparator function accepts two arguments—representing two elements from the target array—and returns a boolean:
arrVal: The value being evaluated from the array.othVal: A value already retained in the unique list.- Return Value:
trueif the items should be treated as duplicates, orfalseif they are distinct.
During execution, _.uniqWith iterates over the input
array. For every candidate element, it runs the comparator against the
items that have already been accepted as unique. If the comparator
evaluates to true for any comparison, the candidate is
flagged as a duplicate and omitted from the final array. Only the first
occurrence of an element is retained.
Common Use Cases
1. Deep Object Comparison
The most common implementation pairs _.uniqWith with
Lodash’s deep equality checker, _.isEqual. This removes
objects that share identical structures and values, regardless of their
location in memory.
const records = [
{ id: 101, details: { active: true } },
{ id: 102, details: { active: false } },
{ id: 101, details: { active: true } }
];
const uniqueRecords = _.uniqWith(records, _.isEqual);
// Result: Retains only the first and second objects2. Matching on Specific Properties
When deduplication needs to occur based on a partial subset of fields, an inline comparator can target specific keys.
const users = [
{ id: 1, name: "Alice", role: "Admin" },
{ id: 1, name: "Alice", role: "User" },
{ id: 2, name: "Bob", role: "User" }
];
const uniqueById = _.uniqWith(users, (a, b) => a.id === b.id);
// Result: Retains the first Alice entry and the Bob entry3. Approximate or Custom Matching
Comparators can also handle domain-specific equivalence, such as case-insensitive string matching or floating-point tolerance comparisons.
const coordinates = [
{ x: 10.001, y: 20.002 },
{ x: 10.000, y: 20.000 },
{ x: 15.000, y: 30.000 }
];
// Deduplicate if distance is within a tolerance of 0.01
const uniqueCoords = _.uniqWith(coordinates, (a, b) => {
return Math.hypot(a.x - b.x, a.y - b.y) < 0.01;
});Performance Considerations
Because the custom comparator runs pairwise comparisons between
elements, _.uniqWith generally has a time complexity of
O(n²). For large collections, performing computationally heavy logic
inside the comparator (such as deep serialization or complex
calculations) will degrade performance. When deduplicating strictly by a
single primitive key (like an ID), using _.uniqBy or a
native Map approach provides an O(n) alternative.