javascript-today

Type Coercion

Type coercion is JavaScript’s automatic conversion of values from one type to another. Understanding coercion helps you avoid bugs and write predictable code.

What is Type Coercion?

Implicit coercion = JavaScript converts types automatically
Explicit coercion = You manually convert types

// Implicit - JavaScript converts automatically
console.log('5' - 3);      // 2 (string '5' → number 5)
console.log('5' + 3);      // '53' (number 3 → string '3')

// Explicit - you convert manually
console.log(Number('5') + 3);  // 8
console.log('5' + String(3));  // '53'

String Coercion

The + Operator

When + is used with a string, everything becomes a string:

// String + anything = string
console.log('5' + 3);          // '53'
console.log('Hello' + 5);      // 'Hello5'
console.log('5' + true);       // '5true'
console.log('5' + null);       // '5null'
console.log('5' + undefined);  // '5undefined'

// Multiple operations (left to right)
console.log('5' + 3 + 2);      // '532' ('5' + 3 = '53', '53' + 2 = '532')
console.log(5 + 3 + '2');      // '82' (5 + 3 = 8, 8 + '2' = '82')
console.log('5' + (3 + 2));    // '55' (3 + 2 = 5, '5' + 5 = '55')

Common Gotcha

const a = '10';
const b = '5';

console.log(a + b);  // '105' (concatenation, not addition!)

// Solution: convert to numbers first
console.log(Number(a) + Number(b));  // 15
console.log(parseInt(a) + parseInt(b));  // 15
console.log(+a + +b);  // 15 (unary + converts to number)

Explicit String Conversion

// String() function
console.log(String(42));        // '42'
console.log(String(true));      // 'true'
console.log(String(null));      // 'null'
console.log(String(undefined)); // 'undefined'

// .toString() method
console.log((42).toString());   // '42'
console.log(true.toString());   // 'true'

// Template literal
console.log(`${42}`);           // '42'
console.log(`${true}`);         // 'true'

Number Coercion

Arithmetic Operators (-, *, /, %)

These operators convert to numbers:

// String to number conversion
console.log('10' - 5);    // 5 (string '10' → number 10)
console.log('10' * 2);    // 20
console.log('10' / 2);    // 5
console.log('10' % 3);    // 1

// But + is different!
console.log('10' + 5);    // '105' (number 5 → string '5')

// Multiple strings
console.log('10' - '5');  // 5 (both strings → numbers)
console.log('10' * '2');  // 20

// Invalid conversions
console.log('hello' - 5);    // NaN
console.log('10px' * 2);     // NaN

Unary + Operator

Quickest way to convert to number:

console.log(+'42');       // 42
console.log(+'3.14');     // 3.14
console.log(+'  10  ');   // 10 (trims whitespace)
console.log(+true);       // 1
console.log(+false);      // 0
console.log(+null);       // 0
console.log(+undefined);  // NaN
console.log(+'hello');    // NaN

Explicit Number Conversion

// Number() function (strict)
console.log(Number('42'));      // 42
console.log(Number('3.14'));    // 3.14
console.log(Number('  10  '));  // 10
console.log(Number(true));      // 1
console.log(Number(false));     // 0
console.log(Number(null));      // 0
console.log(Number(undefined)); // NaN
console.log(Number('42px'));    // NaN (fails on invalid)

// parseInt() (lenient)
console.log(parseInt('42'));        // 42
console.log(parseInt('42.99'));     // 42 (drops decimal)
console.log(parseInt('42px'));      // 42 (stops at non-digit)
console.log(parseInt('  42  '));    // 42

// parseFloat()
console.log(parseFloat('3.14'));    // 3.14
console.log(parseFloat('3.14px'));  // 3.14
console.log(parseFloat('  2.5  ')); // 2.5

Boolean Coercion

Falsy Values

Only 6 values are falsy (convert to false):

Boolean(false)      // false
Boolean(0)          // false
Boolean('')         // false (empty string)
Boolean(null)       // false
Boolean(undefined)  // false
Boolean(NaN)        // false

// Everything else is truthy!
Boolean(true)       // true
Boolean(1)          // true
Boolean(-1)         // true
Boolean('0')        // true (non-empty string!)
Boolean('false')    // true (non-empty string!)
Boolean([])         // true (empty array!)
Boolean({})         // true (empty object!)

if Statements and Truthiness

const value = '';

// Implicit boolean conversion
if (value) {
  console.log('Truthy');
} else {
  console.log('Falsy');  // This runs
}

// Equivalent to
if (Boolean(value)) {
  console.log('Truthy');
} else {
  console.log('Falsy');
}

Common Patterns

// Check if string is not empty
const name = '';
if (name) {
  console.log('Name provided');
} else {
  console.log('Name is empty');  // This runs
}

// Check if number is not zero
const count = 0;
if (count) {
  console.log('Has items');
} else {
  console.log('No items');  // This runs
}

// Check if variable is defined and not null
const user = null;
if (user) {
  console.log('User exists');
} else {
  console.log('No user');  // This runs
}

Explicit Boolean Conversion

// Boolean() function
console.log(Boolean(1));        // true
console.log(Boolean(0));        // false
console.log(Boolean('hello'));  // true
console.log(Boolean(''));       // false

// !! (double NOT operator - common trick)
console.log(!!1);        // true
console.log(!!0);        // false
console.log(!!'hello');  // true
console.log(!!'');       // false

Logical Operators and Coercion

&& (AND) Returns Value, Not Boolean

// Returns first falsy value, or last value if all truthy
console.log(0 && 5);          // 0 (first falsy)
console.log('' && 'hello');   // '' (first falsy)
console.log(5 && 10);         // 10 (all truthy, return last)
console.log('a' && 'b' && 'c'); // 'c'

// Common pattern: conditional execution
const user = { name: 'Alice' };
user && console.log(user.name);  // Logs 'Alice' if user exists

|| (OR) Returns Value, Not Boolean

// Returns first truthy value, or last value if all falsy
console.log(0 || 5);          // 5 (first truthy)
console.log('' || 'hello');   // 'hello' (first truthy)
console.log(null || 0);       // 0 (all falsy, return last)
console.log('a' || 'b');      // 'a' (first truthy)

// Common pattern: default values
const name = userInput || 'Guest';
const port = process.env.PORT || 3000;

Watch Out for Falsy Defaults

// ❌ Problem: 0 and '' are valid but falsy
const count = 0;
const display = count || 10;  // 10 (oops! we wanted 0)

const name = '';
const greeting = name || 'Guest';  // 'Guest' (maybe we wanted empty?)

// ✅ Solution: use ?? (nullish coalescing)
const display = count ?? 10;   // 0 (only replaces null/undefined)
const greeting = name ?? 'Guest';  // '' (only replaces null/undefined)

Nullish Coalescing (??)

Only replaces null and undefined (not other falsy values):

console.log(0 ?? 10);          // 0 (0 is not null/undefined)
console.log('' ?? 'default');  // '' (empty string is not null/undefined)
console.log(false ?? true);    // false

console.log(null ?? 10);       // 10 (null is replaced)
console.log(undefined ?? 10);  // 10 (undefined is replaced)

// Comparison with ||
console.log(0 || 10);          // 10 (|| replaces falsy)
console.log(0 ?? 10);          // 0 (?? only replaces null/undefined)

When to Use ?? vs ||

// Use ?? for values where 0, '', false are valid
const timeout = userTimeout ?? 5000;  // 0 is valid timeout
const message = userMessage ?? 'Default';  // '' is valid message
const enabled = userEnabled ?? true;  // false is valid

// Use || for values where only null/undefined are invalid
const name = userName || 'Guest';  // Empty string should become 'Guest'

Comparison Operators

== (Loose Equality) - Coerces Types

// These are all true with ==
console.log(5 == '5');         // true (string '5' → number 5)
console.log(0 == false);       // true (false → 0)
console.log('' == 0);          // true ('' → 0, false → 0)
console.log(null == undefined); // true (special case)
console.log([1] == 1);         // true ([1] → '1' → 1)

// Confusing cases
console.log(' ' == 0);         // true (space → 0)
console.log('0' == false);     // true
console.log([] == 0);          // true ([] → '' → 0)

=== (Strict Equality) - No Coercion

// All false with ===
console.log(5 === '5');         // false (different types)
console.log(0 === false);       // false
console.log('' === 0);          // false
console.log(null === undefined); // false

// Only true if type AND value match
console.log(5 === 5);           // true
console.log('hello' === 'hello'); // true
console.log(true === true);     // true

Always Use ===

// ❌ Bad - unpredictable with ==
if (count == 0) {  // Also matches '', false, [], etc.
  // ...
}

// ✅ Good - explicit with ===
if (count === 0) {  // Only matches exactly 0
  // ...
}

// Exception: checking for null or undefined
if (value == null) {  // Matches both null and undefined
  // Same as: value === null || value === undefined
}

Object Coercion

toString() Method

const obj = { name: 'Alice' };
console.log(String(obj));  // '[object Object]'
console.log(obj + '');     // '[object Object]'

// Custom toString()
const person = {
  name: 'Bob',
  toString() {
    return this.name;
  }
};
console.log(String(person));  // 'Bob'
console.log(person + '!');    // 'Bob!'

valueOf() Method

const obj = {
  value: 42,
  valueOf() {
    return this.value;
  }
};

console.log(obj + 10);  // 52 (obj.valueOf() + 10)
console.log(obj * 2);   // 84

Array Coercion

// Arrays convert to comma-separated strings
console.log([1, 2, 3] + '');   // '1,2,3'
console.log(String([1, 2, 3])); // '1,2,3'

console.log([5] + 3);          // '53' ([5] → '5', '5' + 3 → '53')
console.log([5] - 3);          // 2 ([5] → '5' → 5, 5 - 3 → 2)

// Empty array
console.log([] + '');          // '' (empty string)
console.log(+[]);              // 0

Common Coercion Patterns

1. Convert to Number

// Unary + (quickest)
const num = +'42';  // 42

// Number() (explicit)
const num = Number('42');  // 42

// parseInt/parseFloat (lenient)
const num = parseInt('42px');  // 42

2. Convert to String

// String() (explicit)
const str = String(42);  // '42'

// .toString()
const str = (42).toString();  // '42'

// Template literal
const str = `${42}`;  // '42'

// Concatenate with empty string
const str = 42 + '';  // '42' (implicit)

3. Convert to Boolean

// Boolean() (explicit)
const bool = Boolean(1);  // true

// !! (double NOT - common)
const bool = !!1;  // true

// In conditions (implicit)
if (value) {  // value is coerced to boolean
  // ...
}

4. Default Values

// || for any falsy value
const name = userName || 'Guest';

// ?? for only null/undefined
const timeout = userTimeout ?? 5000;

// Ternary for explicit check
const display = count === 0 ? 'None' : count;

Best Practices

DO:

// Use explicit conversion
const num = Number(userInput);
const str = String(value);
const bool = Boolean(condition);

// Use === instead of ==
if (value === 5) {
  // ...
}

// Use ?? for default values when 0/'' are valid
const timeout = userTimeout ?? 5000;

// Be explicit in conditions
if (array.length > 0) {  // Better than if (array.length)
  // ...
}

// Convert before comparison
if (Number(userInput) === 42) {
  // ...
}

DON’T:

// Don't rely on implicit coercion
const result = '5' - 3;  // Confusing

// Don't use ==
if (value == 5) {  // Unpredictable
  // ...
}

// Don't use || when 0/'' are valid values
const count = userCount || 10;  // Bug if userCount is 0

// Don't chain + with mixed types
const result = 5 + '3' + 2;  // '532' - confusing!

// Don't trust truthiness for numbers
if (value) {  // Bug: value could be 0
  // Use: if (value !== undefined)
}

Summary

Coercion Types:

  • String - + with string converts to string
  • Number - -, *, /, %, unary + convert to number
  • Boolean - if, while, !, &&, || convert to boolean

Falsy Values (6 total):

  1. false
  2. 0
  3. '' (empty string)
  4. null
  5. undefined
  6. NaN

Best Practices:

  • Use === instead of ==
  • Use explicit conversion: Number(), String(), Boolean()
  • Use ?? for defaults when 0/'' are valid
  • Use || for defaults when only truthy values matter

Quick Conversions:

// To number
+value          // Unary plus (quick)
Number(value)   // Explicit

// To string
String(value)   // Explicit
`${value}`      // Template literal

// To boolean
!!value         // Double NOT (quick)
Boolean(value)  // Explicit