Type Conversion & Coercion
JavaScript freely changes a value’s type to make an operation work, and that single design decision is behind both its convenience and its most notorious bugs. There are two ways types change: explicit conversion, where you deliberately call Number(), String(), or Boolean(), and implicit coercion, where the engine converts for you inside operators like +, ==, and conditionals. Understanding the rules — and choosing explicit conversion plus === — is what separates predictable code from mysterious one-off failures.
Explicit conversion
Explicit conversion means you call a conversion function yourself, so the result is obvious to anyone reading the code. The three core conversion functions are Number(), String(), and Boolean().
console.log(Number("42")); // string -> number
console.log(String(42)); // number -> string
console.log(Boolean(0)); // number -> boolean
console.log(Number("")); // empty string -> 0
console.log(Number("12px")); // not fully numeric -> NaN
Output:
42
42
false
0
NaN
For parsing user input, prefer parseInt() and parseFloat(), which read leading numeric characters and stop at the first invalid one — Number() is all-or-nothing.
console.log(parseInt("12px", 10)); // 12 (always pass the radix)
console.log(parseFloat("3.14em")); // 3.14
console.log(Number("12px")); // NaN
Output:
12
3.14
NaN
Tip: always pass the radix to
parseInt(value, 10). Without it, older inputs like leading-zero strings can be misread, and being explicit removes all ambiguity.
Truthy and falsy values
Every value has an inherent boolean meaning used in conditionals, &&, ||, and !. There are exactly eight falsy values; everything else is truthy.
| Falsy values | Truthy examples |
|---|---|
false | true |
0, -0 | any non-zero number |
0n (BigInt zero) | "0", "false" (non-empty strings) |
"" (empty string) | [] (empty array) |
null | {} (empty object) |
undefined | any function |
NaN | Infinity |
The two that surprise people most: "0" and [] are both truthy, because they are a non-empty string and an object respectively.
if ([]) console.log("empty array is truthy");
if ("0") console.log("the string zero is truthy");
console.log(Boolean(""), Boolean(" ")); // empty vs. a space
Output:
empty array is truthy
the string zero is truthy
false true
Implicit coercion in operators
The + operator is overloaded: if either operand is a string, it does string concatenation; otherwise it does numeric addition. Every other arithmetic operator (-, *, /, %) always coerces to number.
console.log(1 + "2"); // "12" — string wins
console.log("5" - 1); // 4 — subtraction forces number
console.log("5" * "2"); // 10 — both coerced to number
console.log(1 + 2 + "3"); // "33" — left to right
console.log([] + {}); // "[object Object]"
Output:
12
4
10
33
[object Object]
The == coercion table
Loose equality == converts operands to a common type before comparing, which produces results that are hard to predict. Strict equality === never converts and is almost always what you want.
| Expression | == result | Why |
|---|---|---|
1 == "1" | true | string coerced to number |
0 == "" | true | both coerce to 0 |
0 == "0" | true | both coerce to 0 |
"" == "0" | false | two strings, compared directly |
null == undefined | true | special-cased to equal each other |
null == 0 | false | null only loosely equals undefined |
NaN == NaN | false | NaN is never equal to anything |
[] == false | true | [] -> "" -> 0, false -> 0 |
console.log(0 == ""); // true
console.log(0 === ""); // false
console.log(null == undefined); // true
console.log(null === undefined); // false
Output:
true
false
true
false
Warning: the only defensible use of
==isvalue == null, a concise check that matches bothnullandundefined. Everywhere else, use===.
NaN: the value that isn’t equal to itself
NaN (“Not a Number”) is the result of an invalid numeric operation, and it is the only value in JavaScript not equal to itself. That means === cannot detect it — use Number.isNaN() instead.
const result = Number("oops");
console.log(result); // NaN
console.log(result === NaN); // false (!)
console.log(Number.isNaN(result)); // true
// Older global isNaN() coerces first, so it lies:
console.log(isNaN("hello")); // true — coerced to NaN
console.log(Number.isNaN("hello")); // false — not actually NaN
Output:
NaN
false
true
true
false
Prefer Number.isNaN() over the legacy global isNaN(), which coerces its argument first and so reports true for any non-numeric string.
Best Practices
- Convert deliberately with
Number(),String(), andBoolean()so your intent is visible at the call site. - Always use
===and!==; reserve==exclusively for thevalue == nullidiom. - Pass a radix to
parseInt(str, 10), and preferparseInt/parseFloatfor parsing strings with trailing units. - Memorize the eight falsy values — and remember
"0"and[]are truthy. - Detect
NaNwithNumber.isNaN(), never with===or the coercing globalisNaN(). - Avoid relying on
+for type changes;String(x)is clearer thanx + ""andNumber(x)clearer than+x.