Type Conversion & Coercion
A form on a train booking site gives you the number of passengers as the text "3", not the number 3. Before you can multiply it by the fare, the value has to change type. Sometimes you do that yourself; sometimes JavaScript does it quietly behind your back, and that is where most strange bugs come from.
In this lesson you will learn the rules JavaScript follows when it converts values, so you can predict results like "5" + 2 and [] == false instead of guessing. It builds on Data Types and Operators.
Explicit conversion and implicit coercion
There are two ways a value can change type:
- Explicit conversion (also called type casting): you ask for it, for example
Number("3"). - Implicit coercion: JavaScript converts a value on its own because an operator needs a different type, for example
"6" / 2.
Both follow the same rules, but explicit conversion is visible to the reader while coercion is hidden. Good code converts explicitly where data comes in (forms, URLs, files) and then works with values of the right type. There are only three target types: string, number and boolean.
Converting to a string
String(value) works for every value, including null and undefined. A template literal does the same conversion for each ${} part.
const price = 1299;
const seats = ["A1", "A2"];
console.log(String(price));
console.log(`₹${price}`);
console.log(String(null), String(undefined));
console.log(String(true));
console.log(String(seats));
console.log(String({ city: "Pune" }));
console.log(price.toString(2));1299 ₹1299 null undefined true A1,A2 [object Object] 10100010011
Arrays become their items joined with commas. A plain object becomes the unhelpful "[object Object]", which is why you use JSON.stringify() when you want to see an object's contents (see JSON).
The .toString() method gives the same results and can take a base for numbers (toString(2) gives binary). But it fails on null and undefined with a TypeError, because they have no methods. When a value might be missing, use String(value).
Converting to a number
Number(value) is strict: the whole string must look like a number. Spaces at the start and end are ignored.
| Value | Number(value) | Why |
|---|---|---|
"42", " 42 " | 42 | Spaces around the number are trimmed |
"4.5", "1e3", "0x1F" | 4.5, 1000, 31 | Decimals, exponents and hex are understood |
"" | 0 | An empty string counts as zero (a trap) |
"42px", "1,299" | NaN | Extra characters, including commas |
true, false | 1, 0 | Booleans become 1 and 0 |
null | 0 | "Nothing" becomes zero |
undefined | NaN | "Not set" cannot be a number |
[], [7], [1, 2] | 0, 7, NaN | The array is turned into a string first |
The last row looks odd, but it follows a rule you will meet below: an object is first turned into a primitive. [7] becomes "7", which becomes 7.
parseInt and parseFloat
parseInt() and parseFloat() work differently. They read characters from the start of a string and stop at the first one they do not understand.
console.log(parseInt("15 kg"), Number("15 kg"));
console.log(parseInt("7.99"), parseFloat("7.99"));
console.log(parseInt(""), Number(""));
console.log(parseInt("1,299"));
console.log(parseInt("ff", 16), parseInt("101", 2));
console.log(parseInt(0.0000005));
console.log(["10", "10", "10"].map(parseInt));
console.log(["10", "10", "10"].map(Number));15 NaN 7 7.99 NaN 0 1 255 5 5 [ 10, NaN, 2 ] [ 10, 10, 10 ]
Each line hides a lesson:
parseIntis forgiving:"15 kg"gives15, and"1,299"gives1because it stops at the comma. Useful for"15px", dangerous for prices.- An empty string gives
NaNwithparseIntbut0withNumber. - The second argument is the radix (number base). Always pass
10:parseInt(text, 10). parseInt(0.0000005)is5because the number is first written as the string"5e-7". To round numbers, useMath.trunc(),Math.round()orMath.floor().map(parseInt)fails becausemappasses the index as the second argument, so the radix becomes 0, 1 and 2. PassNumber, or writemap((s) => parseInt(s, 10)).
Converting to a boolean
Boolean(value) turns any value into true or false. An if, while, !, && and || use the same rule automatically. Only eight values are falsy; everything else is truthy:
Falsy (become false) | Truthy values that surprise people |
|---|---|
false, 0, -0, 0n, NaN | "0", "false", " " (a space) |
"", null, undefined | [], {}, -1, Infinity |
More examples are in Booleans & if...else.
Where JavaScript coerces for you
Each operator wants certain types and converts its operands to get them:
| Operator or place | Converts to |
|---|---|
+ when either side is a string | string (joins text) |
+ otherwise, and -, *, /, %, ** | number |
<, >, <=, >= | number, unless both sides are strings |
if, while, !, &&, ||, ? : | boolean |
Template literal ${} | string |
The + operator is the one that bites, because it does two jobs.
console.log("5" + 2);
console.log("5" - 2);
console.log("5" * "2");
console.log(5 + 2 + "1");
console.log("1" + 5 + 2);
console.log(true + 1);
console.log(null + 1, undefined + 1);
console.log([1, 2] + [3]);52 3 10 71 152 2 1 NaN 1,23
+ works from left to right. In 5 + 2 + "1", the first + adds two numbers (7), then the second joins 7 and "1" into "71". In "1" + 5 + 2, the string comes first, so everything after it is joined as text: "152". The arrays are turned into the strings "1,2" and "3" and joined.
Loose equality in detail
The loose equality operator == coerces before it compares. Its rules, simplified:
- If both values have the same type, it behaves like
===. nullandundefinedare equal to each other and to nothing else.- A boolean is turned into a number first (
truebecomes1,falsebecomes0). - When a string meets a number, the string becomes a number.
- When an object meets a primitive, the object becomes a primitive.
console.log("" == 0);
console.log("0" == false);
console.log(" " == 0);
console.log([] == false);
console.log(null == undefined);
console.log(null == 0);
console.log(null >= 0);
console.log(NaN == NaN);
console.log(Object.is(NaN, NaN));true true true true true false true false true
Follow [] == false: the boolean becomes 0, the array becomes "", the string becomes 0, and 0 == 0 is true, even though [] is truthy in an if. And null == 0 is false while null >= 0 is true, because >= converts null to 0 but == uses rule 2.
NaN is never equal to anything, even itself. Object.is() is like ===, except that Object.is(NaN, NaN) is true and Object.is(0, -0) is false.
So use === and !==. Some teams allow one exception, value == null, as a short test for "null or undefined"; the ?? operator from Nullish Coalescing & Optional Chaining often makes even that unnecessary.
Objects to primitives
When an operator needs a primitive and gets an object, JavaScript calls the object's valueOf() and toString() methods to get one. For numbers it tries valueOf() first; for strings it tries toString() first. You can provide these methods yourself.
const ticket = {
route: "Jaipur to Delhi",
fare: 545,
valueOf() {
return this.fare;
},
toString() {
return `Ticket: ${this.route}`;
},
};
console.log(ticket + 100);
console.log(ticket > 500);
console.log(`${ticket}`);
const plain = { fare: 545 };
console.log(plain + 100);
console.log(Number(plain));645 true Ticket: Jaipur to Delhi [object Object]100 NaN
A plain object has no useful valueOf(), so it falls back to "[object Object]". Dates are a well-known example of objects with both methods: date1 - date2 gives milliseconds because subtraction asks for numbers. For full control there is a special method named Symbol.toPrimitive, which you will meet in Symbols.
Old shortcuts you will still see
Before Number() and friends were common style, developers used operators as short conversion tricks. You will meet them in older code, in minified files and in interview questions:
| Shortcut | Means | Clear modern version |
|---|---|---|
+text | to number | Number(text) |
"" + value | to string | String(value) |
!!value | to boolean | Boolean(value) |
~~x or x | 0 | drop the decimals | Math.trunc(x) |
parseInt(text) | to whole number | parseInt(text, 10) or Number(text) |
const qty = "3";
console.log(+qty + 1);
console.log(!!"Rohan", !!0);
console.log(~~"7.9", "7.9" | 0);
console.log(3000000000 | 0);
console.log(Math.trunc(3000000000.7));4 true false 7 7 -1294967296 3000000000
The unary + is still common and fine once you know it. The bitwise tricks are worse: they only work for numbers up to about 2.1 billion, and give nonsense above that, as the fourth line shows. Prefer the named functions, which say what you mean.
Common mistakes
- Adding form values without converting.
price + taxwith two strings joins them. Convert withNumber()as soon as you read the input. - Treating an empty field as zero.
Number("")is0, so a blank quantity looks valid. Check for""first. - Calling
parseIntwithout a radix or on numbers. WriteparseInt(text, 10), and useMath.trunc()for numbers. - Using
==to compare. It hides type bugs. Convert, then compare with===. - Calling
.toString()on something that might benull. UseString(value)instead.
Key points
- Explicit conversion is written by you; implicit coercion happens when an operator needs another type.
String(),Number()andBoolean()convert any value and never throw for normal values.Number()needs the whole string to be numeric;parseInt()andparseFloat()stop at the first bad character.+joins text if either side is a string;-,*and/always convert to numbers.==follows coercion rules that produce surprises, so use===and convert first.- Objects become primitives through
valueOf()andtoString().
Exercise
Write a function toQuantity(input) for a shop form in Kochi. It receives the raw text from an input box. It should trim the text, return null if it is empty, convert it with Number(), and return null again if the result is not a whole number greater than 0. Test it with "3", " 12 ", "", "2.5", "abc" and "0".
Show solution
function toQuantity(input) {
const text = String(input).trim();
if (text === "") return null;
const qty = Number(text);
if (!Number.isInteger(qty) || qty <= 0) return null;
return qty;
}
for (const raw of ["3", " 12 ", "", "2.5", "abc", "0"]) {
console.log(JSON.stringify(raw), toQuantity(raw));
}"3" 3 " 12 " 12 "" null "2.5" null "abc" null "0" null
The empty check runs before Number(), because Number("") would be 0. Number.isInteger() returns false for NaN and decimals, so one check rejects both.
Quick Quiz
1. What does "4" + 2 + 1 produce?
The string comes first, so each + joins text from left to right.
2. Which call returns 0 instead of NaN?
An empty string converts to 0 with Number(), which is why blank form fields need a separate check.
3. Why does ["1", "2", "3"].map(parseInt) give [1, NaN, NaN]?
The calls are parseInt("1", 0), parseInt("2", 1) and parseInt("3", 2); radix 1 is invalid and "3" is not a binary digit.
4. What is the clearest way to turn the text qty into a number?
Number() states the intent plainly; the operator tricks work but are harder to read and some break for large numbers.
Learn JavaScript with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
