Type Coercion and Equality
Understanding JavaScript's type system and comparison operators
Challenge
Section titled “Challenge”Why do these comparisons produce unexpected results?
console.log(0 == false); // true or false?console.log(0 === false); // true or false?console.log('' == false); // true or false?console.log(null == undefined); // true or false?console.log([1] == 1); // true or false?Explanation
Section titled “Explanation”JavaScript performs automatic type conversion (coercion) in certain contexts. Understanding strict (===) vs loose (==) equality is critical for avoiding bugs.
Loose Equality (==)
Section titled “Loose Equality (==)”// == performs type coercionconsole.log(0 == false); // true - false converts to 0console.log(1 == true); // true - true converts to 1console.log('' == false); // true - '' converts to 0, false converts to 0console.log('5' == 5); // true - '5' converts to 5console.log(null == undefined); // true - special caseconsole.log([1] == 1); // true - [1] converts to 1
// Coercion rules:// 1. If types are different, coerce to common type// 2. Object to primitive: call valueOf() then toString()// 3. String to number: parse string as numberStrict Equality (===)
Section titled “Strict Equality (===)”// === No type coercion, must be same typeconsole.log(0 === false); // false - different typesconsole.log(1 === true); // false - different typesconsole.log('5' === 5); // false - different typesconsole.log(null === undefined); // false - different typesconsole.log([] === []); // false - different object instancesconsole.log(NaN === NaN); // false - NaN special case!
// Use Object.is() for strict comparison including NaNconsole.log(Object.is(NaN, NaN)); // trueconsole.log(Object.is(-0, +0)); // false (unlike ===)Solution: Safe Comparison
Section titled “Solution: Safe Comparison”// ✅ Best Practice: Always use ===function compareValues(a, b) { return a === b; // Explicit, predictable}
// ✅ For NaN checkingconst value = NaN;console.log(Number.isNaN(value)); // trueconsole.log(Object.is(value, NaN)); // true
// ✅ Nullish coalescing operator (prefer over ==)const username = null;const defaultName = username ?? 'Guest'; // Only null/undefined
// ❌ Avoid loose equality with nullish valuesconst value = null;console.log(value == undefined); // true (but values differ!)console.log(value === undefined); // false (correct)Implicit Type Coercion
Section titled “Implicit Type Coercion”// In arithmetic operationsconsole.log('5' + 3); // '53' - string concatenationconsole.log('5' - 3); // 2 - numeric subtractionconsole.log('5' * '2'); // 10 - numeric multiplicationconsole.log('5' / '2'); // 2.5 - numeric divisionconsole.log(true + 1); // 2 - true converts to 1console.log(false + 1); // 1 - false converts to 0
// In boolean contextconsole.log(!!'hello'); // true - truthy valueconsole.log(!!0); // false - falsy valueconsole.log(!!null); // false - falsy valueconsole.log(!!undefined); // false - falsy valueconsole.log(!!''); // false - falsy valueconsole.log(!!NaN); // false - falsy valueconsole.log(!!-1); // true - all other numbers truthyTruthy and Falsy Values
Section titled “Truthy and Falsy Values”const falsy = [ false, 0, -0, 0n, // BigInt '', // empty string null, undefined, NaN];
const truthy = [ true, 1, -1, '0', // non-empty string (even '0') ' ', // whitespace [], // empty array {}, // empty object () => {} // function];
// Practical: Checking for valuesfunction isValid(value) { return value != null; // Handles null and undefined}
console.log(isValid(0)); // trueconsole.log(isValid('')); // trueconsole.log(isValid(false)); // trueconsole.log(isValid(null)); // falseconsole.log(isValid(undefined)); // falseObject to Primitive Conversion
Section titled “Object to Primitive Conversion”// When object is coerced to primitive, JavaScript calls:// 1. valueOf() - should return primitive// 2. toString() - fallback
const custom = { value: 42, valueOf() { return this.value; }, toString() { return `Custom(${this.value})`; }};
console.log(custom + 10); // 52 (valueOf called)console.log(String(custom)); // 'Custom(42)' (toString called)console.log(`Value: ${custom}`); // 'Value: Custom(42)'
// Array to primitiveconst arr = [1, 2, 3];console.log(arr + 0); // '1,2,30' - arr.toString() = '1,2,3'console.log(String(arr)); // '1,2,3'console.log(Number(arr)); // NaN - can't convert
const single = [42];console.log(Number(single)); // 42 - special caseCommon Pitfalls
Section titled “Common Pitfalls”// ⚠️ Confusing loose equalityif (value == true) { } // Dangerous!if (value) { } // Better - boolean context
// ⚠️ String concatenationconst result = 1 + 2 + '3'; // '33' not '6'const better = 1 + 2 + Number('3'); // 6
// ⚠️ Array comparisonconst arr = [1, 2];if (arr == [1, 2]) { } // false! Different objectsif (arr.toString() === [1, 2].toString()) { } // true
// ⚠️ setTimeout callbacksetTimeout((x = 1) => { // x is parameter, not coerced console.log(x);}, 100, true); // true passed as argument, becomes 1Type Checking Best Practices
Section titled “Type Checking Best Practices”// Checking types safelyfunction checkType(value) { // typeof for primitives console.log(typeof value);
// instanceof for objects console.log(value instanceof Array);
// Object.prototype.toString for accurate type console.log(Object.prototype.toString.call(value));
// Number check (handles NaN) console.log(Number.isFinite(value));
// Array check (modern) console.log(Array.isArray(value));
// Null check console.log(value === null);}
// Safe type coercionfunction safeToNumber(value) { const num = Number(value); return Number.isFinite(num) ? num : 0;}
function safeToString(value) { return value == null ? '' : String(value);}
function safeToBoolean(value) { return Boolean(value);}