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:

  1. Type Inspection: If both operands are primitive strings (i.e., typeof value === 'string' and typeof other === 'string'), the algorithm immediately branches into a lexicographical string comparison.
  2. Character Code Unit Comparison: The engine iterates through the strings index by index, comparing the UTF-16 code unit values of each corresponding character.
  3. 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'); // true

In 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); // false

When 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);               // false

By ensuring the arguments are numbers, you prevent the JavaScript runtime from entering the string-specific comparison branch, ensuring mathematically correct results.