Type Coercion in JavaScript

Type coercion is JavaScript's automatic or implicit conversion of values from one type to another. Understanding how and when JavaScript coerces types is crucial for writing bug-free code and is one of the most commonly tested topics in technical interviews.

Implicit vs Explicit Coercion

JavaScript performs type conversion in two ways:

Explicit Coercion

When you intentionally convert a value using built-in functions:

// String conversion
String(123)           // "123"
String(true)          // "true"
String(null)          // "null"
String(undefined)     // "undefined"
String([1, 2, 3])     // "1,2,3"
String({ a: 1 })      // "[object Object]"

// Number conversion
Number("123")         // 123
Number("123abc")      // NaN
Number("")            // 0
Number(true)          // 1
Number(false)         // 0
Number(null)          // 0
Number(undefined)     // NaN
Number([])            // 0
Number([5])           // 5
Number([1, 2])        // NaN

// Boolean conversion
Boolean(0)            // false
Boolean("")           // false
Boolean(null)         // false
Boolean(undefined)    // false
Boolean(NaN)          // false
Boolean("hello")      // true
Boolean(42)           // true
Boolean([])           // true (!)
Boolean({})           // true (!)

Implicit Coercion

When JavaScript automatically converts types during operations:

// String coercion with +
"5" + 3               // "53" (number → string)
"5" + true            // "5true"
"5" + null            // "5null"
"5" + undefined       // "5undefined"

// Number coercion with arithmetic operators
"5" - 3               // 2 (string → number)
"5" * "2"             // 10
"10" / "2"            // 5
"5" - true            // 4 (true → 1)
"5" - null            // 5 (null → 0)

// Boolean coercion in conditions
if ("hello") { }      // truthy
if (0) { }            // falsy
if ([]) { }           // truthy (!)
if ({}) { }           // truthy (!)

Truthy and Falsy Values

Every value in JavaScript has an inherent boolean value when evaluated in a boolean context.

Falsy Values (Only 8!)

// These are ALL the falsy values in JavaScript:
false
0
-0
0n            // BigInt zero
""            // empty string
null
undefined
NaN

// Everything else is truthy!

Common Truthy Gotchas

// These are all TRUTHY (often surprising!)
Boolean("0")          // true - non-empty string
Boolean("false")      // true - non-empty string
Boolean([])           // true - empty array is truthy!
Boolean({})           // true - empty object is truthy!
Boolean(function(){}) // true
Boolean(-1)           // true - any non-zero number
Boolean(" ")          // true - string with space
Boolean(new Boolean(false)) // true - object wrapper!

Equality Operators: == vs ===

This is one of the most frequently asked interview questions about JavaScript.

Strict Equality (===)

Compares both value AND type. No coercion occurs.

5 === 5               // true
5 === "5"             // false (different types)
null === undefined    // false (different types)
NaN === NaN           // false (NaN is never equal to itself!)
[] === []             // false (different object references)
{} === {}             // false (different object references)

// For objects, === compares references
const arr1 = [1, 2, 3];
const arr2 = arr1;
arr1 === arr2         // true (same reference)

Abstract Equality (==)

Performs type coercion before comparison. The rules are complex:

// Same type: behaves like ===
5 == 5                // true
"hello" == "hello"    // true

// null and undefined are only equal to each other
null == undefined     // true
null == 0             // false
undefined == 0        // false

// Number vs String: string → number
5 == "5"              // true ("5" → 5)
0 == ""               // true ("" → 0)
0 == "0"              // true ("0" → 0)

// Boolean vs anything: boolean → number first
true == 1             // true (true → 1)
false == 0            // true (false → 0)
true == "1"           // true (true → 1, "1" → 1)
false == ""           // true (false → 0, "" → 0)

// Object vs primitive: object → primitive (valueOf/toString)
[1] == 1              // true ([1].toString() → "1" → 1)
["1"] == 1            // true
[1, 2] == "1,2"       // true ([1,2].toString() → "1,2")

The Infamous Gotchas

// Empty array comparisons
[] == false           // true ([] → "" → 0, false → 0)
[] == ![]             // true ([] → 0, ![] → false → 0)
[] == 0               // true
[] == ""              // true

// But remember:
Boolean([])           // true (empty array is truthy!)

// This creates confusion:
if ([]) {
    console.log("truthy!");  // This runs!
}
if ([] == false) {
    console.log("equals false!");  // This also runs!
}

// The transitivity trap
"0" == 0              // true
0 == ""               // true
"0" == ""             // false (!)

The + Operator's Dual Nature

The + operator is particularly tricky because it handles both addition and string concatenation:

// Addition (both operands are numbers)
5 + 3                 // 8

// Concatenation (at least one operand is a string)
"5" + 3               // "53"
5 + "3"               // "53"
"5" + "3"             // "53"

// Coercion order matters
1 + 2 + "3"           // "33" (left-to-right: 1+2=3, 3+"3"="33")
"1" + 2 + 3           // "123" ("1"+2="12", "12"+3="123")

// Unary + converts to number
+"5"                  // 5
+""                   // 0
+true                 // 1
+false                // 0
+null                 // 0
+undefined            // NaN
+[]                   // 0
+[5]                  // 5
+{}                   // NaN

Object to Primitive Coercion

When an object needs to be converted to a primitive, JavaScript uses these methods:

The Coercion Algorithm

  1. If a Symbol.toPrimitive method exists, call it
  2. Otherwise, for "number" hint: try valueOf(), then toString()
  3. For "string" hint: try toString(), then valueOf()
const obj = {
    valueOf() {
        return 42;
    },
    toString() {
        return "hello";
    }
};

// Number context uses valueOf
Number(obj)           // 42
obj + 0               // 42

// String context uses toString
String(obj)           // "hello"
obj + ""              // "42" (+ prefers valueOf for objects!)

// Using Symbol.toPrimitive (takes precedence)
const custom = {
    [Symbol.toPrimitive](hint) {
        if (hint === "number") return 10;
        if (hint === "string") return "ten";
        return true;  // default
    }
};

Number(custom)        // 10
String(custom)        // "ten"
custom + ""           // "true"

Practical Patterns and Best Practices

Safe Type Checking

// Checking for null or undefined
if (value == null) {
    // This catches BOTH null and undefined
    // This is one of the few valid uses of ==
}

// Better: explicit checks
if (value === null || value === undefined) { }

// Or use nullish coalescing
const result = value ?? "default";

// Type checking
typeof value === "string"
typeof value === "number"
typeof value === "boolean"
Array.isArray(value)
value instanceof Date

// Checking for NaN (special case!)
Number.isNaN(value)   // correct
value !== value       // also works (NaN !== NaN)
isNaN(value)          // careful - coerces first!
isNaN("hello")        // true (coerced to NaN)
Number.isNaN("hello") // false (no coercion)

Converting to Boolean Safely

// Double negation (common pattern)
!!value               // converts to boolean

// Explicit conversion
Boolean(value)

// Watch out for these:
!!"0"                 // true (non-empty string)
!!"false"             // true (non-empty string)
!![]                  // true (empty array)
!!{}                  // true (empty object)

Converting to Number Safely

// parseInt and parseFloat
parseInt("42")        // 42
parseInt("42px")      // 42 (stops at non-digit)
parseInt("")          // NaN
parseInt("0x10")      // 16 (hexadecimal)
parseInt("10", 2)     // 2 (binary)

parseFloat("3.14")    // 3.14
parseFloat("3.14abc") // 3.14

// Number() is stricter
Number("42")          // 42
Number("42px")        // NaN (requires entire string)
Number("")            // 0 (!)

// Unary plus
+"42"                 // 42
+"42px"               // NaN

Interview Tips

  1. Always use === unless you have a specific reason for ==
  2. Know the 8 falsy values - everything else is truthy
  3. Remember [] and {} are truthy but [] == false is true
  4. The + operator concatenates if either operand is a string
  5. null == undefined is true, but null === undefined is false
  6. NaN is never equal to anything, including itself
  7. Use Number.isNaN() instead of global isNaN()
  8. Prefer explicit coercion for readability: String(x), Number(x), Boolean(x)

Common Interview Questions

Test your understanding with these classic questions:

// What does each expression evaluate to?

// 1. Comparison operators
[] == ![]             // ?
[] == false           // ?
"0" == false          // ?

// 2. Arithmetic
"5" - 3               // ?
"5" + 3               // ?
"5" - - "3"           // ?

// 3. Boolean context
if ([]) { }           // runs or not?
if ([] == false) { }  // runs or not?

// 4. NaN behavior
NaN === NaN           // ?
Number.isNaN(NaN)     // ?
Number.isNaN("NaN")   // ?

Understanding type coercion deeply will help you avoid subtle bugs and confidently answer interview questions about JavaScript's type system.

Practice Problems

TheoryEasy

Truthy and Falsy Values

TheoryMedium

Equality Operators

TheoryMedium

Type Coercion with Operators

TheoryMedium

NaN Comparisons

TheoryHard

Empty Array Coercion

Let's continue exploring the next page. Take your time, and proceed when you're ready.

Lesson completed?

Found a bug, typo, or have feedback?

Let me know