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 } // false

The 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:

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 objects

2. 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 entry

3. 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.