How Lodash lt Compares Strings Lexicographically
The Lodash _.lt method determines whether one value is
less than another, but when provided with string arguments, it does not
convert them into numbers for mathematical evaluation. Instead, Lodash
delegates directly to JavaScript’s native relational comparison
operators, which enforce lexicographical (alphabetical) comparison based
on UTF-16 character code values whenever both inputs are strings. This
article explains the internal mechanics of _.lt, how
JavaScript's Abstract Relational Comparison algorithm processes string
parameters, and how to avoid common pitfalls when working with numeric
strings.
The Internal Mechanism of
Lodash _.lt
Lodash implements _.lt as a lightweight relational
helper designed to mimic and standardize JavaScript’s native
< operator. In Lodash’s source code, _.lt
is defined as:
function lt(value, other) {
return !(value === other) && (value < other);
}Because Lodash performs no explicit type casting or pre-processing on
strings (such as calling Number() or
parseFloat()), the comparison behavior is entirely governed
by the ECMAScript language specification for the native
< operator.
JavaScript's Abstract Relational Comparison
When JavaScript evaluates an expression like
value < other, it executes the ECMAScript
Abstract Relational Comparison algorithm. The algorithm
follows these key steps:
- Type Inspection: If both operands are primitive
strings (i.e.,
typeof value === 'string'andtypeof other === 'string'), the algorithm immediately branches into a lexicographical string comparison. - Character Code Unit Comparison: The engine iterates through the strings index by index, comparing the UTF-16 code unit values of each corresponding character.
- Evaluation: The comparison terminates at the first differing character. The string with the smaller UTF-16 value at that position is determined to be "less than" the other.
Because the engine encounters two strings, numeric coercion is completely bypassed.
Lexicographical Evaluation in Practice
When comparing numeric representations as strings, lexicographical rules produce outcomes that differ significantly from mathematical comparisons.
import _ from 'lodash';
// Both are strings: Lexicographical comparison
_.lt('20', '3'); // trueIn the example above, _.lt('20', '3') evaluates to
true. JavaScript compares the first character of each
string: '2' (UTF-16 code unit 50) and '3'
(UTF-16 code unit 51). Because 50 < 51,
'20' is determined to be less than '3', even
though 20 is mathematically greater than 3.
Similarly, alphabetical order and letter casing affect the evaluation:
_.lt('apple', 'banana'); // true ('a' comes before 'b')
_.lt('Banana', 'apple'); // true ('B' has code 66; 'a' has code 97)Mixed-Type Comparisons
If only one operand is a string and the other is a number, the Abstract Relational Comparison algorithm behaves differently:
_.lt('20', 3); // falseWhen one operand is not a string, JavaScript attempts to convert both
operands into numbers via ToNumeric(). Here,
'20' is converted to the number 20, and
20 < 3 evaluates mathematically to
false.
Enforcing Mathematical Comparison on Strings
To enforce actual numerical evaluation between string values rather
than lexicographical ordering, values must be explicitly coerced to
numbers prior to passing them into _.lt:
const strA = '20';
const strB = '3';
// Explicit numeric coercion
_.lt(Number(strA), Number(strB)); // false
_.lt(+strA, +strB); // falseBy ensuring the arguments are numbers, you prevent the JavaScript runtime from entering the string-specific comparison branch, ensuring mathematically correct results.