JavaScriptType Conversion & Coercion

Type Conversion & Coercion

JavaScript is happy to convert values between types — sometimes when you ask, sometimes silently as part of an operator. The explicit version is conversion; the implicit version is coercion. Both are useful, both have sharp edges, and untangling them is one of the rites of passage in learning the language.

Explicit conversion

Call the built-in constructors as plain functions (no new) to convert a value.

JS
Number("42");       // 42
Number("42px");     // NaN — entire string must be a number
Number("");         // 0   (!)
Number(true);       // 1
Number(false);      // 0
Number(null);       // 0
Number(undefined);  // NaN

String(42);         // "42"
String(true);       // "true"
String(null);       // "null"
String([1, 2, 3]);  // "1,2,3"

Boolean(0);         // false
Boolean("");        // false
Boolean("false");   // true — non-empty string is truthy
Boolean([]);        // true — empty array is truthy
Boolean({});        // true — empty object is truthy
Alternative shorthands
`+value` is the same as `Number(value)` — `+"42"` is `42`. `!!value` is the same as `Boolean(value)` — `!!`"" is `false`. `parseInt` and `parseFloat` are a separate, lenient family: `parseInt("42px") === 42` because they stop at the first non-numeric character.
Implicit coercion

Operators that expect a specific type will convert their operands first. The rules are stable but unintuitive in places.

JS
// + with a string → string concatenation
"3" + 4;          // "34"
"3" + true;       // "3true"
[] + [];          // ""
[] + {};          // "[object Object]"

// Other arithmetic → number
"3" - 1;          // 2
"3" * "4";        // 12
"6" / "2";        // 3

// Comparison with a string and a number → number
"5" > 3;          // true
"10" > "9";       // false — string comparison is lexicographic ("1" < "9")

// Boolean context → boolean
if ("0") { /* runs — non-empty string is truthy */ }
if (0)   { /* doesn't run */ }
The `+` operator is special
If *either* operand of `+` is a string, the other is coerced to a string. For every other arithmetic operator, both operands are coerced to numbers. That asymmetry is the source of the famous `"3" + 1 === "31"` but `"3" - 1 === 2` gotcha.
== vs ===

== is loose equality: if the two values are different types, it coerces one or both and tries again. === is strict equality: same type, same value, no coercion.

JS
0 == "";          // true
0 == false;       // true
null == undefined;// true
1 == "1";         // true
[] == false;      // true
[1] == 1;         // true

0 === "";         // false
1 === "1";        // false
null === undefined; // false

The full == algorithm spans three pages of the spec. The short version: nobody remembers all the rules, and most bugs in this area come from accidental loose equality.

Default to ===
Use `===` and `!==` by default. The one place `==` is genuinely useful is testing "null or undefined" together: `if (x == null)` matches both. Enabling `eqeqeq` in ESLint covers this with an exception for `== null`.
Truthy and falsy

A value is falsy if it coerces to false, otherwise truthy. There are exactly eight falsy values in JavaScript — everything else is truthy.

The complete falsy list

Text
false
0
-0
0n            // bigint zero
""            // empty string
null
undefined
NaN

That means an empty array [], an empty object {} and the string "0" are all truthy. People get caught by this regularly.

JS
if ([]) console.log("array");  // runs
if ({}) console.log("object"); // runs
if ("0") console.log("zero");  // runs

const items = [];
if (items)         console.log("has items?"); // runs — wrong test
if (items.length)  console.log("has items?"); // doesn't run — correct test
The common gotchas

JS
// "0" is truthy, but Boolean("0") is true, AND "0" == false is true
"0" == false;        // true
Boolean("0");        // true

// Adding numbers received from inputs
const a = "3", b = "4";
a + b;               // "34"
Number(a) + Number(b); // 7
+a + +b;             // 7  (terse)

// Date arithmetic is fine — Date coerces to number via valueOf
new Date(2026, 0) - new Date(2025, 0); // milliseconds between

// NaN propagates
1 + NaN;             // NaN
Number("abc") + 1;   // NaN

// Empty array tricks
+[];                 // 0
+[1];                // 1
+[1, 2];             // NaN
+"";                 // 0
NaN means "could not produce a number"
Any arithmetic with `NaN` produces `NaN`. If a calculation surprises you with `NaN`, trace your inputs — somewhere upstream, a value failed to parse.
Object.is — strict equality plus

Object.is is almost identical to === with two differences:

  • Object.is(NaN, NaN) is true — useful when you specifically want NaN-to-NaN to match.

  • Object.is(+0, -0) is false=== thinks they're equal.

JS
Object.is(NaN, NaN);  // true   — === would be false
Object.is(+0, -0);    // false  — === would be true
Object.is(2, 2);      // true

You'll rarely write this in app code, but React's reconciliation uses Object.is, and so do other libraries that need a defensible notion of "same value".

Practical guidance
  • Convert explicitly at boundaries — when you receive user input, when you serialise data, when you cross from one system to another.

  • Use ===/!== by default. Reach for == only when you really want the loose semantics (usually x == null).

  • Test arrays and objects on .length or specific properties, not on truthiness.

  • When you want a clean boolean, use Boolean(value) or !!value.

  • When parsing numeric input, Number(value) is strict, parseInt/parseFloat are lenient — pick deliberately.

A debugging trick
If something *almost* works and you suspect coercion, log the type: `console.log(typeof value, value)`. Seeing `"string", "0"` next to a "this should be a number" bug answers itself.