Skip to main content
Why does "5" + 3 give you "53" but "5" - 3 gives you 2? Why does [] == ![] return true? How does JavaScript decide what type a value should be?
// JavaScript's "helpful" type conversion in action
console.log("5" + 3);    // "53" (string concatenation!)
console.log("5" - 3);    // 2 (numeric subtraction)
console.log([] == ![]);  // true (wait, what?!)
This surprising behavior is type coercion. JavaScript automatically converts values from one type to another. Understanding these rules helps you avoid bugs and write more predictable code.
What you’ll learn in this guide:
  • The difference between implicit and explicit coercion
  • How JavaScript converts to strings, numbers, and booleans
  • The 8 falsy values every developer must memorize
  • How objects convert to primitives
  • The famous JavaScript “WAT” moments explained
  • Best practices for avoiding coercion bugs
Prerequisites: This guide assumes you understand Primitive Types. If terms like string, number, boolean, null, and undefined are new to you, read that guide first!

What Is Type Coercion?

Type coercion is the automatic or implicit conversion of values from one data type to another in JavaScript. According to the ECMAScript specification, JavaScript performs these conversions through a set of “abstract operations” — internal algorithms like ToString, ToNumber, and ToBoolean. When you use operators or functions that expect a certain type, JavaScript will convert (coerce) values to make the operation work, sometimes helpfully, sometimes surprisingly. Understanding these conversion rules helps you write predictable, bug-free code.

The Shapeshifter Analogy

Imagine JavaScript as an overly helpful translator. When you give it values of different types, it tries to “help” by converting them, sometimes correctly, sometimes… creatively.
┌─────────────────────────────────────────────────────────────────────────┐
│                     THE OVERLY HELPFUL TRANSLATOR                        │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  YOU: "Hey JavaScript, add 5 and '3' together"                           │
│                                                                          │
│  JAVASCRIPT (thinking): "Hmm, one's a number, one's a string...          │
│                          I'll just convert the number to a string!       │
│                          '5' + '3' = '53'. You're welcome!"              │
│                                                                          │
│  YOU: "That's... not what I meant."                                      │
│                                                                          │
│  JAVASCRIPT: "¯\_(ツ)_/¯"                                                │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
This “helpful” behavior is called type coercion. JavaScript automatically converts values from one type to another. Sometimes it’s useful, sometimes it creates bugs that will haunt your dreams.
┌─────────────────────────────────────────────────────────────────────────┐
│                    TYPE COERCION: THE SHAPESHIFTER                       │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│        ┌─────────┐                    ┌─────────┐                        │
│        │   "5"   │ ──── + 3 ────────► │  "53"   │  String won!           │
│        │ string  │                    │ string  │                        │
│        └─────────┘                    └─────────┘                        │
│                                                                          │
│        ┌─────────┐                    ┌─────────┐                        │
│        │   "5"   │ ──── - 3 ────────► │    2    │  Number won!           │
│        │ string  │                    │ number  │                        │
│        └─────────┘                    └─────────┘                        │
│                                                                          │
│        Same values, different operators, different results!              │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Explicit vs Implicit Coercion

There are two ways coercion happens:
You control the conversion using built-in functions. This is predictable and intentional.
// YOU decide when and how to convert
Number("42")      // 42
String(42)        // "42"
Boolean(1)        // true

parseInt("42px")  // 42
parseFloat("3.14") // 3.14
These functions — Number(), String(), Boolean(), parseInt(), and parseFloat() — give you full control.This is the safe way. You know exactly what’s happening.

Why Does JavaScript Do This?

JavaScript is a dynamically typed language. Variables don’t have fixed types. This flexibility means JavaScript needs to figure out what to do when types don’t match.
// In JavaScript, variables can hold any type
let x = 42;       // x is a number
x = "hello";      // now x is a string
x = true;         // now x is a boolean

// So what happens here?
let result = x + 10;  // JavaScript must decide how to handle this
Other languages would throw an error. JavaScript tries to make it work. Whether that’s a feature or a bug… depends on who you ask!

The Three Types of Conversion

Here’s the most important rule: JavaScript can only convert to THREE primitive types:
Target TypeExplicit MethodCommon Implicit Triggers
StringString(value)+ with a string, template literals
NumberNumber(value)Math operators (- * / %), comparisons
BooleanBoolean(value)if, while, !, &&, ||, ? :
That’s it. No matter how complex the coercion seems, the end result is always a string, number, or boolean.
┌─────────────────────────────────────────────────────────────────────────┐
│                    THE THREE CONVERSION DESTINATIONS                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│                      ┌──────────────────────┐                            │
│                      │    ANY VALUE         │                            │
│                      │  (string, number,    │                            │
│                      │   object, array...)  │                            │
│                      └──────────┬───────────┘                            │
│                                 │                                        │
│                ┌────────────────┼────────────────┐                       │
│                ▼                ▼                ▼                       │
│          ┌──────────┐    ┌──────────┐    ┌──────────┐                    │
│          │  String  │    │  Number  │    │ Boolean  │                    │
│          │   "42"   │    │    42    │    │   true   │                    │
│          └──────────┘    └──────────┘    └──────────┘                    │
│                                                                          │
│          These are the ONLY three possible destinations!                 │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

String Conversion

String conversion is the most straightforward. Almost anything can become a string.

When Does It Happen?

// Explicit conversion
String(123)           // "123"
String(true)          // "true"
(123).toString()      // "123"

// Implicit conversion
123 + ""              // "123" (concatenation with empty string)
`Value: ${123}`       // "Value: 123" (template literal)
"Hello " + 123        // "Hello 123" (+ with a string)
The toString() method and template literals are also common ways to convert values to strings.

String Conversion Rules

ValueResultNotes
123"123"Numbers become digit strings
-12.34"-12.34"Decimals and negatives work too
true"true"Booleans become their word
false"false"
null"null"
undefined"undefined"
[1, 2, 3]"1,2,3"Arrays join with commas
[]""Empty array becomes empty string
{}"[object Object]"Objects become this (usually useless)
Symbol("id")Throws TypeError!Symbols can’t implicitly convert

The + Operator’s Split Personality

The + operator is special: it does both addition and concatenation:
// With two numbers: addition
5 + 3               // 8

// With any string involved: concatenation
"5" + 3             // "53" (3 becomes "3")
5 + "3"             // "53" (5 becomes "5")
"Hello" + " World"  // "Hello World"

// Order matters with multiple operands!
1 + 2 + "3"         // "33" (1+2=3, then 3+"3"="33")
"1" + 2 + 3         // "123" (all become strings left-to-right)
Common gotcha: The + operator with strings catches many developers off guard. If you’re doing math and get unexpected string concatenation, check if any value might be a string!
// Dangerous: user input is always a string!
const userInput = "5";
const result = userInput + 10;  // "510", not 15!

// Safe: convert first
const result = Number(userInput) + 10;  // 15

Number Conversion

Number conversion has more triggers than string conversion, and more edge cases to memorize.

When Does It Happen?

// Explicit conversion
Number("42")          // 42
parseInt("42px")      // 42 (stops at non-digit)
parseFloat("3.14")    // 3.14
+"42"                 // 42 (unary plus trick)

// Implicit conversion
"6" - 2               // 4 (subtraction)
"6" * 2               // 12 (multiplication)
"6" / 2               // 3 (division)
"6" % 4               // 2 (modulo)
"10" > 5              // true (comparison)
+"42"                 // 42 (unary plus)

Number Conversion Rules

ValueResultNotes
"123"123Numeric strings work
" 123 "123Whitespace is trimmed
"123abc"NaNAny non-numeric char → NaN
""0Empty string becomes 0
" "0Whitespace-only becomes 0
true1
false0
null0null → 0
undefinedNaNundefined → NaN (different!)
[]0Empty array → "" → 0
[1]1Single element array
[1, 2]NaNMultiple elements → NaN
{}NaNObjects → NaN
null vs undefined: Notice that Number(null) is 0 but Number(undefined) is NaN. This inconsistency trips up many developers!
Number(null)       // 0
Number(undefined)  // NaN

null + 5           // 5
undefined + 5      // NaN

Math Operators Always Convert to Numbers

Unlike +, the other math operators (-, *, /, %) only do math. They always convert to numbers:
"6" - "2"    // 4 (both become numbers)
"6" * "2"    // 12
"6" / "2"    // 3
"10" % "3"   // 1

// This is why - and + behave differently!
"5" + 3      // "53" (concatenation)
"5" - 3      // 2 (math)

The Unary + Trick

The unary + (plus sign before a value) is a quick way to convert to a number:
+"42"        // 42
+true        // 1
+false       // 0
+null        // 0
+undefined   // NaN
+"hello"     // NaN
+""          // 0

Boolean Conversion

Boolean conversion is actually the simplest. Every value is either truthy or falsy.

When Does It Happen?

// Explicit conversion
Boolean(1)            // true
Boolean(0)            // false
!!value               // double negation trick

// Implicit conversion
if (value) { }        // condition check
while (value) { }     // loop condition
value ? "yes" : "no"  // ternary operator
value && doSomething() // logical AND
value || defaultValue  // logical OR
!value                // logical NOT

The 8 Falsy Values (Memorize These!)

As documented in MDN’s reference on falsy values, there are 8 common values that convert to false. Everything else is true.
// THE FALSY EIGHT
Boolean(false)        // false (obviously)
Boolean(0)            // false
Boolean(-0)           // false (yes, -0 exists)
Boolean(0n)           // false ([BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) zero)
Boolean("")           // false (empty string)
Boolean(null)         // false
Boolean(undefined)    // false
Boolean(NaN)          // false
Technical note: There’s actually a 9th falsy value: document.all. It’s a legacy browser API that returns false in boolean context despite being an object. You’ll rarely encounter it in modern code, but it exists for backwards compatibility with ancient websites.

Everything Else Is Truthy!

This includes some surprises:
// These are all TRUE!
Boolean(true)         // true (obviously)
Boolean(1)            // true
Boolean(-1)           // true (negative numbers!)
Boolean("hello")      // true
Boolean("0")          // true (non-empty string!)
Boolean("false")      // true (non-empty string!)
Boolean([])           // true (empty array!)
Boolean({})           // true (empty object!)
Boolean(function(){}) // true
Boolean(new Date())   // true
Boolean(Infinity)     // true
Boolean(-Infinity)    // true
Common gotchas:
// These catch people ALL the time:
Boolean("0")          // true (it's a non-empty string!)
Boolean("false")      // true (it's a non-empty string!)
Boolean([])           // true (arrays are objects, objects are truthy)
Boolean({})           // true (even empty objects)

// If checking for empty array, do this:
if (arr.length) { }   // checks if array has items
if (arr.length === 0) { }  // checks if array is empty

Logical Operators Don’t Return Booleans!

A common misconception: && and || don’t necessarily return true or false. They return one of the original values:
// || returns the FIRST truthy value (or the last value)
"hello" || "world"    // "hello"
"" || "world"         // "world"
"" || 0 || null || "yes"  // "yes"

// && returns the FIRST falsy value (or the last value)
"hello" && "world"    // "world"
"" && "world"         // ""
1 && 2 && 3           // 3

// This is useful for defaults!
const name = userInput || "Anonymous";
const display = user && user.name;

Object to Primitive Conversion

When JavaScript needs to convert an object to a primitive (including arrays), it follows a specific algorithm.

The ToPrimitive Algorithm

1

Check if already primitive

If the value is already a primitive (string, number, boolean, etc.), return it as-is.
2

Determine the 'hint'

JavaScript decides whether it wants a “string” or “number” based on context:
  • String hint: String(), template literals, property keys
  • Number hint: Number(), math operators, comparisons
  • Default hint: + operator, == (usually treated as number)
3

Try valueOf() or toString()

  • For number hint: try valueOf() first, then toString()
  • For string hint: try toString() first, then valueOf()
4

Return primitive or throw

If a primitive is returned, use it. Otherwise, throw TypeError.

How Built-in Objects Convert

// Arrays - toString() returns joined elements
[1, 2, 3].toString()   // "1,2,3"
[1, 2, 3] + ""         // "1,2,3"
[1, 2, 3] - 0          // NaN (can't convert "1,2,3" to number)

[].toString()          // ""
[] + ""                // ""
[] - 0                 // 0 (empty string → 0)

[1].toString()         // "1"
[1] - 0                // 1

// Plain objects - toString() returns "[object Object]"
({}).toString()        // "[object Object]"
({}) + ""              // "[object Object]"

// Dates - special case, prefers string for + operator
const date = new Date(0);
date.toString()        // "Thu Jan 01 1970 ..."
date.valueOf()         // 0 (timestamp in ms)

date + ""              // "Thu Jan 01 1970 ..." (uses toString)
date - 0               // 0 (uses valueOf)

Custom Conversion with valueOf and toString

You can control how your objects convert:
const price = {
  amount: 99.99,
  currency: "USD",
  
  valueOf() {
    return this.amount;
  },
  
  toString() {
    return `${this.currency} ${this.amount}`;
  }
};

// Number conversion uses valueOf()
price - 0              // 99.99
price * 2              // 199.98
+price                 // 99.99

// String conversion uses toString()
String(price)          // "USD 99.99"
`Price: ${price}`      // "Price: USD 99.99"

// + is ambiguous, uses valueOf() if it returns primitive
price + ""             // "99.99" (valueOf returned number, then → string)

ES6 Symbol.toPrimitive

ES6 introduced a cleaner way to control conversion — Symbol.toPrimitive:
const obj = {
  [Symbol.toPrimitive](hint) {
    console.log(`Converting with hint: ${hint}`);
    
    if (hint === "number") {
      return 42;
    }
    if (hint === "string") {
      return "forty-two";
    }
    // hint === "default"
    return "default value";
  }
};

+obj           // 42 (hint: "number")
`${obj}`       // "forty-two" (hint: "string")
obj + ""       // "default value" (hint: "default")

The == Algorithm Explained

The loose equality operator == is where type coercion gets wild. For a deeper dive into all equality operators, see our Equality Operators guide. Here’s how == actually works:

Simplified == Rules

1

Same type?

Compare directly (like ===).
5 == 5           // true
"hello" == "hello"  // true
2

null or undefined?

null == undefined is true. Neither equals anything else.
null == undefined    // true
null == null         // true
null == 0            // false (special rule!)
null == ""           // false
3

Number vs String?

Convert the string to a number.
5 == "5"
// becomes: 5 == 5
// result: true
4

Boolean involved?

Convert the boolean to a number FIRST.
true == "1"
// step 1: 1 == "1" (true → 1)
// step 2: 1 == 1 (string → number)
// result: true

true == "true"
// step 1: 1 == "true" (true → 1)
// step 2: 1 == NaN ("true" → NaN)
// result: false (surprise!)
5

Object vs Primitive?

Convert the object to a primitive.
[1] == 1
// step 1: "1" == 1 (array → string "1")
// step 2: 1 == 1 (string → number)
// result: true

Step-by-Step Examples

// Example 1: "5" == 5
"5" == 5
// String vs Number → convert string to number
// 5 == 5
// Result: true

// Example 2: true == "1"
true == "1"
// Boolean involved → convert boolean to number first
// 1 == "1"
// Number vs String → convert string to number
// 1 == 1
// Result: true

// Example 3: [] == false
[] == false
// Boolean involved → convert boolean to number first
// [] == 0
// Object vs Number → convert object to primitive
// "" == 0 (empty array → empty string)
// String vs Number → convert string to number
// 0 == 0
// Result: true

// Example 4: [] == ![]
[] == ![]
// First, evaluate ![] → false (arrays are truthy)
// [] == false
// Boolean involved → false becomes 0
// [] == 0
// Object vs Number → [] becomes ""
// "" == 0
// String vs Number → "" becomes 0
// 0 == 0
// Result: true (yes, really!)
Just use ===! The triple equals operator never coerces types. If the types are different, it returns false immediately. This is almost always what you want.
5 === "5"     // false (different types)
5 == "5"      // true (coerced)

null === undefined  // false
null == undefined   // true

Operators & Coercion Cheat Sheet

Quick reference for which operators trigger which coercion:
OperatorCoercion TypeExampleResult
+ (with string)String"5" + 3"53"
+ (unary)Number+"5"5
- * / %Number"5" - 32
++ --Numberlet x = "5"; x++6
> < >= <=Number"10" > 5true
== !=Complex"5" == 5true
=== !==None"5" === 5false
&& ||Boolean (internal)"hi" || "bye""hi"
!Boolean!"hello"false
if while ? :Booleanif ("hello")true
& | ^ ~Number (32-bit int)"5" | 05

JavaScript WAT Moments

Let’s explore the famous “weird parts” that make JavaScript… special.
"5" + 3         // "53" (string concatenation)
"5" - 3         // 2 (math!)

// Why? + does both addition AND concatenation
// If either operand is a string, it concatenates
// - only does subtraction, so it converts to numbers
[] + []         // "" 
// Both arrays → "", then "" + "" = ""

[] + {}         // "[object Object]"
// [] → "", {} → "[object Object]"

{} + []         // 0 (in browser console!)
// {} is parsed as empty block, then +[] = 0
// Wrap in parens to fix: ({}) + [] = "[object Object]"
true + true     // 2 (1 + 1)
true + false    // 1 (1 + 0)
true - true     // 0 (1 - 1)

// Booleans convert to 1 (true) or 0 (false)
[] == ![]       // true

// Step by step:
// 1. ![] → false (arrays are truthy, negated = false)
// 2. [] == false
// 3. [] == 0 (boolean → number)
// 4. "" == 0 (array → string)
// 5. 0 == 0 (string → number)
// 6. true!

// Meanwhile...
[] === ![]      // false (different types, no coercion)
"foo" + + "bar"   // "fooNaN"

// Step by step:
// 1. +"bar" is evaluated first (unary +)
// 2. +"bar" → NaN (can't convert "bar" to number)
// 3. "foo" + NaN → "fooNaN"
NaN === NaN     // false
NaN == NaN      // false

// NaN is the only value in JavaScript not equal to itself!
// This is by design (IEEE 754 spec)

// How to check for NaN:
Number.isNaN(NaN)     // true (correct way)
isNaN(NaN)            // true
isNaN("hello")        // true (wrong! it converts first)
Number.isNaN("hello") // false (correct)

Use Number.isNaN() instead of the global isNaN() for reliable NaN checking.
typeof NaN        // "number" (wat)
typeof null       // "object" (historical bug)
typeof []         // "object" (arrays are objects)
typeof function(){} // "function" (special case)
[1, 2] + [3, 4]   // "1,23,4"

// Arrays convert to strings:
// [1, 2] → "1,2"
// [3, 4] → "3,4"
// "1,2" + "3,4" → "1,23,4"

// To actually combine arrays:
[...[1, 2], ...[3, 4]]  // [1, 2, 3, 4]
[1, 2].concat([3, 4])   // [1, 2, 3, 4]

Best Practices

How to avoid coercion bugs:
  1. Use === instead of == — No surprises, no coercion
  2. Be explicit — Use Number(), String(), Boolean() when converting
  3. Validate input — Don’t assume types, especially from user input
  4. Use Number.isNaN() — Not isNaN() or === NaN
  5. Be careful with + — Remember it concatenates if any operand is a string

When Implicit Coercion IS Useful

Stack Overflow’s 2023 Developer Survey reports that type-related bugs remain among the most common debugging challenges for JavaScript developers. Despite the gotchas, some implicit coercion patterns are actually helpful:
// 1. Checking for null OR undefined in one shot
if (value == null) {
  // This catches BOTH null and undefined
  // Much cleaner than: if (value === null || value === undefined)
}

// 2. Boolean context is natural and readable
if (user) {
  // Truthy check - totally fine
}

if (items.length) {
  // Checking if array has items - totally fine
}

// 3. Quick string conversion
const str = value + "";
// or
const str = String(value);
// or
const str = `${value}`;

// 4. Quick number conversion
const num = +value;
// or
const num = Number(value);

Anti-Patterns to Avoid

// BAD: Relying on == for type-unsafe comparisons
if (x == true) { }  // Don't do this!
if (x) { }          // Do this instead

// BAD: Using == with 0 or ""
if (x == 0) { }     // Matches "", but not null (null == 0 is false!)
if (x === 0) { }    // Clear intent

// BAD: Truthy check when you need specific type
function process(count) {
  if (!count) return;  // Fails for count = 0!
  // ...
}

function process(count) {
  if (typeof count !== "number") return;  // Better
  // ...
}

Key Takeaways

The key things to remember about Type Coercion:
  1. Three conversions only — JavaScript converts to String, Number, or Boolean — nothing else
  2. Implicit vs Explicit — Know when JS converts automatically vs when you control it
  3. The 8 common falsy valuesfalse, 0, -0, 0n, "", null, undefined, NaN — everything else is truthy (plus the rare document.all)
  4. + is special — It prefers string concatenation if ANY operand is a string
  5. - * / % are consistent — They ALWAYS convert to numbers
  6. == coerces, === doesn’t — Use === by default to avoid surprises
  7. null == undefined — This is true, but neither equals anything else with ==
  8. Objects convert via valueOf() and toString() — Learn these methods to control conversion
  9. When in doubt, be explicit — Use Number(), String(), Boolean()
  10. NaN is unique — It’s the only value not equal to itself; use Number.isNaN() to check

Test Your Knowledge

Answer: "53" (string)The + operator, when one operand is a string, performs string concatenation. The number 3 is converted to "3", resulting in "5" + "3" = "53".
Answer:
  1. false
  2. 0
  3. -0
  4. 0n (BigInt zero)
  5. "" (empty string)
  6. null
  7. undefined
  8. NaN
Everything else is truthy, including [], {}, "0", and "false".Bonus: There’s also a 9th falsy value — document.all — a legacy browser API you’ll rarely encounter.
Answer: This is a multi-step coercion:
  1. ![] evaluates first: arrays are truthy, so ![] = false
  2. Now we have [] == false
  3. Boolean converts to number: [] == 0
  4. Array converts to primitive: "" == 0
  5. String converts to number: 0 == 0
  6. Result: true
Answer:
  • === (strict equality) never coerces. If types differ, it returns false immediately.
  • == (loose equality) coerces values to the same type before comparing, following a complex algorithm.
5 === "5"    // false (different types)
5 == "5"     // true (string coerced to number)
Best practice: Use === unless you specifically need coercion.
Answer:
Number(null)       // 0
Number(undefined)  // NaN
This inconsistency is a common source of bugs. null converts to 0 (like “nothing” = zero), while undefined converts to NaN (like “no value” = not a number).
Answer: "1hello"Step by step:
  1. true + false = 1 + 0 = 1 (booleans → numbers)
  2. 1 + "hello" = "1hello" (number → string for concatenation)

Frequently Asked Questions

Type coercion is JavaScript’s automatic conversion of values from one type to another. According to the ECMAScript specification, all coercion ultimately converts to one of three primitive types: String, Number, or Boolean. It can be implicit (triggered by operators) or explicit (using functions like Number(), String(), or Boolean()).
There are 8 common falsy values: false, 0, -0, 0n (BigInt zero), "" (empty string), null, undefined, and NaN. As documented by MDN, every other value in JavaScript is truthy — including empty arrays [], empty objects {}, and the string "0".
The + operator has a dual role: it performs both addition and string concatenation. When either operand is a string, + concatenates. The - operator only performs subtraction, so it always converts operands to numbers. This asymmetry is one of the most frequently asked JavaScript interview questions according to Stack Overflow surveys.
Explicit coercion is when you intentionally convert types using functions like Number("42") or String(42). Implicit coercion happens automatically when JavaScript encounters mismatched types in operations — for example, "5" - 3 implicitly converts "5" to the number 5. Most style guides recommend explicit coercion for clarity.
JavaScript uses the ToPrimitive abstract operation, which checks for a Symbol.toPrimitive method first, then falls back to valueOf() and toString(). For number hints, valueOf() is tried first; for string hints, toString() is tried first. Arrays convert via toString(), which is why [1,2,3] + "" produces "1,2,3".


Reference

Articles

Videos

Last modified on February 17, 2026