How Lodash sortedIndexOf Finds Array Elements
The _.sortedIndexOf method in the Lodash JavaScript
library is an optimized search utility designed specifically for arrays
that are already sorted in ascending order. Instead of checking elements
sequentially from beginning to end, it implements a binary search
algorithm to pinpoint the exact index of an element in \(O(\log n)\) time. This article breaks down
the internal mechanics of _.sortedIndexOf, explains how it
locates the lowest matching index, and highlights why it outperforms
standard linear search approaches on large datasets.
The Binary Search Mechanism
Standard array lookup methods like JavaScript's native
Array.prototype.indexOf or Lodash's _.indexOf
use linear search. Linear search iterates through the array index by
index from left to right, resulting in a time complexity of \(O(n)\). When dealing with large arrays
containing thousands or millions of elements, this approach can become a
performance bottleneck.
Because _.sortedIndexOf assumes the target array is
already sorted, it bypasses the linear scan entirely and uses a binary
search. The binary search operates through a divide-and-conquer
strategy:
- Establish Boundaries: The algorithm maintains two
pointers representing the current search range: a lower boundary
(initially index
0) and an upper boundary (initiallyarray.length - 1). - Determine the Midpoint: In each iteration, it
calculates the midpoint index between the lower and upper bounds:
mid = Math.floor((low + high) / 2). - Compare and Halve: The value at the midpoint is
compared to the target value:
- If the target is smaller than the midpoint value, the target must
reside in the left half, so the upper bound is updated to
mid - 1. - If the target is larger than the midpoint value, the target must
reside in the right half, so the lower bound is updated to
mid + 1.
- If the target is smaller than the midpoint value, the target must
reside in the left half, so the upper bound is updated to
- Repeat: The window narrows by half with every iteration until the target is isolated or the search bounds overlap, indicating the value does not exist in the collection.
Resolving Duplicate Values
A standard binary search stops as soon as it encounters any match.
However, _.sortedIndexOf mirrors the contract of
_.indexOf, meaning it must return the first
(lowest) index at which an element can be found.
To achieve this, Lodash internally uses a variant of the binary
search known as a lower-bound search (similar to
_.sortedIndex). When a matching value is found during
division, the algorithm does not terminate immediately. Instead, it
continues to adjust the upper boundary downward to explore the left side
of the midpoint. This guarantees that if multiple identical values
exist, the search resolves to the very first occurrence.
Once the lower-bound index is determined, Lodash performs a final validation check:
- If the element at that calculated index strictly matches
(
===) the target value, that index is returned. - If the element at that index does not match the target, or if the
index falls outside the array boundaries,
_.sortedIndexOfreturns-1.
Code Example
const _ = require('lodash');
const sortedNumbers = [10, 20, 30, 30, 30, 40, 50];
// Finds the first occurrence of 30 using binary search
const index = _.sortedIndexOf(sortedNumbers, 30);
console.log(index); // Output: 2
// Returns -1 when the element is absent
const missingIndex = _.sortedIndexOf(sortedNumbers, 25);
console.log(missingIndex); // Output: -1Performance and Constraints
The primary advantage of _.sortedIndexOf is efficiency.
On an array of 1,000,000 items, a linear search may require up to
1,000,000 comparisons in the worst case. In contrast,
_.sortedIndexOf will locate the item or confirm its absence
in at most 20 comparisons (\(\log_2(1,000,000)
\approx 20\)).
The critical requirement is array ordering. If
_.sortedIndexOf is called on an unsorted array, the binary
search logic will evaluate incorrect branches, leading to false
negatives or inaccurate index returns without throwing a runtime
error.