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.14These functions — Number(), String(), Boolean(), parseInt(), and parseFloat() — give you full control.
This is the safe way. You know exactly what's happening.
JavaScript automatically converts types when operators or functions expect a different type.
// JavaScript "helps" without asking
"5" + 3 // "53" (number became string)
"5" - 3 // 2 (string became number)
if ("hello") {} // string became boolean (true)
5 == "5" // true (types were coerced)This is where bugs hide. Learn the rules or suffer the consequences!
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 thisOther 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 Type | Explicit Method | Common Implicit Triggers |
|---|---|---|
| String | String(value) | + with a string, template literals |
| Number | Number(value) | Math operators (- * / %), comparisons |
| Boolean | Boolean(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
| Value | Result | Notes |
|---|---|---|
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; // 15Number 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
| Value | Result | Notes |
|---|---|---|
"123" | 123 | Numeric strings work |
" 123 " | 123 | Whitespace is trimmed |
"123abc" | NaN | Any non-numeric char → NaN |
"" | 0 | Empty string becomes 0 |
" " | 0 | Whitespace-only becomes 0 |
true | 1 | |
false | 0 | |
null | 0 | null → 0 |
undefined | NaN | undefined → NaN (different!) |
[] | 0 | Empty array → "" → 0 |
[1] | 1 | Single element array |
[1, 2] | NaN | Multiple elements → NaN |
{} | NaN | Objects → 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 // NaNMath 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
+"" // 0Boolean 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 NOTThe 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) // falseTechnical 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) // trueCommon 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 emptyLogical 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
Check if already primitive
If the value is already a primitive (string, number, boolean, etc.), return it as-is.
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)
Try valueOf() or toString()
- For number hint: try
valueOf()first, thentoString() - For string hint: try
toString()first, thenvalueOf()
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
Same type?
Compare directly (like ===).
5 == 5 // true
"hello" == "hello" // truenull or undefined?
null == undefined is true. Neither equals anything else.
null == undefined // true
null == null // true
null == 0 // false (special rule!)
null == "" // falseNumber vs String?
Convert the string to a number.
5 == "5"
// becomes: 5 == 5
// result: trueBoolean 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!)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: trueStep-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 // trueOperators & Coercion Cheat Sheet
Quick reference for which operators trigger which coercion:
| Operator | Coercion Type | Example | Result |
|---|---|---|---|
+ (with string) | String | "5" + 3 | "53" |
+ (unary) | Number | +"5" | 5 |
- * / % | Number | "5" - 3 | 2 |
++ -- | Number | let x = "5"; x++ | 6 |
> < >= <= | Number | "10" > 5 | true |
== != | Complex | "5" == 5 | true |
=== !== | None | "5" === 5 | false |
&& || | Boolean (internal) | "hi" || "bye" | "hi" |
! | Boolean | !"hello" | false |
if while ? : | Boolean | if ("hello") | true |
& | ^ ~ | Number (32-bit int) | "5" | 0 | 5 |
JavaScript WAT Moments
Let's explore the famous "weird parts" that make JavaScript... special.
Best Practices
How to avoid coercion bugs:
- Use
===instead of==— No surprises, no coercion - Be explicit — Use
Number(),String(),Boolean()when converting - Validate input — Don't assume types, especially from user input
- Use
Number.isNaN()— NotisNaN()or=== NaN - 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:
-
Three conversions only — JavaScript converts to String, Number, or Boolean — nothing else
-
Implicit vs Explicit — Know when JS converts automatically vs when you control it
-
The 8 common falsy values —
false,0,-0,0n,"",null,undefined,NaN— everything else is truthy (plus the raredocument.all) -
+ is special — It prefers string concatenation if ANY operand is a string
-
- * / % are consistent — They ALWAYS convert to numbers
-
== coerces, === doesn't — Use
===by default to avoid surprises -
null == undefined — This is true, but neither equals anything else with
== -
Objects convert via valueOf() and toString() — Learn these methods to control conversion
-
When in doubt, be explicit — Use
Number(),String(),Boolean() -
NaN is unique — It's the only value not equal to itself; use
Number.isNaN()to check
Test Your Knowledge
Frequently Asked Questions
Related Concepts
Primitive Types
Understanding the basic data types that coercion converts between
Primitives vs Objects
How primitives and objects behave differently during coercion
Equality Operators
Deep dive into ==, ===, and how coercion affects comparisons
JavaScript Engines
How engines like V8 implement type coercion internally
Reference
Type Coercion — MDN
Official MDN glossary entry explaining type coercion fundamentals.
Equality Comparisons — MDN
Comprehensive guide to ==, ===, Object.is() and the coercion rules behind each.
Type Conversion — MDN
The difference between type coercion (implicit) and type conversion (explicit).
Truthy and Falsy — MDN
Complete list of falsy values and how boolean context works.
Articles
JavaScript Type Coercion Explained
Comprehensive freeCodeCamp article by Alexey Samoshkin covering all coercion rules with tons of examples and quiz questions. One of the best resources available.
What you need to know about Javascript's Implicit Coercion
Practical guide by Promise Tochi covering implicit coercion patterns, valueOf/toString, and common gotchas with clear examples.
Object to Primitive Conversion
Deep-dive from javascript.info into how objects convert to primitives using Symbol.toPrimitive, toString, and valueOf. Essential for advanced understanding.
You Don't Know JS: Types & Grammar, Ch. 4
Kyle Simpson's definitive deep-dive into JavaScript coercion. Explains abstract operations, ToString, ToNumber, ToBoolean, and the "why" behind every rule. Free to read online.
Videos
== ? === ??? ...#@^% — JSConf EU
Entertaining JSConf talk by Shirmung Bielefeld exploring the chaos of JavaScript equality operators with live examples and audience participation.
Coercion in Javascript — Hitesh Choudhary
Hitesh walks through coercion step-by-step in the browser console, showing exactly what JavaScript does at each conversion. Good pace for beginners.
What is Coercion? — Steven Hancock
Steven breaks down the three conversion types (string, number, boolean) with simple examples. Short video that covers the fundamentals quickly.
