JavaScript Type Coercion
Learn JavaScript type coercion, why "5" + 3 gives "53" but "5" - 3 gives 2, how the Abstract Equality algorithm works, implicit vs explicit conversion rules, and how to write coercion-safe code.
If you’ve been following this JavaScript series with us, you’ve covered a lot of ground. Data types, strings, numbers, and just recently, booleans, null, and undefined with all their falsy quirks. Those articles were mostly about understanding what each type is. This one is about what happens when those types collide.
And trust me, they collide in some really unexpected ways.
Open your browser console right now and type this:
console.log("5" + 3);
console.log("5" - 3);
The first line gives you "53". The second gives you 2. Same string, different operator, completely different behavior. This is JavaScript type coercion, and it follows real, predictable rules once you understand them.
By the end of this article, none of it will feel like magic anymore. You’ll understand exactly why these things happen, how to predict them, and how to make sure they never cause a bug in your code.
01What Type Coercion in JavaScript Actually Is
Let’s start simple. JavaScript is a dynamically typed language. That means you don’t declare the type of a variable when you create it, and JavaScript doesn’t stop you from using a number where it expects a string, or a string where it expects a boolean.
Instead, when a mismatch happens, JavaScript quietly converts one type into another so it can proceed. That automatic conversion is called type coercion.
Here’s a simple example of it happening invisibly:
const result = "10" - 5;
console.log(result); // 2... wait, 5?
console.log(typeof result); // "number"
You put a string in, got a number out. You didn’t ask for that conversion. JavaScript just did it on its own. That’s coercion.
Now, coercion isn’t inherently bad. It can be convenient. The problem is when it happens unexpectedly, and you end up with a bug that takes an hour to track down. This article exists so that never happens to you.
02Implicit vs Explicit Type Conversion: Two Completely Different Things
Before we go deeper, let’s separate two terms that often get used interchangeably but mean very different things.
Explicit Type Conversion: You’re in Control
Explicit conversion is when you, the developer, intentionally convert a value from one type to another using a built-in function. You know what’s happening, you meant for it to happen.
// Explicit conversions using built-in functions
console.log(Number("42")); // 42
console.log(Number("3.14")); // 3.14
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
console.log(Number("hello")); // NaN
console.log(String(42)); // "42"
console.log(String(true)); // "true"
console.log(String(null)); // "null"
console.log(String(undefined)); // "undefined"
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean("hello")); // true
console.log(Boolean(42)); // true
console.log(Boolean(null)); // false
console.log(Boolean([])); // true (yes, really)
You can also use parseInt() and parseFloat() for converting strings to numbers, which we’ll come back to later.
Implicit Type Conversion: JavaScript Decides for You
Implicit conversion, or coercion, is when JavaScript automatically converts a type during an operation, without you asking. This happens with operators, comparisons, and conditionals.
// Implicit conversions - JavaScript doing it on its own
"5" * 2 // JavaScript converts "5" to 5 → 10
true + 1 // JavaScript converts true to 1 → 2
if ("hello") // JavaScript converts "hello" to true → runs the block
"3" > 2 // JavaScript converts "3" to 3 → true
The first kind, you’re in control. The second kind is where bugs are born, if you don’t understand the rules.
Explicit conversion is sometimes called type casting in other languages. In JavaScript, the terms are used loosely. What matters is the intent: did you choose the conversion, or did JavaScript choose it for you?
03The Addition Operator: Why "5" + 3 Gives "53", Not 8
This is the one that trips everyone up first. Let’s understand it properly.
The + operator in JavaScript does two completely different things:
- When used with two numbers, it adds them:
5 + 3 = 8 - When used with strings, it concatenates them:
"hello" + " world" = "hello world"
The rule is: if either operand is a string, the other one gets converted to a string, and they get joined together.
So when you write "5" + 3, JavaScript sees a string on the left side. It converts 3 to "3", and then concatenates: "5" + "3" = "53". That’s it. Not a bug, not a quirk. That’s the rule.
console.log("5" + 3); // "53" — string wins
console.log(3 + "5"); // "35" — string wins (order doesn't change the rule)
console.log("5" + 3 + 2); // "532" — left to right, "5"+3="53", "53"+2="532"
console.log(5 + 3 + "2"); // "82" — 5+3=8 first, then 8+"2"="82"
console.log(typeof ("5" + 3)); // "string"
// With other types and +:
console.log(true + " story"); // "true story" — boolean to string
console.log(null + " value"); // "null value" — null to string
console.log(1 + 2 + "3"); // "33" — 1+2=3 first, then 3+"3"="33"
The order of operations matters here. JavaScript evaluates left to right, so 1 + 2 + "3" becomes 3 + "3" which becomes "33". But "1" + 2 + 3 becomes "12" + 3 which becomes "123". Same numbers, different results depending on position.
This is one of the most common real-world bugs in JavaScript. When you read user input from a form or URL, it always comes in as a string. Adding numbers to it without explicit conversion gives you string concatenation, not math.
04Why "5" - 3 Gives 2: The Operators That Always Want Numbers
Here’s where it gets interesting. Subtraction, multiplication, division, and exponentiation don’t have a “string version.” They only make sense for numbers.
So when JavaScript encounters "5" - 3, it has no choice but to convert "5" to the number 5, then subtract. The result is 2.
// These operators always convert to numbers
console.log("5" - 3); // 2 — "5" becomes 5
console.log("5" * 3); // 15 — "5" becomes 5
console.log("10" / 2); // 5 — "10" becomes 10
console.log("5" ** 2); // 25 — "5" becomes 5
// What about non-numeric strings?
console.log("abc" - 3); // NaN — "abc" can't become a number
console.log("" - 3); // -3 — empty string becomes 0
// Other types converted to numbers:
console.log(true - 1); // 0 — true becomes 1
console.log(false - 1); // -1 — false becomes 0
console.log(null - 1); // -1 — null becomes 0
console.log(undefined - 1); // NaN — undefined becomes NaN
That’s the full picture for operators: + checks for strings first and prefers concatenation, everything else converts to numbers and does math.
Operator Behavior at a Glance
| Expression | Result | Type | Why |
|---|---|---|---|
| “5” + 3 | “53” | string | + sees a string, converts 3 to “3”, concatenates |
| “5” – 3 | 2 | number | – only does math, converts “5” to 5 |
| “5” * 2 | 10 | number | * converts “5” to 5 |
| true + 1 | 2 | number | true converts to 1 |
| false + 1 | 1 | number | false converts to 0 |
| null + 1 | 1 | number | null converts to 0 |
| undefined + 1 | NaN | NaN | undefined converts to NaN, NaN + 1 = NaN |
| “abc” – 3 | NaN | NaN | “abc” can’t become a number → NaN |
| true + “1” | “true1” | string | + sees a string, converts true to “true” |
| 1 + 2 + “3” | “33” | string | 1+2=3 first, then 3+”3″=”33″ |
05JavaScript’s Internal Conversion Rules: ToNumber, ToString, ToBoolean
When JavaScript needs to convert a value, it follows specific internal algorithms. They’re described in the ECMAScript specification, but they translate into very concrete, learnable rules. Let’s go through each one.
ToNumber: How JavaScript Converts Values to a Number
This conversion happens when you use arithmetic operators (except + with strings), comparison operators with numeric values, or call Number() explicitly.
// The ToNumber rules:
Number("") // 0 — empty string is 0
Number(" ") // 0 — whitespace-only string is 0
Number("42") // 42 — valid number string
Number("3.14") // 3.14
Number("0x1A") // 26 — hexadecimal works
Number("42abc") // NaN — mixed content fails
Number("abc") // NaN
Number(true) // 1
Number(false) // 0
Number(null) // 0
Number(undefined) // NaN — undefined is NOT 0!
Number([]) // 0 — empty array becomes "" then 0
Number([5]) // 5 — single-element array
Number([1,2]) // NaN — multiple elements
The biggest thing to remember: null converts to 0, but undefined converts to NaN. They’re both “empty” in a sense, but they behave completely differently when numbers get involved. We talked about this distinction in the booleans, null, and undefined article, and now you can see the practical impact.
ToString: How JavaScript Converts Values to a String
This conversion happens when + encounters a string operand, or when you call String().
String(42) // "42"
String(3.14) // "3.14"
String(0) // "0"
String(-0) // "0" — negative zero becomes "0", not "-0"
String(true) // "true"
String(false) // "false"
String(null) // "null"
String(undefined) // "undefined"
String(NaN) // "NaN"
String(Infinity) // "Infinity"
String([1,2,3]) // "1,2,3"
String({}) // "[object Object]"
String conversion is generally straightforward. Most values just become their string representation. The ones worth noting: arrays become comma-separated strings, and objects become "[object Object]" (which is why you’ll sometimes see that confusing output in the console).
ToBoolean: How JavaScript Converts Values to a Boolean
This one you’ve actually already seen. In our last article, we went deep on falsy values in JavaScript. This is the same thing.
The rule is simple: there are exactly six falsy values in JavaScript. Everything else is truthy.
// The six falsy values:
Boolean(false) // false
Boolean(0) // false (also: -0 and 0n)
Boolean("") // false (empty string)
Boolean(null) // false
Boolean(undefined) // false
Boolean(NaN) // false
// Everything else is truthy:
Boolean("0") // true — a string with content, even "0"
Boolean([]) // true — empty array is truthy!
Boolean({}) // true — empty object is truthy!
Boolean(" ") // true — a space is truthy
Boolean(-1) // true — any non-zero number
Boolean("0") is true, not false. The string "0" is a non-empty string, so it’s truthy. If you want to check for zero, check Number("0") === 0, not if ("0").
Live Coercion Explorer
Type a JavaScript value below and see how it converts to Number, String, and Boolean.
Try: 0, "42", null, undefined, true, "", "hello", NaN
5
typeof: number
“5”
typeof: string
true
typeof: boolean
06The Abstract Equality Algorithm: How == Actually Works
Alright, this is the section that a lot of developers just avoid by memorizing “always use ===” without understanding why. That’s honestly fine advice, and we’ll get to it. But understanding why == behaves the way it does is genuinely useful, because then you’re not just following a rule blindly, you actually understand the language.
When you use ==, JavaScript runs what the specification calls the Abstract Equality Comparison algorithm. Here’s what it checks, in order:
- If both values are the same type, compare them directly (like
===would) - If one is
nulland the other isundefined, returntrue - If one is a number and the other is a string, convert the string to a number and compare
- If one is a boolean, convert it to a number first (
trueto1,falseto0) - If one is an object and the other is a primitive, convert the object to a primitive first
- Otherwise, return
false
That sounds complex, but it produces some very concrete, predictable results:
// Same types — behaves like ===
console.log(5 == 5); // true
console.log("hi" == "hi"); // true
// null and undefined are only equal to each other
console.log(null == undefined); // true ← the special case
console.log(null == false); // false ← NOT truthy/falsy logic
console.log(null == 0); // false ← null doesn't coerce to 0 here
console.log(undefined == 0); // false
// Number vs String — string converts to number
console.log(1 == "1"); // true → 1 == 1
console.log(0 == "0"); // true → 0 == 0
console.log(0 == ""); // true → "" becomes 0
console.log(0 == " "); // true → " " becomes 0
// Boolean converts to number first
console.log(true == 1); // true → 1 == 1
console.log(false == 0); // true → 0 == 0
console.log(true == "1"); // true → true→1, "1"→1, 1==1
console.log(false == ""); // true → false→0, ""→0, 0==0
console.log(true == "true"); // false → true→1, "true"→NaN, 1==NaN is false
Notice that last one. true == "true" is false. That surprises people because intuitively, both feel “true.” But the algorithm converts true to the number 1, then tries to convert "true" to a number, gets NaN, and 1 == NaN is always false.
Also notice: null == false is false. Even though null is falsy. The rule is very specific: null only equals undefined when using ==, nothing else. The algorithm has a dedicated step for that case before it ever gets to any boolean conversion logic.
The == Comparison Results You Need to Know
Purple border = results that tend to surprise developers
true
false
Surprise
false
Surprise
true
true
true
true
true
true
Surprise
false
Surprise
false
true
Surprise
true
Surprise
false
That last set, [] == false and [] == 0 returning true, is a result of object-to-primitive conversion. An empty array converts to the empty string "", which then converts to 0. It’s consistent, but it’s exactly the kind of thing that makes people uncomfortable relying on ==.
07Loose Equality vs Strict Equality: When to Use Which
Strict equality (===) never performs type coercion. If the two values have different types, it immediately returns false. No conversion, no algorithm, just a direct comparison.
// === never coerces — different types always return false
console.log(1 === "1"); // false — number vs string
console.log(0 === false); // false — number vs boolean
console.log("" === false); // false — string vs boolean
console.log(null === undefined); // false — different types
console.log(1 === 1); // true — same type, same value
console.log("hi" === "hi"); // true — same type, same value
The general guidance in the JavaScript community is: use === by default. It’s predictable, it’s explicit, and it has no hidden coercion logic.
The one situation where == is commonly considered acceptable is checking for both null and undefined in a single check:
// Checking for null or undefined — the acceptable == use case
function greet(name) {
if (name == null) {
// This catches BOTH null AND undefined
console.log("Name is missing");
return;
}
console.log("Hello, " + name);
}
greet(null); // "Name is missing"
greet(undefined); // "Name is missing"
greet("Daniyal"); // "Hello, Daniyal"
// With ===, you'd need to write it like this:
if (name === null || name === undefined) { ... }
Using name == null is a known, intentional pattern that experienced developers recognize. It’s one of the few places where == earns its keep. But it’s the exception, not the rule. In all other cases, reach for ===.
08Real-World JavaScript Coercion Bugs You’ll Actually Encounter
Enough theory. Let’s talk about the bugs that actually show up in real codebases.
Bug #1: Form Input Is Always a String
This is probably the most common one. Every value you read from an HTML input is a string, even if the user typed a number. If you forget to convert it, addition becomes concatenation.
// Simulating what you get from an input field
const quantity = "5"; // value from an
const price = 10;
// The bug:
const total = quantity + price;
console.log(total); // "510" — NOT 50!
// The fix:
const totalFixed = Number(quantity) * price;
console.log(totalFixed); // 50
// Or using parseInt for whole numbers:
const totalParsed = parseInt(quantity, 10) * price;
console.log(totalParsed); // 50
Bug #2: Loose Equality in Conditional Logic
When you use == to check a value, you might accidentally catch things you didn’t intend to.
// Bug: unintended coercion in conditions
const score = 0;
if (score == false) {
console.log("No score!"); // This runs! 0 == false is true
}
// You probably meant: is score exactly false?
if (score === false) {
console.log("This won't run"); // Correct
}
// Or maybe: is score falsy?
if (!score) {
console.log("Score is falsy"); // Runs, but intentional this time
}
Bug #3: Sorting Numbers Stored as Strings
This one is sneaky. JavaScript’s default Array.sort() converts everything to strings and sorts alphabetically. If your numbers are already strings, or you forget to provide a comparator, you get wrong results.
// Bug: sorting without a comparator
const scores = [10, 9, 100, 2, 21];
console.log(scores.sort());
// [10, 100, 2, 21, 9] — sorted as strings!
// Fix: provide a numeric comparator
console.log(scores.sort((a, b) => a - b));
// [2, 9, 10, 21, 100] — correct numeric sort
Bug #4: parseInt Without a Radix
When you call parseInt(), always pass the second argument (the radix, which is 10 for decimal). Without it, older engines can interpret strings starting with 0 as octal (base 8), and that creates subtle, hard-to-find bugs.
// Always pass 10 as the radix:
parseInt("08", 10); // 8 (safe)
parseInt("09", 10); // 9 (safe)
// Without it (in older environments):
parseInt("08"); // Could be 0 in legacy environments
parseInt("09"); // Could be 0 in legacy environments
// Number() is cleaner for simple conversions:
Number("08"); // 8 — always decimal, no radix needed
09Writing Coercion-Safe JavaScript: The Practical Rules
Now that you understand where coercion comes from and where it bites, here are the habits that will keep your code clean.
const qty = form.qty.value;
const total = qty + 10;
// “510” if qty is “5”!
// Loose comparison
if (score == 0) { … }
// Also catches false, “”
// Checking type
if (value == null) {
// OK for null/undefined
// but easy to misuse elsewhere
}
const qty = Number(form.qty.value);
const total = qty + 10;
// 15 — numeric addition
// Strict comparison
if (score ===0) { … }
// Only matches exactly 0
// Explicit null check
if (value ===null ||
value === undefined) {
// Clear intent, no surprises
}
Here are the rules to code by:
1. Always use === and !== for comparisons. There’s very little reason to use ==. The null/undefined check is the one accepted exception, and even then, some teams prefer explicit === null || === undefined for clarity.
2. Convert input values explicitly before using them in math. Use Number(), parseInt(str, 10), or parseFloat() as appropriate. Always validate with isNaN() after conversion if the value came from user input.
// Good pattern for user input:
function parseQuantity(input) {
const parsed = Number(input);
if (isNaN(parsed) || parsed < 0) {
throw new Error("Invalid quantity: " + input);
}
return parsed;
}
console.log(parseQuantity("5")); // 5
console.log(parseQuantity(" 3 ")); // 3 — Number() trims whitespace
// parseQuantity("abc"); // throws error — handled properly
3. Be careful with the + operator in mixed contexts. If you're doing string building, use template literals. If you're doing math, make sure your values are already numbers.
// Use template literals for string building — no coercion surprises
const name = "Daniyal";
const score = 95;
const message = `${name} scored ${score} points`; // Clean, explicit
// Use arithmetic operators only when values are already numbers
const a = Number(input1);
const b = Number(input2);
const sum = a + b; // Safe — both are already numbers
4. Use typeof and instanceof to guard functions. If a function expects a number, check it at the top. Don't rely on coercion to save you.
function double(n) {
if (typeof n !== "number" || isNaN(n)) {
throw new TypeError("Expected a number, got: " + typeof n);
}
return n * 2;
}
console.log(double(5)); // 10
// double("5"); // throws TypeError — caught early
// double(undefined); // throws TypeError — caught early
10Quick Reference: JavaScript Type Coercion Rules at a Glance
Conversion Cheat Sheet
11parseInt vs Number vs Unary +: Which One Should You Use?
Now that you know about explicit conversion, you might be wondering: when should I use Number() vs parseInt() vs the unary + operator? They're all ways to convert something to a number, but they behave differently.
// Number() — strict, whole string must be a valid number
Number("42"); // 42
Number("42px"); // NaN — partial match fails
Number(""); // 0
Number(null); // 0
Number(undefined); // NaN
// parseInt(str, radix) — stops at first non-numeric character
parseInt("42px", 10); // 42 — takes the leading number
parseInt("px42", 10); // NaN — no leading number
parseInt("3.9", 10); // 3 — truncates the decimal
// parseFloat(str) — like parseInt but keeps the decimal
parseFloat("3.14px"); // 3.14 — takes leading float
parseFloat("3.14"); // 3.14
// Unary + — same as Number(), but shorter to write
+"42"; // 42
+"42px"; // NaN
+""; // 0
+null; // 0
+undefined; // NaN
The guideline: use Number() when you want strict conversion (the whole value must be a valid number). Use parseInt(str, 10) when you're reading values like "42px" from CSS or user input and just want the number part. Use parseFloat() for decimal values in that same context. The unary + is a shorthand for Number() and works fine, but some teams avoid it for readability since it's easy to miss in code.
12What's Coming Next in the Series
Now that you know how JavaScript converts types, you're ready for something that builds directly on all of this: operators.
In the next article, we'll cover all the arithmetic operators, comparison operators (where strict equality lives), logical operators like && and ||, and some modern ones like the nullish coalescing operator ?? and optional chaining ?.. These all interact with coercion in specific ways, and now that you understand the coercion rules, understanding operators becomes a lot cleaner.
Type coercion is JavaScript converting one type to another. Implicit coercion happens automatically during operations. Explicit coercion happens when you call
Number(), String(), or Boolean(). The + operator prefers strings (concatenation) if either side is a string. All other arithmetic operators convert to numbers. The == operator runs the Abstract Equality algorithm and may coerce types before comparing. Use === by default to avoid unexpected results.
Type coercion was the last "silent" behavior in JavaScript's basics to understand. Variables, data types, strings, numbers, booleans, and now coercion: you've got the full picture of how JavaScript handles data. That's not a small thing. Most confusion that beginners have with JavaScript traces directly back to one of those topics, and you've covered all of them.
Keep going. The next article is where that understanding starts turning into real code.
JavaScript Type Coercion
Loading challenge…
Ready to Practice?
Select a difficulty level above to load your challenge.