π 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(), andparseFloat() - 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()orString(). - 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.
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
+ 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
β 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."
| Expression | Result | Why |
|---|---|---|
'5' + 3 | "53" | + with a string concatenates |
'5' - 3 | 2 | - forces numbers |
[] + [] | "" | both arrays become empty strings |
[] + {} | "[object Object]" | array β "", object β its tag |
true + true | 2 | each true β 1 |
'' == 0 | true | loose == coerces both to 0 |
NaN === NaN | false | NaN is never equal to anything |
β οΈ Theif (count)trap: checking a value for existence with a bare truthiness test fails when0or''is a valid value.if (count)skips a legitimate0. Useif (count != null)or an explicitcount !== undefined && count !== nullinstead.
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 when0or''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== nullshortcut. - Don't test existence with bare truthiness when
0/''/falseare valid. - Don't call
parseIntwithout 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
- Make the sum numeric even when a price is a string.
- Replace
==with strict equality. - 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== nullshortcut. - Convert explicitly at the boundary where external data enters your program.
- Watch the classic traps:
if (0)is falsy,NaN === NaNis false, and'' == 0is true.
π Further Reading
- MDN β Type coercion
- MDN β Strict equality (===)
- MDN β Equality comparisons and sameness
- You Don't Know JS β Types & Grammar
π 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.