JavaScript Core
Type Coercion
How JavaScript converts types implicitly
Beginner
javascript types weird-parts
Definition
Type coercion is the automatic or implicit conversion of values from one data type to another. JavaScript is a loosely typed language with dynamic coercion rules that can lead to unexpected results when not fully understood.
Implicit Coercion
String Coercion
// Number to String
console.log("5" + 3); // "53" (number converted to string)
console.log("5" - 3); // 2 (string converted to number)
console.log("5" * "2"); // 10 (both converted to numbers)
// Boolean to String
console.log("result: " + true); // "result: true"
console.log("count: " + null); // "count: null"
// Object to String
console.log("user: " + {}); // "user: [object Object]"
console.log("" + [1, 2, 3]); // "1,2,3"
Number Coercion
// Unary plus operator
console.log(+"42"); // 42
console.log(+true); // 1
console.log(+false); // 0
console.log(+null); // 0
console.log(+undefined); // NaN
// Arithmetic operations
console.log("6" / "2"); // 3
console.log("10" % "3"); // 1
console.log(10 - "5"); // 5
// Empty string
console.log(+""); // 0
console.log("" - 1); // -1
Boolean Coercion
// Logical operators
console.log(!"hello"); // false (string is truthy)
console.log(!0); // true (0 is falsy)
console.log(!!"test"); // true (double negation)
// In conditions
if ("string") { /* executes */ }
if (42) { /* executes */ }
if (0) { /* doesn't execute */ }
if ("") { /* doesn't execute */ }
Explicit Coercion
String Conversion
// String constructor
String(42); // "42"
String(true); // "true"
String(null); // "null"
String(undefined); // "undefined"
String([1, 2, 3]); // "1,2,3"
String({a: 1}); // "[object Object]"
// toString method
(42).toString(); // "42"
true.toString(); // "true"
[1, 2, 3].toString(); // "1,2,3"
// Template literals
`${42}`; // "42"
`${{name: 'John'}}`; // "[object Object]"
Number Conversion
// Number constructor
Number("42"); // 42
Number("3.14"); // 3.14
Number(""); // 0
Number(" "); // 0
Number("hello"); // NaN
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(undefined); // NaN
// Parse functions (more lenient)
parseInt("42px"); // 42
parseInt("101", 2); // 5 (binary)
parseInt("FF", 16); // 255 (hex)
parseFloat("3.14abc"); // 3.14
// Unary plus (shorthand)
+"42"; // 42
+true; // 1
Boolean Conversion
// Boolean constructor
Boolean(1); // true
Boolean(0); // false
Boolean("hello"); // true
Boolean(""); // false
Boolean([]); // true
Boolean({}); // true
Boolean(null); // false
Boolean(undefined); // false
// Double negation (shorthand)
!!"hello"; // true
!!0; // false
!![]; // true
!!{}; // true
Falsy Values
// These coerce to false
false
0
-0
0n (BigInt zero)
"" (empty string)
null
undefined
NaN
document.all (legacy quirk)
// Everything else is truthy
if ([]) console.log("Empty array is truthy");
if ({}) console.log("Empty object is truthy");
if ("0") console.log("String '0' is truthy");
if (" ") console.log("String with space is truthy");
Equality Operators
Loose Equality (==)
// Type coercion happens!
5 == "5"; // true (string converted to number)
0 == false; // true
1 == true; // true
"" == false; // true
null == undefined; // true
// Arrays and objects
[] == false; // true
[] == 0; // true
[1] == "1"; // true
[1, 2] == "1,2"; // true
// Surprising cases
[] == ![]; // true (![] is false, [] == false)
"0" == false; // true
0 == "0"; // true
// NaN is never equal to anything
NaN == NaN; // false
Strict Equality (===)
// No type coercion
5 === "5"; // false (different types)
5 === 5; // true
null === undefined; // false
// Always use === and !==
// Only exception: checking null/undefined together
if (value == null) {
// catches both null and undefined
}
Object Comparison
// Object reference comparison
{} === {}; // false (different objects)
[] === []; // false
const obj = {};
obj === obj; // true (same reference)
// Arrays
[1, 2, 3] == [1, 2, 3]; // false
[1, 2, 3] === [1, 2, 3]; // false
Abstract Equality Algorithm
The Rules
- If types are same, use ===
- null and undefined are equal to each other
- Number and String: convert string to number
- Boolean: convert to number (true=1, false=0)
- Object and Primitive: convert object to primitive
// Object to primitive conversion
const obj = {
valueOf() { return 42; },
toString() { return "hello"; }
};
obj == 42; // true (valueOf used)
obj == "hello"; // false (valueOf takes precedence)
// When valueOf returns object
// default:
obj == "hello"; // true (falls back to toString)
Common Pitfalls
The typeof null Bug
typeof null === "object"; // true (historical bug)
// Safe null check
function isNull(value) {
return value === null;
}
// Check for object (excluding null)
function isObject(value) {
return typeof value === "object" && value !== null;
}
Array.isArray
typeof []; // "object"
Array.isArray([]); // true
// Safe array check
function isArray(value) {
return Array.isArray(value);
}
parseInt Gotchas
parseInt(0.0000008); // 8 (!!!)
// Because: 0.0000008 -> "8e-7" -> parseInt stops at 'e'
// Always use radix
parseInt("08"); // 8 (in strict mode)
parseInt("08", 10); // 8 (explicit, safe)
isNaN vs Number.isNaN
isNaN("hello"); // true (coerces to NaN)
isNaN(undefined); // true
isNaN({}); // true
Number.isNaN("hello"); // false (no coercion)
Number.isNaN(NaN); // true
Number.isNaN(0/0); // true
Best Practices
Always Use Strict Equality
// Good
if (count === 5) { }
if (name !== "") { }
// Bad
if (count == 5) { }
if (name != "") { }
// Exception: null/undefined check
if (value == null) {
// value is either null or undefined
}
Explicit Conversions
// Good - explicit
const num = Number(input);
const str = String(value);
const bool = Boolean(count);
// Acceptable - clear intent
const num = +input;
const str = `${value}`;
const bool = !!count;
// Bad - implicit, confusing
const num = input - 0;
const str = value + "";
const bool = count ? true : false;
Defensive Coding
// Convert to number safely
function toNumber(value) {
const num = Number(value);
return Number.isNaN(num) ? 0 : num;
}
// Convert to string safely
function toString(value) {
if (value == null) return "";
return String(value);
}
// Check for valid number
function isValidNumber(value) {
return typeof value === "number" &&
!Number.isNaN(value) &&
Number.isFinite(value);
}
Practical Examples
Form Input Handling
// Input values are always strings
function calculateTotal(priceInput, quantityInput) {
const price = Number(priceInput) || 0;
const quantity = parseInt(quantityInput, 10) || 0;
return price * quantity;
}
// User input: "19.99" and "3"
calculateTotal("19.99", "3"); // 59.97
URL Parameter Parsing
const params = new URLSearchParams("?page=1&active=true");
const page = parseInt(params.get("page"), 10); // 1
const active = params.get("active") === "true"; // true (explicit)
Feature Detection
// Check if feature exists
if (typeof fetch !== "undefined") {
// Use fetch API
}
// Check for null/undefined
if (config != null) {
// config has some value
}
Key Takeaway
Always prefer strict equality (===) over loose equality (==) to avoid unexpected coercion. When conversion is needed, be explicit with Number(), String(), or Boolean(). Understand falsy values and use Array.isArray() and Number.isNaN() for reliable type checking. Explicit is better than implicit - make your intentions clear in the code.