Constant Iteratee in Lodash sortedLastIndexBy
Using a mismatched iteratee that returns a constant value in Lodash's
_.sortedLastIndexBy breaks the core binary search
assumption that values are ordered, causing the function to return an
incorrect insertion index—typically the full length of the array
(array.length) or index 0. This behavior leads
to silent application bugs where elements are inserted into invalid
positions without triggering any runtime errors or warnings.
How
_.sortedLastIndexBy Operates
The _.sortedLastIndexBy method uses a binary search
algorithm to identify the highest index at which a given
value should be inserted into an already-sorted
array to maintain its sorted order. It calls the iteratee
function on each evaluated array element and on the value
argument to compute a sort key before performing comparisons:
// Expected usage
const objects = [{ x: 4 }, { x: 5 }];
_.sortedLastIndexBy(objects, { x: 4 }, (o) => o.x); // Returns 1The Mechanism of Failure with Constant Iteratees
A binary search relies on a strict invariant: the computed keys must be monotonic (non-decreasing). When an iteratee returns a constant value, this invariant collapses. Two primary scenarios occur depending on whether the constant applies universally or creates a mismatch:
1. Universal Constant Output
If the iteratee returns the same constant for both the target value
and all elements in the array (e.g., o => 'constant' or
referencing a non-existent property that evaluates to
undefined on both sides):
iteratee(value)equalsiteratee(array[mid])at every step.- Because
_.sortedLastIndexByseeks the last valid index, equality comparisons push the search partition to the right (low = mid + 1). - The loop continues halving the search space upward until
lowreachesarray.length. - Result: The function returns
array.length, regardless of where the element actually belongs.
2. Mismatched Target vs. Array Constant Output
If the iteratee returns a constant for array elements (due to missing properties, fallback bugs, or hardcoded lambdas), but yields a different value for the search target:
- Target evaluated as smaller: If
iteratee(value) < constant, the search continuously partitions to the left, returning0. - Target evaluated as larger: If
iteratee(value) > constant, the search continuously partitions to the right, returningarray.length.
Practical Consequences in Applications
- Corrupted Array Invariants: If the returned index
is passed to
Array.prototype.splice(index, 0, value)to maintain an ordered list, the list loses its sorted structure. Subsequent binary searches on this array will fail unpredictably. - Silent Failures: Because
_.sortedLastIndexBytreats any constant—includingundefined,null, or static strings—as valid comparison outputs, JavaScript will not throw aTypeErroror runtime exception. - Degraded Lookup Predictability: When attempting to find insertion boundaries for deduplication or range slicing, constant return values cause downstream operations to skip ranges or append duplicates at the tail end of the dataset.
Prevention
To avoid constant iteratee behavior:
- Ensure the iteratee property string or callback references an existing, populated key in both the collection items and the target value.
- Avoid iteratees with static fallbacks (e.g.,
o => o.timestamp || 0) if the fallback disrupts the sorting sequence. - Validate datasets before executing binary searches, ensuring that missing keys are handled prior to index calculation.