In JavaScript:
'1' + 1 // '11' (string)
'1' - 1 // 0 (number)
Same operands, different operators, different results. This is type coercion—JavaScript automatically converting values between types.
The + Operator Prefers Strings
When + sees a string, it converts everything to strings and concatenates:
'1' + 1 // '11'
'1' + true // '1true'
'1' + null // '1null'
'1' + undefined // '1undefined'
The logic: + works for both addition and concatenation. If either operand is a string, assume concatenation.
Other Operators Prefer Numbers
Subtraction, multiplication, and division have no string equivalent. They convert to numbers:
'5' - 2 // 3
'10' * '2' // 20
'12' / '4' // 3
'5' - true // 4 (true becomes 1)
'5' - 'abc' // NaN (can't convert 'abc')
How Values Convert to Numbers
JavaScript's Number() function shows the conversion rules:
Number('123') // 123
Number('12.5') // 12.5
Number('') // 0
Number(' ') // 0
Number('abc') // NaN
Number(true) // 1
Number(false) // 0
Number(null) // 0
Number(undefined) // NaN
Empty strings and whitespace become 0. Non-numeric strings become NaN.
How Values Convert to Strings
The String() function shows string conversion:
String(123) // '123'
String(true) // 'true'
String(false) // 'false'
String(null) // 'null'
String(undefined) // 'undefined'
String({}) // '[object Object]'
String([1, 2]) // '1,2'
Everything has a string representation, though it's not always useful.
The Double Equals Trap
Loose equality (==) coerces types before comparing:
1 == '1' // true (string '1' becomes number 1)
0 == false // true (false becomes 0)
'' == 0 // true (empty string becomes 0)
null == undefined // true (special rule)
This causes confusion. Use strict equality (===) instead:
1 === '1' // false (different types)
0 === false // false
'' === 0 // false
null === undefined // false
Strict equality doesn't coerce. Same type and value required.
Truthy and Falsy Values
In boolean contexts, values coerce to true or false:
if (value) { } // Coerces value to boolean
Falsy values (coerce to false):
false0-
''(empty string) nullundefinedNaN
Everything else is truthy, including:
if ('0') { } // true (non-empty string)
if ('false') { } // true (non-empty string)
if ([]) { } // true (empty array is truthy)
if ({}) { } // true (empty object is truthy)
Array to Number Coercion
Arrays have surprising coercion behavior:
[] + [] // '' (two empty strings)
[] + {} // '[object Object]'
{} + [] // 0 (confusing - {} is block, not object)
[1] + [2] // '12' (arrays become strings)
[1, 2] + [3] // '1,23'
Arrays convert to strings (via join(',')), then concatenate or convert to numbers.
Object to Primitive Coercion
Objects convert via valueOf() or toString():
const obj = {
valueOf() { return 42; },
toString() { return 'hello'; }
};
obj + 1 // 43 (uses valueOf)
String(obj) // 'hello' (uses toString)
The operator determines which method is called.
The Unary + Trick
Unary + converts to number:
+'123' // 123
+'12.5' // 12.5
+true // 1
+false // 0
+null // 0
+undefined // NaN
This is shorthand for Number(). Common in code golf and minification.
Explicit vs Implicit Coercion
Explicit: You call conversion functions:
Number('123') // Explicit
String(123) // Explicit
Boolean(1) // Explicit
Implicit: JavaScript converts automatically:
'1' + 1 // Implicit
'5' - 2 // Implicit
if (value) { } // Implicit
Explicit is clearer. Implicit is shorter but can surprise.
Avoiding Coercion Bugs
Use strict equality:
if (x === 5) { } // Not if (x == 5)
Convert explicitly when needed:
const num = Number(input);
const str = String(value);
Check types before operations:
if (typeof x === 'number' && typeof y === 'number') {
return x + y;
}
Use parseInt/parseFloat for strings:
parseInt('123px') // 123 (stops at non-digit)
Number('123px') // NaN
When Coercion Is Useful
Converting user input:
const age = +prompt('Enter age:'); // Converts string to number
Boolean checks:
if (array.length) { } // Truthy if array has items
if (object.property) { } // Truthy if property exists and isn't falsy
Default values:
const value = input || 'default'; // If input is falsy, use default
But nullish coalescing (??) is better:
const value = input ?? 'default'; // Only if null/undefined
Template Literals Always Coerce
Template literals convert everything to strings:
`Result: ${123}` // 'Result: 123'
`Value: ${true}` // 'Value: true'
`Object: ${{a: 1}}` // 'Object: [object Object]'
Common Gotchas
Leading zeros in numeric strings:
parseInt('08') // 8
parseInt('08', 10) // 8 (always specify radix)
Array length coercion:
if (array.length) { } // Works, but:
if (array.length > 0) { } // More explicit
null vs undefined:
null == undefined // true (coerced)
null === undefined // false (strict)
null + 1 // 1 (null becomes 0)
undefined + 1 // NaN (undefined becomes NaN)
TypeScript's Role
TypeScript prevents many coercion errors at compile time:
function add(a: number, b: number) {
return a + b;
}
add('1', 1); // Error: Argument of type 'string' is not assignable
Further Reading
Kyle Simpson's You Don't Know JS: Types & Grammar covers coercion comprehensively.
The ECMAScript specification defines all coercion rules precisely.
MDN's guide to equality comparisons explains == vs === in detail.
Type coercion is one of JavaScript's defining features. Understanding it turns confusing behavior into predictable rules.
0 comments