In JavaScript, why does '5' + 3 give '53' while '5' - 3 gives 2? Explain what the + operator does with mixed operand types.
answer
- one operator, two meanings
- primitives first, then the string test
- either side being a string is enough
- no other arithmetic operator concatenates
- left to right, so strings spread
basics
~20 sBinary + is the only arithmetic operator that is also string concatenation: after converting both operands to primitives, if either one is a string it concatenates, otherwise it adds numerically. Every other arithmetic operator converts to number unconditionally, so '5' - 3 is 2.
solid answer
~50 sBinary `+` is overloaded. It first converts each operand to a primitive using the default hint, then checks the results: if **either** primitive is a string, both are converted to strings and concatenated; otherwise both go to `ToNumber` and are added. `'5' + 3` has a string operand, so it concatenates to `'53'`. Every other arithmetic operator — `-`, `*`, `/`, `%`, `**` — has no string meaning, so it calls `ToNumber` on both sides unconditionally and `'5' - 3` is `2`. This asymmetry is why `+` is the operator that surprises people: `1 + '2'` is `'12'` but `1 * '2'` is `2`, and `1 + 2 + '3'` is `'33'` while `'1' + 2 + 3` is `'123'`, because `+` is left-associative and the first string result poisons the rest of the chain.
code
javascript · 7 linesconsole.log('5' + 3); // '53' concatenation
console.log('5' - 3); // 2 subtraction coerces to number
console.log(1 + 2 + '3'); // '33' left-associative
console.log('1' + 2 + 3); // '123'
console.log(1 + null); // 1 null → 0 on the number branch
console.log('1' + null); // '1null' null → 'null' on the string branch
console.log(+'42' + 1); // 43 unary + converts firstgo deeper
Be able to state the rule in one line — if either side is a string, + concatenates; every other arithmetic operator converts to number — and predict outputs for mixed literals without hesitating.
Explain the ordering: primitives are produced first, then the string test decides the branch. Walk through a left-associative chain and show where the result flips to a string.
Talk about the bug in production terms — a total that becomes a string because one value came from an input, a query parameter or JSON — and describe where you place explicit conversion and validation so it cannot recur.
Frame the policy: numeric parsing belongs at system boundaries with validation, not scattered through business logic, and argue when a codebase should lean on a typed layer or schema validation instead of hand-rolled coercion.
## Two operators wearing one symbol In JavaScript the binary `+` token means either numeric addition or string concatenation, and which one you get is decided at runtime from the operand *values*, never from how the code looks. The specification's algorithm is short: 1. Evaluate both operands. 2. Convert each to a primitive via `ToPrimitive` with the **default** hint (objects go through `Symbol.toPrimitive`, otherwise `valueOf` then `toString`). 3. If **either** primitive is a string, apply `ToString` to both and concatenate. 4. Otherwise apply `ToNumber` (or `ToNumeric`) to both and add. Step 3 is the whole story: one string anywhere in the pair turns the operation into concatenation. Note the ordering — the primitive conversion happens *before* the string test, which is why an object that stringifies can flip a whole expression into concatenation. ## Why the other operators behave `-`, `*`, `/`, `%` and `**` have no second meaning. Their algorithm skips the string test entirely and converts both operands with `ToNumber`: ```js '5' - 3 // 2 '5' * '2' // 10 '10' / 2 // 5 '5' - 'a' // NaN — 'a' does not parse as a number ``` That is the fastest way to remember the rule: **only `+` can concatenate**. Some codebases even exploit it, using unary `+value` or `value * 1` as a terse numeric coercion. ## ToNumber on strings, briefly When a string is converted to a number the whole string must be a valid numeric literal after trimming whitespace, otherwise the result is `NaN`. The empty string is the trap: `Number('')` is `0`, not `NaN`, so `'' - 1` is `-1` and `[] * 2` is `0`. ```js Number(' 42 ') // 42 Number('42px') // NaN Number('') // 0 Number('\n') // 0 ``` ## Left-associativity turns one string into many `+` groups left to right, so a chain is evaluated pairwise and the first concatenation makes every later operand a string too: ```js 1 + 2 + '3' // '33' — (1+2) is 3, then 3 + '3' concatenates '1' + 2 + 3 // '123' — '1'+2 is '12', then '12'+3 1 + 2 + '3' + 4 // '334' ``` This is the mechanism behind the classic production bug where a numeric total silently becomes a string because one value arrived from an input field, `JSON` payload or `localStorage` as text, and every subsequent `total = total + item` appends instead of adding. ## Null, undefined and booleans Because these are primitives already, step 2 does nothing and step 4 applies `ToNumber`: `null` becomes `0`, `undefined` becomes `NaN`, `true` becomes `1`, `false` becomes `0`. ```js 1 + null // 1 1 + undefined // NaN 1 + true // 2 '1' + null // '1null' — string present, so ToString(null) is 'null' ``` Note how the same `null` becomes `0` in one line and `'null'` in the next: the operand's contribution depends entirely on whether the pair went down the string branch or the number branch. ## Objects on either side An object operand is converted before the branch is chosen, and with the default hint that means `valueOf` first, `toString` second for ordinary objects. Plain objects therefore produce `'[object Object]'` and arrays produce their comma-joined elements — both strings, so `+` concatenates: ```js [1, 2] + [3] // '1,23' ({}) + 'x' // '[object Object]x' [5] * 2 // 10 — '*' ignores strings, ToNumber('5') is 5 ``` ## Writing code that does not hit this The practical defences are boring and effective: convert explicitly at the boundary where untrusted text enters (`Number(input.value)` or `parseFloat`, then check `Number.isNaN`), keep numeric state numeric rather than re-parsing at each use, and prefer template literals or `String(x)` when you *want* concatenation so the intent is visible. If you see `+` used as "make this a number", remember unary `+` and binary `+` are different operators — unary `+` always converts to a number and never concatenates.
- How would you safely add two values that might arrive as strings?Convert explicitly before the arithmetic and validate: `const n = Number(raw)` followed by a `Number.isNaN(n)` check, or `parseFloat` if trailing units are expected. Do the conversion once at the boundary where the text enters — a form field, JSON body or `localStorage` read — and keep the value numeric from then on, rather than re-coercing at every use site.
- Is unary + the same operator as binary +?No. Unary `+x` is a separate operator that always performs `ToNumber` and can never concatenate, so `+'5'` is `5` and `+''` is `0`. Binary `+` is the overloaded one. That is why `+'5' + 3` is `8` while `'5' + 3` is `'53'` — the unary form converts before the binary form sees a string.
saying these in an interview costs you the question
- Says + always concatenates when a string is involved, without mentioning other operators differ
- Claims '5' - 3 is a syntax error or NaN
- Thinks the left operand's type alone decides the outcome
- Believes Number('') is NaN
- Confuses unary + with binary + as one operator