Type Conversion & Coercion

JavaScript · Lesson 7 of 61 9 min read Updated

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.

JavaScriptRun it yourself
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));
Output
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.

ValueNumber(value)Why
"42", " 42 "42Spaces around the number are trimmed
"4.5", "1e3", "0x1F"4.5, 1000, 31Decimals, exponents and hex are understood
""0An empty string counts as zero (a trap)
"42px", "1,299"NaNExtra characters, including commas
true, false1, 0Booleans become 1 and 0
null0"Nothing" becomes zero
undefinedNaN"Not set" cannot be a number
[], [7], [1, 2]0, 7, NaNThe 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.

JavaScriptRun it yourself
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));
Output
15 NaN
7 7.99
NaN 0
1
255 5
5
[ 10, NaN, 2 ]
[ 10, 10, 10 ]

Each line hides a lesson:

  • parseInt is forgiving: "15 kg" gives 15, and "1,299" gives 1 because it stops at the comma. Useful for "15px", dangerous for prices.
  • An empty string gives NaN with parseInt but 0 with Number.
  • The second argument is the radix (number base). Always pass 10: parseInt(text, 10).
  • parseInt(0.0000005) is 5 because the number is first written as the string "5e-7". To round numbers, use Math.trunc(), Math.round() or Math.floor().
  • map(parseInt) fails because map passes the index as the second argument, so the radix becomes 0, 1 and 2. Pass Number, or write map((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 placeConverts to
+ when either side is a stringstring (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.

JavaScriptRun it yourself
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]);
Output
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:

  1. If both values have the same type, it behaves like ===.
  2. null and undefined are equal to each other and to nothing else.
  3. A boolean is turned into a number first (true becomes 1, false becomes 0).
  4. When a string meets a number, the string becomes a number.
  5. When an object meets a primitive, the object becomes a primitive.
JavaScriptRun it yourself
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));
Output
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.

JavaScriptRun it 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));
Output
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:

ShortcutMeansClear modern version
+textto numberNumber(text)
"" + valueto stringString(value)
!!valueto booleanBoolean(value)
~~x or x | 0drop the decimalsMath.trunc(x)
parseInt(text)to whole numberparseInt(text, 10) or Number(text)
JavaScriptRun it yourself
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));
Output
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 + tax with two strings joins them. Convert with Number() as soon as you read the input.
  • Treating an empty field as zero. Number("") is 0, so a blank quantity looks valid. Check for "" first.
  • Calling parseInt without a radix or on numbers. Write parseInt(text, 10), and use Math.trunc() for numbers.
  • Using == to compare. It hides type bugs. Convert, then compare with ===.
  • Calling .toString() on something that might be null. Use String(value) instead.

Key points

  • Explicit conversion is written by you; implicit coercion happens when an operator needs another type.
  • String(), Number() and Boolean() convert any value and never throw for normal values.
  • Number() needs the whole string to be numeric; parseInt() and parseFloat() 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() and toString().

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
JavaScript
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));
}
Output
"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?

2. Which call returns 0 instead of NaN?

3. Why does ["1", "2", "3"].map(parseInt) give [1, NaN, NaN]?

4. What is the clearest way to turn the text qty into a number?

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