Type coercion is behind many JavaScript gotchas. Learn == vs ===, implicit conversion rules, and how to answer tricky output questions.
JavaScript is like an overly helpful translator who never admits they don't understand something. When you give it two different types to compare or combine, instead of stopping and saying "these don't match," it quietly converts one value into a type it can work with — and then proceeds. This conversion is type coercion. The problem is that the "translation" follows rules that are consistent but deeply unintuitive. JavaScript isn't random — it's following a spec — but the spec's rules produce results that look like bugs until you've studied them. There are two kinds: implicit coercion (JavaScript does it automatically, without you asking) and explicit coercion (you do it intentionally with Number(), String(), Boolean()). The key to mastering coercion is learning the three conversion algorithms JavaScript uses internally: ToNumber, ToString, and ToBoolean. Once you know the tables, the "weird" results stop being surprising.
There are exactly 8 falsy values in JavaScript. Everything else is truthy.
| Falsy value | Notes |
|---|---|
false | the boolean false |
0 | positive zero |
-0 | negative zero |
0n | BigInt zero |
"" | empty string (any empty string) |
null | |
undefined | |
NaN |
Truthy surprises — values you might expect to be falsy that aren't:
Boolean("0") // true — non-empty string, even if it's "0"
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
Number(true) // 1
Number(false) // 0
Number(null) // 0
Number(undefined) // NaN
Number("") // 0
Number(" ") // 0 (whitespace strings → 0)
Number("42") // 42
Number("3.14") // 3.14
Number("42abc") // NaN (can't fully parse)
Number([]) // 0 ([] → "" → 0)
Number([3]) // 3 ([3] → "3" → 3)
Number([1,2]) // NaN ([1,2] → "1,2" → NaN)
Number({}) // NaN ({} → "[object Object]" → NaN)
String(true) // "true"
String(false) // "false"
String(null) // "null"
String(undefined) // "undefined"
String(0) // "0"
String(-0) // "0" ← watch out
String([1,2,3]) // "1,2,3"
String([]) // ""
String({}) // "[object Object]"
The + operator is the most dangerous source of implicit coercion. Its rule: if either operand is a string (or can be converted to one), it concatenates. Otherwise it adds.
"5" + 3 // "53" — 3 coerced to string
5 + "3" // "53"
"5" + true // "5true"
"5" + null // "5null"
"5" + undefined // "5undefined"
5 + null // 5 — null coerced to 0
5 + undefined // NaN — undefined coerced to NaN
5 + true // 6 — true coerced to 1
[] + [] // "" — both become "", concatenated
[] + {} // "[object Object]"
{} + [] // 0 — {} parsed as empty block, +[] = 0
-, *, /, %, ** always trigger ToNumber on both operands.
"5" - 3 // 2 — "5" → 5
"5" * "2" // 10 — both to numbers
true + true // 2 — both → 1
false * 5 // 0
null * 5 // 0 — null → 0
undefined * 5 // NaN — undefined → NaN
"abc" - 1 // NaN — "abc" → NaN
The == (loose equality) algorithm follows a specific set of coercion rules. Understanding them is the key to the "weird" outputs you see in interviews.
// Type matching — no coercion
1 == 1 // true
"a" == "a" // true
// null == undefined — special case, always true
null == undefined // true
null == 0 // false ← critical interview trap
null == false // false
undefined == false // false
// Number vs String — string converts to number
"42" == 42 // true ("42" → 42)
"0" == 0 // true ("0" → 0)
"" == 0 // true ("" → 0)
// Boolean vs anything — boolean converts to number FIRST
true == 1 // true (true → 1)
false == 0 // true (false → 0)
true == "1" // true (true → 1, then "1" == 1, "1" → 1)
false == "" // true (false → 0, "" → 0)
false == "0" // true (false → 0, "0" → 0)
// Object vs primitive — calls valueOf() or toString()
[0] == false // true ([0] → "0" → 0, false → 0)
[] == false // true ([] → "" → 0, false → 0)
[] == 0 // true ([] → "" → 0)
"" == false // true (false → 0, "" → 0)
===).null and undefined? Always true to each other, always false to everything else.ToPrimitive on the object (tries valueOf() then toString()), compare result.// To number
Number("42") // 42 — clearest
parseInt("42px", 10) // 42 — parses integer, stops at non-numeric
parseFloat("3.14em") // 3.14
+"42" // 42 — unary + (common, but less readable)
"42" * 1 // 42 — works but confusing
// To string
String(42) // "42" — clearest
(42).toString() // "42"
`${42}` // "42" — template literal
// To boolean
Boolean(value) // clearest
!!value // common shorthand — double negation
typeof NaN // "number" — NaN is a number type
NaN === NaN // false — NaN is not equal to itself
NaN == NaN // false — same
// Correct way to check for NaN:
Number.isNaN(NaN) // true
Number.isNaN("NaN") // false — doesn't coerce, unlike global isNaN()
isNaN("abc") // true — global isNaN coerces first, unreliableMany devs think empty array [] is falsy — but actually [] is truthy. Only 8 specific values are falsy in JavaScript, and empty array is not one of them. Boolean([]) === true. This trips up nearly every developer at least once.
Many devs think null == false is true — but actually null only loosely equals undefined and nothing else. null == false is false. null == 0 is false. This is a special-cased exception in the == algorithm specifically to make null checks safe with ==.
Many devs think + always does addition — but actually + is string concatenation when either operand is a string. The moment one side is a string, the other is converted to string and they're concatenated. "5" + 3 is "53", not 8. Subtraction, multiplication, and division always convert to numbers — only + has this dual behaviour.
Many devs think the fix for coercion bugs is to always use === — but actually === only prevents type coercion in comparisons. Arithmetic coercion (like "5" - 3) still happens with strict equality elsewhere in the code. Understanding coercion is the real fix; === is just one defensive tool.
Many devs think NaN means "not a number in type" — but actually typeof NaN === "number". NaN is a numeric value that represents an invalid computation result. It's still the Number type. The check NaN === NaN is false because IEEE 754 floating point defines it that way — use Number.isNaN() instead.
Many devs think explicit coercion like Number() and String() is the same as implicit coercion — but actually they follow the same conversion algorithms (ToNumber, ToString). The difference is intent: explicit coercion is deliberate and readable, implicit happens as a side effect and is often surprising.
JSON.stringify coerces values silently — undefined, functions, and symbols are dropped from objects entirely, not converted to strings. Arrays with these values get null substituted. Production bugs have been caused by assuming JSON serialization preserves all values.
Form input values in the browser are always strings — document.getElementById('age').value returns "25", not 25. Arithmetic on form values without explicit conversion produces string concatenation. This bug ships to production constantly: "25" + 1 = "251" instead of 26.
localStorage and sessionStorage only store strings — storing a number or boolean and reading it back gives you a string. Developers who don't know this write bugs where stored settings like "false" are truthy when read back.
React's conditional rendering uses truthiness checks — rendering {count && <Component />} will render the number 0 when count is 0, not nothing, because 0 is falsy but React renders falsy numbers as text. The correct pattern is {count > 0 && <Component />} or {Boolean(count) && <Component />}.
TypeScript exists largely because of JavaScript's coercion model — the entire point of a type system is to catch the class of bugs where two incompatible types meet unexpectedly and coercion silently produces a wrong result. Understanding coercion helps you understand why TypeScript's type errors matter.
The == operator is not always wrong — null == undefined is a useful pattern for checking "this value was either not provided or explicitly cleared" with a single check instead of value === null || value === undefined. Many style guides allow this specific use of ==.
No questions tagged to this topic yet.
Tag questions in Admin → Questions by setting the "Topic Page" field to javascript-type-coercion-interview-questions.
Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.