Lodash Type Coercion in Math Operations Like _.add
This article provides an overview of how the Lodash library manages
explicit and implicit type coercion when complex objects are passed to
mathematical utilities such as _.add. It details the
internal pipeline Lodash employs to process arguments, explains how it
delegates object conversion to native ECMAScript coercion protocols, and
highlights how custom object methods like valueOf,
toString, and Symbol.toPrimitive determine the
final result of these operations.
The Architecture of Lodash Math Functions
Lodash creates most of its binary mathematical functions—such as
_.add, _.subtract, _.multiply,
and _.divide—using an internal higher-order factory
function named createMathOperation. This factory
standardizes argument validation, default handling, and type
normalization before executing the actual arithmetic operator.
When a function like _.add(augend, addend) is called,
createMathOperation evaluates both inputs using the
following rules:
- Undefined Handling: If both arguments are
undefined, it returns a designated default (such as0for_.add). If only one operand isundefined, the defined operand is returned as-is without further coercion. - String Precedence: If either argument is strictly
of type
'string', both arguments are converted to strings via Lodash's internalbaseToStringhelper, and native concatenation occurs. - Numeric Coercion: If neither argument is a string,
both arguments are passed to
baseToNumberbefore the arithmetic operator executes.
Internal Coercion via
baseToNumber
When complex objects (such as plain objects, arrays, or class
instances) are provided, they pass into Lodash's internal
baseToNumber utility. The implementation relies directly on
the native JavaScript unary plus operator (+value):
function baseToNumber(value) {
if (typeof value == 'number') {
return value;
}
if (isSymbol(value)) {
return NAN;
}
return +value;
}Because Lodash applies +value to objects, it delegates
the conversion process entirely to the ECMAScript ToNumber
abstract operation, which in turn invokes ToPrimitive with
a hint of "number".
The
ToPrimitive Resolution Order for Objects
When an object is coerced to a number during
baseToNumber, the JavaScript runtime evaluates the object's
methods in a strict order:
Symbol.toPrimitive: If defined on the object, the engine calls[Symbol.toPrimitive]('number'). The returned primitive is then converted directly to a number.valueOf(): IfSymbol.toPrimitiveis absent, the engine executesobject.valueOf(). IfvalueOf()returns a primitive value (such as a number, boolean, or string), that value is converted to a number.toString(): IfvalueOf()does not return a primitive (by default,Object.prototype.valueOfreturns the object itself), the engine falls back to callingobject.toString(). The resulting string is then parsed as a numeric literal.TypeError: If neithervalueOf()nortoString()produces a primitive, the runtime throws aTypeError.
Coercion Behavior Across Different Object Types
Plain Objects
By default, plain objects inherit
Object.prototype.toString, which returns
"[object Object]". Because "[object Object]"
cannot be parsed as a valid numeric literal, native coercion produces
NaN:
const objA = {};
const objB = { value: 10 };
_.add(objA, 5); // Returns NaN
_.add(objB, 5); // Returns NaNObjects with Custom Value Handlers
When an object defines a custom valueOf or
[Symbol.toPrimitive] method, Lodash respects the returned
value:
const item = {
amount: 42,
valueOf() {
return this.amount;
}
};
_.add(item, 8); // Returns 50If [Symbol.toPrimitive] is implemented, it takes
precedence over valueOf:
const advancedItem = {
amount: 42,
valueOf() {
return 100;
},
[Symbol.toPrimitive](hint) {
return hint === 'number' ? this.amount : 0;
}
};
_.add(advancedItem, 8); // Returns 50Arrays
Arrays inherit Array.prototype.toString, which joins
elements with commas:
- An empty array
[]coerces to the empty string"", which converts to0. Therefore,_.add([], 5)returns5. - A single-element numeric array
[42]coerces to"42", which converts to42. Thus,_.add([42], 8)returns50. - Multi-element arrays such as
[1, 2]coerce to"1,2", which evaluates toNaN.
Edge Cases: Objects Coercing to Strings First
If one of the operands is an explicit primitive string, Lodash
bypasses baseToNumber and forces string conversion on the
other operand via baseToString:
const item = {
valueOf() {
return 10;
},
toString() {
return 'custom';
}
};
// String branch triggered by '5'
_.add(item, '5'); // Returns "custom5"In this scenario, baseToString calls
object.toString() directly rather than evaluating
valueOf(), resulting in string concatenation instead of
numeric addition.