DEV Community

ValPetal Tech Labs
ValPetal Tech Labs

Posted on

Javascript Question of the Day #19 [Talk::Overflow]

This post explains a quiz originally shared as a LinkedIn poll.


🔹 The Question

const prices = [9.99, '4.99', 5.00];
const total = prices.reduce((sum, p) => sum + p, 0);

console.log(total);
console.log(typeof total);
console.log(total === 19.98);
Enter fullscreen mode Exit fullscreen mode

Hint: The + operator has two jobs in JavaScript. What decides which one it picks? Watch what happens to the accumulator the moment it meets a string.

Follow me for JavaScript puzzles and weekly curations of developer talks & insights at Talk::Overflow: https://talkoverflow.substack.com/


🔹 Solution

Correct answer: B) 9.994.995, string, false

The output is:

9.994.995
string
false
Enter fullscreen mode Exit fullscreen mode

🧠 How this works

The + operator in JavaScript is overloaded: it performs numeric addition when both operands are numbers, but switches to string concatenation the moment either operand is a string. This is defined by the ECMAScript spec's Abstract Addition operation — if ToPrimitive of either side produces a string, both sides are coerced to strings and concatenated.

Inside reduce, the accumulator sum starts as 0 (a number). The first iteration adds 9.99, keeping it numeric. But the second element '4.99' is a string. At that point, + switches to concatenation and the accumulator becomes a string. From there, every subsequent + operation continues to concatenate — even though 5.00 is a number, the left operand is already a string, so 5.00 is coerced to "5" and appended.

This is a textbook silent data corruption bug. No error is thrown, no warning is logged. The code runs to completion, but total holds a nonsensical string like "9.994.995" instead of the expected 19.98.

🔍 Line-by-line explanation

  1. const prices = [9.99, '4.99', 5.00] — An array with three elements. The first and third are numbers. The second is a string — this is the trap. In production, this often happens when API responses, form inputs, URL parameters, or localStorage values return numeric data as strings.

  2. prices.reduce((sum, p) => sum + p, 0) — Starts reducing with an initial accumulator of 0.

  3. Iteration 1: sum = 0, p = 9.99 — Both are numbers. 0 + 9.99 = 9.99. The accumulator is 9.99 (number).

  4. Iteration 2: sum = 9.99, p = '4.99'sum is a number, p is a string. The + operator sees a string on the right side and chooses concatenation. 9.99 is coerced to "9.99", then concatenated with "4.99". The accumulator becomes "9.994.99" (string). This is the point of no return.

  5. Iteration 3: sum = '9.994.99', p = 5.00sum is now a string. Even though p is a number, + still chooses concatenation because the left operand is a string. 5.00 is coerced to "5" (trailing zero is dropped in Number.toString()). The accumulator becomes "9.994.995".

  6. console.log(total) — Prints 9.994.995. This looks like it could be a decimal number with two dots, but it's actually a string that makes no numeric sense.

  7. console.log(typeof total) — Prints string. The type has silently changed from number to string mid-reduction.

  8. console.log(total === 19.98)"9.994.995" === 19.98 is false. Strict equality between a string and a number always returns false without coercion.

The non-obvious part: The + operator's switch from addition to concatenation is irreversible within the reduce chain. Once a single string element flips the accumulator to a string, every subsequent iteration concatenates — even for numeric elements. There is no error, no NaN, no indication that anything went wrong. The code silently produces garbage.


🔹 Key Takeaways

  1. The + operator picks concatenation over addition if either operand is a string. This is asymmetric — once a string appears, the operation flips irreversibly in a reduce chain.

  2. Always coerce values before arithmetic. Use Number(p), parseFloat(p), or the unary +p operator inside your reducer: prices.reduce((sum, p) => sum + Number(p), 0).

  3. API data types are a contract, not a guarantee. Validate and parse numeric fields at the boundary where external data enters your application — never assume the type is correct.

  4. This bug produces no errors. Unlike most type-related issues, + coercion never throws. The result is valid JavaScript — it's just not the value you expected. This makes it one of the hardest bugs to catch without explicit type checks or TypeScript.

  5. typeof checks or runtime validation in reducers can act as guardrails. In critical financial calculations, consider asserting that every element is a number before reducing, or use a library that enforces decimal arithmetic.

Top comments (0)