Lodash Math Coercion Rules: Objects to Primitives
This article explains the specific mechanisms and internal rules the
Lodash JavaScript library uses to coerce objects into primitive values
across its math functions. You will learn the execution order of object
methods like valueOf and toString, how Lodash
normalizes derived primitives into numbers, and how specific functions
such as _.add, _.subtract, and
_.sum handle complex types, arrays, and edge cases without
throwing native runtime exceptions.
The Internal Coercion Pipeline
Lodash math operations—including arithmetic methods
(_.add, _.subtract, _.multiply,
_.divide) and aggregate functions (_.sum,
_.mean, _.min, _.max)—process
arguments through an internal conversion pipeline primarily governed by
baseToNumber and toNumber.
When an object is supplied as an operand to a Lodash math function, it does not immediately enter standard ECMAScript arithmetic evaluation. Instead, Lodash explicitly extracts a primitive value before performing the calculation.
1. Object-to-Primitive Extraction Order
When Lodash encounters an argument where isObject(value)
evaluates to true, it follows a strict two-step unwrapping
process:
valueOf()Evaluation: Lodash checks whether the object has a callablevalueOfmethod. Iftypeof value.valueOf === 'function', it invokes it. If the returned value is a primitive (not an object), this value is used for the next conversion step.- Fallback to String Conversion: If
valueOf()returns another object (which is the default behavior for plain objects and arrays), Lodash falls back to string coercion usingvalue + ''. This implicitly invokestoString().
Unlike standard modern JavaScript engines, which consult the
Symbol.toPrimitive method first, Lodash's legacy-compatible
toNumber implementation manually isolates
valueOf() and stringification to prevent unhandled
prototype exceptions.
2. Primitive-to-Number Normalization
Once an object is unwrapped to a primitive, Lodash normalizes the resulting value into a standard JavaScript number using the following rules:
- Direct Numbers: Returned directly if already of
type
number. - Symbols: Unlike native JavaScript, which throws a
TypeError: Cannot convert a Symbol value to a number, Lodash explicitly catches symbols (isSymbol(value)) and returnsNaN. - Booleans:
trueis coerced to1, andfalseis coerced to0. - Null and Undefined:
nullis converted to0.undefinedis converted toNaN. - Strings: Lodash sanitizes strings before
conversion:
- Leading and trailing whitespace is stripped via
replace(/^\s+|\s+$/g, ''). - Binary literals (prefixed with
0bor0B) and octal literals (prefixed with0oor0O) are parsed into integers usingparseInt(value.slice(2), radix). - Signed hexadecimal strings (such as
-0x1a) are detected and resolved toNaNrather than evaluating incorrectly. - Standard numeric strings are cast using the unary
+operator.
- Leading and trailing whitespace is stripped via
3. Function-Specific Coercion Behaviors
Lodash math functions handle coerced primitives differently depending on whether the operation is strictly arithmetic or supports concatenation.
_.add (Polymorphic
Addition)
_.add uses the internal createMathOperation
wrapper with a string fallback:
- Operands are coerced using
baseToValue(which unwraps objects to primitives). - If either coerced operand is a string, Lodash coerces the
other operand to a string via
baseToStringand performs string concatenation. - If neither operand is a string, both are coerced to numbers via
baseToNumber, and mathematical addition is performed.
_.add({ valueOf: () => 10 }, 5); // 15
_.add({ toString: () => 'hello ' }, 'world'); // "hello world"
_.add([10], [20]); // "1020" (Arrays stringify to "10" and "20")_.subtract,
_.multiply, and _.divide (Strict
Arithmetic)
These operations enforce numeric coercion on both sides:
- Both operands are passed directly into
baseToNumber. - Strings, custom objects, and arrays are forced through the full numeric conversion pipeline.
- If coercion results in a non-numeric string or invalid
representation, the operation evaluates to
NaN.
_.subtract({ valueOf: () => 50 }, 20); // 30
_.multiply(['5'], ['4']); // 20
_.divide({ valueOf: () => 'invalid' }, 2); // NaN_.sum,
_.mean, and Collection Functions
Aggregation functions iterate over collections and apply numeric coercion per element:
- Each item in the array or collection is passed through
baseToNumber. - If any object in the collection resolves to
NaN(such as a plain object{}whosetoString()yields"[object Object]"), the entire aggregation will returnNaN. - Elements with valid custom
valueOfor numerictoStringoutputs evaluate correctly.
const items = [
{ valueOf: () => 10 },
{ valueOf: () => 20 },
[30]
];
_.sum(items); // 60
_.mean(items); // 20Common Object Coercion Results Summary
- Plain Objects (
{}): EvaluatesvalueOf()(returns{}), falls back totoString()("[object Object]"), which parses toNaN. - Single-Element Arrays (
[42]):valueOf()returns[42], falls back to string"42", which parses to the number42. - Multi-Element Arrays (
[1, 2]): Falls back to string"1,2", which parses toNaN. - Empty Arrays (
[]): Falls back to string"", which parses to0. - Date Objects:
Date.prototype.valueOf()natively returns the epoch timestamp in milliseconds, which Lodash uses directly as a valid number.