Lodash uniqWith Comparator Recursive Limit Explained
Lodash's _.uniqWith method allows developers to create
duplicate-free arrays using a custom comparator function to determine
element equivalence. While developers often wonder about the internal
recursion limits imposed on this comparator, Lodash itself enforces no
recursive limit. Because _.uniqWith evaluates elements
iteratively rather than recursively, any recursion limits encountered
are entirely governed by the host JavaScript engine's maximum call stack
size.
How Lodash Executes the Custom Comparator
Under the hood, _.uniqWith relies on Lodash’s internal
baseUniq implementation. When a custom comparator is
provided, baseUniq iterates through the target array using
a standard while or for loop. For each
element, it runs another loop (typically via
arrayIncludesWith) to compare the current value against
already accumulated unique values.
Because Lodash drives this comparison process using iterative loops,
the library itself introduces zero call stack frames between elements.
It simply invokes comparator(arrVal, othVal) on each
iteration.
The JavaScript Engine Call Stack Limit
Because Lodash does not manage recursion depth, any recursion that occurs must originate inside the custom comparator itself—for instance, when traversing deeply nested or recursive data structures. In these scenarios, the recursion limit is determined solely by the JavaScript engine running the code:
- V8 (Node.js and Google Chrome): Typically allows between 10,000 and 15,000 recursive calls, depending on the size of the stack frames.
- SpiderMonkey (Mozilla Firefox): Often supports larger stack depths, frequently exceeding 50,000 frames.
- JavaScriptCore (Safari): Implements dynamic limits that generally trigger between 10,000 and 40,000 calls depending on available memory.
Exceeding these limits causes the runtime to throw an uncatchable or
terminating
RangeError: Maximum call stack size exceeded.
Handling Recursion with _.isEqual
A frequent pattern is passing _.isEqual as the custom
comparator to _.uniqWith to compare objects by value. While
_.isEqual performs deep object comparisons, it does not
suffer from runaway recursion when encountering circular references.
Lodash equips _.isEqual with an internal stack mechanism
(a Stack cache using Map or pairwise arrays)
that tracks previously compared object references. If a circular
reference is detected, comparison stops for that branch, preventing
stack overflows. However, extremely deep (non-circular) object trees
that exceed thousands of nested properties can still exhaust the
engine's call stack.
Preventing Stack Overflows
To ensure recursive custom comparators do not break execution during
_.uniqWith runs:
- Use Iteration for Deep Trees: If comparing objects
with arbitrary depth, rewrite the traversal logic using an explicit
stack array and a
whileloop rather than recursive function calls. - Sanitize Data Depth: Flatten or normalize nested structures before passing them to array deduplication.
- Guard Against Cyclic Data: When implementing custom
deep comparators without
_.isEqual, track visited object references using aWeakSetorSetto prevent infinite loops.