Lodash sortedLastIndexOf with Sparse Arrays

This article explores how Lodash’s _.sortedLastIndexOf function interacts with sparse arrays containing missing indices or empty slots. Because _.sortedLastIndexOf relies on a binary search algorithm designed for contiguous, sorted data, encountering empty indices causes JavaScript to evaluate missing elements as undefined. This disrupts the search logic, leading to inaccurate index calculations or unexpected -1 return values.

Mechanism of _.sortedLastIndexOf

The _.sortedLastIndexOf method performs a binary search over an array that is assumed to be sorted in ascending order. Instead of iterating sequentially from the end of the collection like _.lastIndexOf, it repeatedly divides the search range in half to find the highest index at which a specified value appears.

For the binary search to function correctly, the dataset must satisfy a strict sorting invariant where every element at index i is less than or equal to the element at index i + 1.

A sparse array in JavaScript contains "holes"—indices that have not been assigned a value (for example, [1, , , 4]). When Lodash retrieves an element at a midpoint index using bracket notation (array[mid]), accessing an empty slot evaluates to undefined.

This creates two critical issues for _.sortedLastIndexOf:

  1. Broken Relational Comparisons: In JavaScript, relational comparisons involving undefined (such as value < undefined or undefined < value) evaluate to false. The binary search algorithm uses these comparisons to determine whether to branch left or right. When comparisons fail silently, the search boundary moves in the wrong direction.
  2. Violation of Sorted Order: Even if all defined numbers in a sparse array appear in ascending order, the implicit undefined values scattered throughout the array violate the monotonic order required by binary search algorithms.

Resulting Behavior

When calling _.sortedLastIndexOf on a sparse array:

Example

const _ = require('lodash');

// A sparse array with holes at indices 1 and 2
const sparseArray = [10, , , 20, 20, 30];

// Expected to find the last index of 20 (index 4)
console.log(_.sortedLastIndexOf(sparseArray, 20)); // May output -1 or an incorrect index

In this scenario, if the binary search checks index 2 (an empty slot evaluating to undefined), the relational comparison fails, causing the algorithm to discard the partition containing the target value.

To safely use _.sortedLastIndexOf, normalize sparse arrays into dense arrays prior to execution. Removing missing slots or re-indexing guarantees that the binary search operates across continuous, predictable boundaries: