Skip to main content

πŸ”„ Type Coercion and Conversion

JavaScript is famous β€” and occasionally infamous β€” for automatically converting values from one type to another. This lesson demystifies both the conversions you ask for and the ones JavaScript performs behind your back, so its surprises become predictable rules you control.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Distinguish explicit conversion from implicit coercion
  • Convert deliberately with String(), Number(), Boolean(), parseInt(), and parseFloat()
  • Predict the result of the + operator and the numeric operators on mixed types
  • Explain why === is safer than == and use each appropriately
  • Apply best practices that prevent an entire class of coercion bugs

Estimated Time: 30–40 minutes  β€’  Difficulty: Beginner–Intermediate

Hands-on: Hunt down and fix the coercion bugs hiding in a shopping-cart calculator.

In This Lesson

Two Kinds of Conversion

Changing a value's type happens in two ways:

  • Explicit conversion (type casting) β€” you deliberately convert with a function like Number() or String().
  • Implicit coercion β€” JavaScript converts automatically during an operation, sometimes with surprising results.
πŸ’‘ A useful analogy: Explicit conversion is like hiring a professional translator β€” you request the translation and control it. Implicit coercion is like a local who switches to English the moment they sense you're struggling: convenient, but the meaning can drift in ways you didn't intend.
graph TD A[Type conversion] --> B[Explicit β€” you convert] A --> C[Implicit β€” JS coerces] B --> D["String() Β· Number() Β· Boolean()"] B --> E["parseInt() Β· parseFloat()"] C --> F["+ with a string"] C --> G["- * / % comparisons"] C --> H["if / while / && / ||"]

Understanding both is essential: the explicit tools are how you write robust code, and knowing the implicit rules is how you read code others wrote β€” and debug the strange output it sometimes produces.

Explicit Conversion

These are the conversions you control. Prefer them whenever a value's type matters β€” for example, right after reading form input, which always arrives as strings.

To string

console.log(String(42));        // "42"
console.log(String(true));      // "true"
console.log(String(null));      // "null"
console.log(String([1, 2, 3])); // "1,2,3"
console.log((255).toString(16));// "ff"  (hexadecimal via radix)
console.log(`${42}`);           // "42"  (template literal)

To number

console.log(Number('42'));      // 42
console.log(Number('42.5'));    // 42.5
console.log(Number(''));        // 0     (empty string β†’ 0)
console.log(Number('42px'));    // NaN   (Number() is all-or-nothing)
console.log(Number(true));      // 1
console.log(Number(null));      // 0
console.log(Number(undefined)); // NaN

// parseInt / parseFloat read as far as they can, then stop
console.log(parseInt('42px', 10)); // 42   (always pass the radix!)
console.log(parseFloat('3.14em')); // 3.14
console.log(+'42');                // 42   (unary plus β€” a terse Number())

⚠️ Number() vs parseInt()

Number('42px') is NaN because the whole string must be numeric. parseInt('42px', 10) is 42 because it parses leading digits and stops at the first non-digit. Choose based on whether trailing junk should be an error or ignored β€” and always pass the radix (10) to parseInt.

To boolean

console.log(Boolean(42));    // true
console.log(Boolean(0));     // false
console.log(Boolean('hi'));  // true
console.log(Boolean(''));    // false
console.log(!!'hi');         // true  (double-NOT β€” a terse Boolean())

Implicit Coercion

Implicit coercion happens automatically when operators meet mismatched types. The single most important rule to internalise concerns the + operator.

πŸ“– The golden rule of +

If either operand of + is a string, JavaScript converts the other to a string and concatenates. Every other arithmetic operator (-, *, /, %) converts both operands to numbers.

// + prefers strings
console.log('5' + 3);   // "53"   (3 β†’ "3", then concatenate)
console.log(5 + '3');   // "53"
console.log('5' + true);// "5true"

// Every other operator prefers numbers
console.log('5' - 3);   // 2      ("5" β†’ 5)
console.log('5' * 2);   // 10
console.log('10' / '2');// 5      (both strings β†’ numbers)
console.log('5' - '2'); // 3

This asymmetry is the source of the classic beginner surprise: adding what looks like two numbers gives a glued-together string because one of them was secretly text.

Coercion in conditions

Anywhere a boolean is expected β€” if, while, ? :, &&, || β€” the value is coerced using the truthy/falsy rules.

if ('hello') console.log('non-empty strings are truthy'); // runs
if (0) { /* skipped β€” 0 is falsy */ }

const name = userInput || 'Guest';   // fall back if userInput is falsy
const timeout = config.wait ?? 3000; // fall back ONLY on null/undefined
How operators coerce mixed types The plus operator with any string concatenates as text. The minus, times, divide, and modulo operators, plus comparisons, convert both sides to numbers. + with any string converts to STRING '5' + 3 β†’ "53" concatenation - * / % < > convert to NUMBER '5' - 3 β†’ 2 arithmetic
Figure 1 β€” Only + is the odd one out. When in doubt, convert explicitly and the ambiguity disappears.

== vs ===

JavaScript has two equality operators, and the difference is all about coercion.

  • == (loose equality) converts operands to a common type before comparing.
  • === (strict equality) compares type and value with no conversion.
// Loose == coerces, producing surprising truths:
console.log(5 == '5');          // true  (string β†’ number)
console.log(1 == true);         // true  (true β†’ 1)
console.log(0 == false);        // true  (false β†’ 0)
console.log(null == undefined); // true  (special-cased)
console.log('' == 0);           // true  ('' β†’ 0)

// Strict === never coerces:
console.log(5 === '5');         // false (number vs string)
console.log(1 === true);        // false
console.log(null === undefined);// false
flowchart TD A["x == y"] --> B{Same type?} B -->|Yes| C["compare like ==="] B -->|No| D{null and undefined?} D -->|Yes| E[return true] D -->|No| F{number vs string?} F -->|Yes| G[convert string to number] F -->|No| H{one is boolean?} H -->|Yes| I[convert boolean to number] H -->|No| J[compare object to primitive]

βœ… The rule that removes the guesswork

Use === and !== by default β€” always. The only common, deliberate use of == is value == null, a compact way to test for "null or undefined" at once. Everything else should be strict.

Famous Gotchas

A short tour of the coercion results that trip everyone up at least once. Knowing them turns "JavaScript is broken" into "ah, that's the rule."

ExpressionResultWhy
'5' + 3"53"+ with a string concatenates
'5' - 32- forces numbers
[] + []""both arrays become empty strings
[] + {}"[object Object]"array β†’ "", object β†’ its tag
true + true2each true β†’ 1
'' == 0trueloose == coerces both to 0
NaN === NaNfalseNaN is never equal to anything
⚠️ The if (count) trap: checking a value for existence with a bare truthiness test fails when 0 or '' is a valid value. if (count) skips a legitimate 0. Use if (count != null) or an explicit count !== undefined && count !== null instead.
const count = 0;
if (count) {
  console.log('has a count'); // ❌ never runs β€” 0 is falsy
}
if (count != null) {
  console.log('count is', count); // βœ… runs β€” "count is 0"
}

Best Practices

You cannot turn coercion off, but you can write code that never relies on its surprises.

βœ… Do

  • Use === and !== by default.
  • Convert explicitly at the boundary β€” the moment data arrives from a form, URL, or API.
  • Guard numeric input with Number.isNaN() after converting.
  • Use ?? for defaults when 0 or '' should count as real values.

⚠️ Don't

  • Don't lean on + to "add" values that might be strings.
  • Don't use == for anything except the == null shortcut.
  • Don't test existence with bare truthiness when 0/''/false are valid.
  • Don't call parseInt without a radix.
// A robust conversion boundary for form input
function readAge(raw) {
  const age = Number(raw);          // explicit conversion
  if (Number.isNaN(age) || age < 0) {
    throw new Error('Age must be a non-negative number');
  }
  return age;
}

console.log(readAge('30')); // 30
// readAge('thirty');       // throws β€” caught early, not silently NaN later

Hands-on Exercise

πŸ‹οΈ Debug the Shopping Cart

Objective: Find and fix the coercion bugs so the total is correct and safe.

This calculator is meant to sum price Γ— quantity for each item and return a formatted total. Some prices arrive as strings from a form, and the code has three coercion bugs.

function cartTotal(items) {
  let total = 0;
  for (let i = 0; i < items.length; i++) {
    total = total + items[i].price;   // BUG: string prices concatenate
  }
  if (total == 100) applyDiscount();   // BUG: loose equality
  return '$' + total;                  // BUG: no formatting / string glue
}

const cart = [
  { name: 'Shirt', price: '25' },  // price is a string!
  { name: 'Hat',   price: 15 },
  { name: 'Socks', price: 10 }
];
console.log(cartTotal(cart)); // "$0251510"  😱  (expected "$50.00")

Your task

  1. Make the sum numeric even when a price is a string.
  2. Replace == with strict equality.
  3. Return the total formatted to two decimals.
πŸ’‘ Hint

Wrap each price in Number(...) before adding, guard against NaN with Number.isNaN, switch == to ===, and build the result with total.toFixed(2).

βœ… Solution
function cartTotal(items) {
  let total = 0;
  for (const item of items) {
    const price = Number(item.price);      // explicit conversion
    if (Number.isNaN(price)) {
      console.warn(`Skipping invalid price for ${item.name}`);
      continue;
    }
    total += price;                        // real numeric addition
  }
  if (total === 100) applyDiscount();      // strict equality
  return '$' + total.toFixed(2);           // formatted currency
}

console.log(cartTotal(cart)); // "$50.00" βœ…

The fix is the same pattern every time: convert explicitly at the point where mixed types meet. Once the values are guaranteed numbers, the operators behave exactly as you expect.

🎯 Quick Quiz

Question 1: What does '5' + 3 evaluate to?

Question 2: Which comparison is true?

Question 3: You want a default only when a value is null or undefined, but 0 must be kept. Which operator is right?

Summary & Quiz

πŸŽ‰ Key Takeaways

  • Explicit conversion is deliberate (Number(), String(), Boolean()); implicit coercion is automatic.
  • The + operator concatenates if either side is a string; every other arithmetic operator converts to numbers.
  • Prefer === and !==; reserve == for the == null shortcut.
  • Convert explicitly at the boundary where external data enters your program.
  • Watch the classic traps: if (0) is falsy, NaN === NaN is false, and '' == 0 is true.

πŸ“š Further Reading

πŸš€ What's Next?

With types and their conversions under control, you're ready to actually compute with them. Up next: the arithmetic and assignment operators β€” the tools that turn values into results.

πŸŽ‰ Nice work!

JavaScript's coercion no longer looks like magic β€” it's a small set of rules you now command.