Deep Dive: Type Coercion & Edge Cases
This lesson explores the trickier aspects of JavaScript's type system that every developer should understand.
Explicit vs Implicit Coercion
Explicit Coercion
You intentionally convert types using built-in functions:
javascript1// To String2String(123) // "123"3String(true) // "true"4String(null) // "null"5(123).toString() // "123"67// To Number8Number("42") // 429Number("42px") // NaN10Number(true) // 111Number(false) // 012Number(null) // 013Number(undefined) // NaN14parseInt("42px") // 42 (stops at non-digit)15parseFloat("3.14m") // 3.141617// To Boolean18Boolean(0) // false19Boolean("") // false20Boolean("0") // true (non-empty string!)21Boolean([]) // true (object!)22Boolean({}) // true23!!value // Double NOT - common idiom
Implicit Coercion
JavaScript automatically converts types in certain operations:
javascript1// + with strings concatenates2"5" + 3 // "53"33 + "5" // "35"4"" + 42 // "42"56// Other math operators convert to numbers7"6" - 2 // 48"6" * "2" // 129"6" / 2 // 31011// Comparison operators12"10" > 9 // true (string "10" converted to 10)13"10" > "9" // false (string comparison: "1" < "9")
The Abstract Equality Algorithm (==)
When using ==, JavaScript follows specific rules:
- If types are the same, compare values
null == undefinedistrue- Number vs String: convert string to number
- Boolean vs anything: convert boolean to number first
- Object vs primitive: convert object via
valueOf()ortoString()
javascript1// Examples2"5" == 5 // true (string to number)3true == 1 // true (boolean to number)4false == 0 // true5null == undefined // true (special case)6null == false // false (null only equals undefined)7[] == false // true ([] -> "" -> 0, false -> 0)8[] == ![] // true (this is why we use ===)
Why Always Use ===
Strict equality (===) never coerces types:
javascript1"5" === 5 // false2true === 1 // false3null === undefined // false4[] === false // false
Rule: Always use === and !== unless you have a specific reason not to.
Truthy/Falsy Edge Cases
Only 6 falsy values exist in JavaScript:
javascript1if (false) {} // falsy2if (0) {} // falsy3if (-0) {} // falsy (yes, -0 exists)4if ("") {} // falsy5if (null) {} // falsy6if (undefined) {} // falsy7if (NaN) {} // falsy
Everything else is truthy, including some surprises:
javascript1if ("0") {} // truthy (non-empty string)2if ("false") {} // truthy (non-empty string)3if ([]) {} // truthy (object)4if ({}) {} // truthy (object)5if (function(){}) {} // truthy6if (new Boolean(false)) {} // truthy (object wrapper!)
Object Wrapper Gotchas
Primitive wrappers can cause confusion:
javascript1const str = "hello"; // primitive2const strObj = new String("hello"); // object wrapper34typeof str // "string"5typeof strObj // "object"67str == strObj // true (coercion)8str === strObj // false (different types)910// Boolean wrappers are especially dangerous11const falseObj = new Boolean(false);12if (falseObj) {13 console.log("This runs!"); // Object is truthy!14}
Rule: Never use new String(), new Number(), or new Boolean().
NaN: Not a Number
NaN is a special numeric value with unique behavior:
javascript1// NaN is produced by invalid math20 / 0 // NaN3Math.sqrt(-1) // NaN4parseInt("hello") // NaN5undefined + 1 // NaN67// NaN is not equal to anything, including itself!8NaN === NaN // false9NaN == NaN // false1011// Use Number.isNaN() to check12Number.isNaN(NaN) // true13Number.isNaN("hello") // false (doesn't coerce)1415// Avoid global isNaN() - it coerces first16isNaN("hello") // true (coerces to NaN, then checks)17Number.isNaN("hello") // false (correct)
Infinity and -Infinity
javascript11 / 0 // Infinity2-1 / 0 // -Infinity3Infinity > 1000000 // true4Infinity === Infinity // true56// Check with isFinite()7isFinite(100) // true8isFinite(Infinity) // false9Number.isFinite(Infinity) // false
The + Operator Precedence
The + operator behaves differently based on operand types:
javascript1// Left to right evaluation21 + 2 + "3" // "33" (3 + "3")3"1" + 2 + 3 // "123" (all string concat)41 + 2 + 3 + "4" // "64" (6 + "4")56// Unary + converts to number7+"42" // 428+true // 19+null // 010+undefined // NaN11+"hello" // NaN12+[] // 0 ([] -> "" -> 0)13+{} // NaN
Object to Primitive Conversion
When objects need to become primitives:
javascript1// Default: valueOf() then toString()2const obj = {3 valueOf() { return 42; },4 toString() { return "hello"; }5};67obj + 1 // 43 (valueOf used)8`${obj}` // "hello" (toString for strings)910// Arrays convert via join()11[1, 2, 3] + "" // "1,2,3"12[1] + [2] // "12"1314// Objects convert via toString()15{} + [] // 0 (depends on context!)16[] + {} // "[object Object]"
Short-Circuit Evaluation
Logical operators return actual values, not just booleans:
javascript1// && returns first falsy or last value2true && "hello" // "hello"3false && "hello" // false4"a" && "b" && "c" // "c"5"a" && 0 && "c" // 067// || returns first truthy or last value8false || "hello" // "hello"9"a" || "b" // "a"100 || "" || null // null1112// Common patterns13const name = user.name || "Anonymous";14const valid = data && data.isValid && data.isValid();
Nullish Coalescing vs OR
Understanding the difference is crucial:
javascript1// || treats all falsy values the same20 || "default" // "default" (0 is falsy)3"" || "default" // "default" ('' is falsy)4false || "default" // "default"56// ?? only triggers on null/undefined70 ?? "default" // 08"" ?? "default" // ""9false ?? "default" // false10null ?? "default" // "default"11undefined ?? "default" // "default"1213// Real-world example14function getConfig(options) {15 // BAD: 0 and false would use defaults16 const timeout = options.timeout || 5000;17 const enabled = options.enabled || true;1819 // GOOD: only null/undefined use defaults20 const timeout = options.timeout ?? 5000;21 const enabled = options.enabled ?? true;22}
Practical Type Checking Patterns
javascript1// Check for null or undefined2if (value == null) { /* null or undefined */ }3if (value === null || value === undefined) { /* same but explicit */ }45// Check for array6Array.isArray(value)78// Check for plain object9typeof value === 'object' && value !== null && !Array.isArray(value)1011// Check for function12typeof value === 'function'1314// Check for number (excluding NaN)15typeof value === 'number' && Number.isFinite(value)1617// Check for integer18Number.isInteger(value)1920// Check for empty string21value === ''22// or truthy string23typeof value === 'string' && value.length > 0
Summary
- Use explicit coercion when you need to convert types
- Always use
===instead of== - Understand falsy values - there are only 6
- Use
??for defaults when 0 or '' are valid values - Use
Number.isNaN()notisNaN() - Avoid object wrappers like
new String()
These concepts will help you avoid subtle bugs and write more predictable JavaScript code.