Type Coercion & Comparison

Difficulty: Beginner

Type coercion is JavaScript's automatic conversion of values from one type to another. It is one of the most misunderstood features of the language and a perennial favorite in technical interviews. There are two forms: implicit coercion (the engine converts types automatically during operations) and explicit coercion (you intentionally convert types using functions like Number(), String(), or Boolean()).

Implicit coercion happens whenever JavaScript encounters an operation where the operand types don't match what the operator expects. For example, the + operator triggers string coercion when either operand is a string ("5" + 3 becomes "53"), but the -, *, and / operators trigger numeric coercion ("5" - 3 becomes 2). Comparison operators like < and > also coerce to numbers. The loose equality operator == has its own complex set of coercion rules defined in the ECMAScript specification's Abstract Equality Comparison algorithm.

Explicit coercion uses built-in functions to convert types deliberately. Number("42") converts a string to a number, String(42) converts a number to a string, and Boolean(0) converts to a boolean. The unary + operator is a shorthand for Number() - writing +"42" converts the string to the number 42. parseInt() and parseFloat() are more lenient: parseInt("42px") returns 42, while Number("42px") returns NaN.

JavaScript values fall into two camps: truthy and falsy. There are exactly eight falsy values: false, 0, -0, 0n (BigInt zero), "" (empty string), null, undefined, and NaN. Every other value is truthy, including empty objects {}, empty arrays [], the string "0", the string "false", and any non-zero number. This matters because if statements, logical operators, and the ternary operator all check for truthiness, not strict boolean true.

The Abstract Equality Comparison algorithm (used by ==) follows specific rules when comparing values of different types. If one is a number and the other a string, the string is converted to a number. If one is a boolean, the boolean is first converted to a number (true becomes 1, false becomes 0) before further comparison. If one is an object and the other a primitive, the object is converted via valueOf() or toString(). The special case is null == undefined, which returns true - but neither null nor undefined equals any other value with ==. Understanding these rules makes seemingly bizarre results like [] == false or [] == ![] perfectly logical.

The strict equality operator (===) bypasses all coercion rules. If the types differ, it immediately returns false without attempting any conversion. This predictable behavior is why the JavaScript community universally recommends === over ==. The only cases where == is arguably acceptable are null checks (value == null catches both null and undefined) and certain library internals.

Code examples

Implicit vs Explicit Coercion

// Implicit coercion (automatic)
console.log("5" + 3);      // "53" (number to string)
console.log("5" - 3);      // 2 (string to number)
console.log("5" * "2");    // 10 (both to numbers)
console.log(true + 1);     // 2 (true becomes 1)
console.log(false + 1);    // 1 (false becomes 0)
console.log("" + 0);       // "0" (number to string)

// Explicit coercion (intentional)
console.log(Number("42"));       // 42
console.log(Number(""));         // 0
console.log(Number("hello"));    // NaN
console.log(Number(true));       // 1
console.log(Number(null));       // 0
console.log(Number(undefined));  // NaN

console.log(String(42));         // "42"
console.log(String(null));       // "null"
console.log(String(undefined));  // "undefined"

console.log(Boolean(0));         // false
console.log(Boolean(""));        // false
console.log(Boolean("0"));       // true
console.log(Boolean([]));        // true

The + operator is the biggest source of confusion: it concatenates with strings but adds with numbers. The - * / operators always coerce to numbers. Explicit coercion with Number(), String(), and Boolean() makes your intent clear and is preferred over relying on implicit behavior.

The Complete Falsy Values List

// All 8 falsy values in JavaScript
const falsyValues = [false, 0, -0, 0n, "", null, undefined, NaN];

falsyValues.forEach(val => {
  console.log(`${String(val).padEnd(12)} => ${Boolean(val)}`);
});

console.log("---");

// Common truthy gotchas
const trickyTruthy = ["0", "false", [], {}, -1, Infinity];
trickyTruthy.forEach(val => {
  const label = JSON.stringify(val);
  console.log(`${label.padEnd(12)} => ${Boolean(val)}`);
});

There are exactly 8 falsy values - memorize them. Everything else is truthy, including tricky cases: the string '0', the string 'false', empty arrays [], and empty objects {} are all truthy. This trips up many developers.

== (Abstract Equality) Deep Dive

// String vs Number: string is converted to number
console.log(5 == "5");       // true ("5" -> 5)
console.log(0 == "");        // true ("" -> 0)

// Boolean vs anything: boolean becomes a number first
console.log(true == 1);      // true (true -> 1)
console.log(true == "1");    // true (true -> 1, "1" -> 1)
console.log(false == 0);     // true (false -> 0)
console.log(false == "");    // true (false -> 0, "" -> 0)

// null and undefined: equal to each other, nothing else
console.log(null == undefined); // true (special rule)
console.log(null == 0);         // false
console.log(null == "");        // false
console.log(null == false);     // false

// Object vs primitive: object calls valueOf()/toString()
console.log([] == 0);         // true ([] -> "" -> 0)
console.log([] == false);     // true ([] -> "" -> 0, false -> 0)
console.log([1] == 1);        // true ([1] -> "1" -> 1)
console.log([] == ![]);       // true! ([] truthy, ![] is false, [] -> 0, false -> 0)

The == operator follows the Abstract Equality Algorithm: (1) same type -> compare directly, (2) null/undefined -> equal to each other only, (3) number/string -> convert string to number, (4) boolean involved -> convert boolean to number, (5) object/primitive -> convert object via valueOf()/toString(). The infamous [] == ![] is true because ![] is false (array is truthy, negated), then [] == false, false becomes 0, [] becomes '' via toString(), '' becomes 0, so 0 == 0 is true.

Practical Coercion Patterns

// Quick number conversion
const input = "42";
console.log(+input);         // 42 (unary +)
console.log(Number(input));  // 42 (explicit)
console.log(parseInt(input)); // 42

// parseInt vs Number
console.log(parseInt("42px"));  // 42 (stops at non-digit)
console.log(Number("42px"));    // NaN (strict)

// Quick boolean conversion (double NOT)
console.log(!!1);       // true
console.log(!!0);       // false
console.log(!!"hello"); // true
console.log(!!"");      // false

// Safe null/undefined check with ==
function processValue(val) {
  if (val == null) { // catches both null AND undefined
    return "No value provided";
  }
  return `Value: ${val}`;
}
console.log(processValue(null));
console.log(processValue(undefined));
console.log(processValue(0));

The unary + and double NOT (!!) are idiomatic shortcuts for type conversion. parseInt is more lenient than Number - useful for parsing CSS values like '42px'. Using val == null is the one acceptable use of == because it checks for both null and undefined in a single expression.

Key points

Concepts covered

Implicit Coercion, Explicit Coercion, Truthy and Falsy, Abstract Equality, == vs ===, Type Conversion Rules