Lodash sortedIndexBy with Mixed Data Types
Lodash's _.sortedIndexBy method determines the lowest
index at which a value should be inserted into a sorted array to
maintain its sort order, utilizing an iteratee (transformer) function to
compute the comparison criteria. When applied to collections containing
mixed data types, the transformer is called uniformly on both the array
elements and the search value, but the ultimate insertion index depends
on whether the transformer normalizes these mixed types or leaves them
to JavaScript's standard type coercion rules during binary search
comparisons.
How the Transformer Function Executes
The syntax for the method is
_.sortedIndexBy(array, value, [iteratee=_.identity]).
Internally, Lodash implements a binary search algorithm
(baseSortedIndexBy) that executes the iteratee function in
two contexts:
- On the Target Value: The iteratee evaluates the
target
valueonce to obtain a derived comparison value. - On Array Elements: As the binary search bisects the
array, the iteratee is applied on-demand to the element at the midpoint
(
array[mid]).
The transformer does not inspect or validate the type of data it receives unless explicitly coded to do so. It simply maps each raw element into a transformed output value.
// Example transformer extracting a property
const array = [{ val: 10 }, { val: '20' }, { val: 30 }];
const index = _.sortedIndexBy(array, { val: 15 }, (o) => o.val);Binary Comparison of Mixed Types
Once the transformer returns values for both the target and the array
midpoint, Lodash compares them using standard relational comparison
operators (<). When mixed data types exist, two distinct
scenarios occur depending on the iteratee's implementation:
1. Unnormalized Transformers (Coercion Risks)
If the transformer simply passes through properties without normalizing their types (such as numbers and numeric strings), JavaScript's Abstract Relational Comparison algorithm dictates the outcome:
- String vs. Number: If one value is a number and the
other is a string, JavaScript attempts to convert the string to a
number. If the string is numeric (e.g.,
'20'), it compares numerically. If the string is non-numeric (e.g.,'abc'), it coerces toNaN. - Comparisons Involving
NaN: Any relational comparison againstNaNevaluates tofalse. Lodash accounts forNaNby sorting it after other values, but unnormalized data often violates the prerequisite that the array must already be sorted, leading to incorrect insertion indices. - Objects and Undefined: Undefined properties yield
undefined, which also sort to the end of the collection under Lodash's internal comparison checks.
Because binary search relies strictly on the mathematical assumption of transitivity (\(A < B\) and \(B < C\) implies \(A < C\)), mixed types that do not evaluate predictably break the search logic.
2. Normalizing Transformers (Predictable Ordering)
To reliably use _.sortedIndexBy with mixed data types,
the transformer function itself should homogenize the types prior to the
comparison step.
const mixedArray = ['2', 5, '10', 25];
// The transformer converts all elements to numbers before comparison
const insertIndex = _.sortedIndexBy(mixedArray, '15', (item) => Number(item));
// Returns 3By enforcing a single primitive type (such as explicitly casting via
Number(), String(), or generating a custom
sort weight), the transformer prevents implicit type coercion, ensuring
the binary search behaves deterministically across heterogeneous
collections.