Type coercion is JavaScript automatically converting a value from one type to another so an operation can go ahead. Instead of throwing an error when types do not match, JavaScript "makes them fit." That is convenient until it guesses differently than you did. This lesson covers the rules, the famous surprises, and how to stay out of trouble.
Coercion is implicit: JavaScript does it for you. Conversion is explicit: you ask for it with Number(), String(), or Boolean() (next lesson). Same two strings, two operators, two completely different results:
console.log("5" + "2"); // + with strings: join
console.log("5" - "2"); // - only does math: convert to numbers
If either side of + is a string, the other side becomes a string and the two are joined. Other values are converted to text first (arrays with their commas, objects as [object Object]):
console.log("5" + 2);
console.log("1" + null, "1" + undefined);
console.log([1, 2] + [3]);
console.log({} + "x");
Without any strings, + adds, turning true into 1 and null into 0. Two arrays have no numbers, so they are turned into strings anyway:
console.log(1 + true, true + true, 1 + null, 1 + undefined);
console.log(JSON.stringify([] + [])); // two empty strings joined
Every other math operator only knows numbers, so it converts both sides to numbers. The unary plus, a + in front of a single value, is a short way to force a number:
console.log("5" - 2, "5" * "2", "10" / "2", "7" % "2");
console.log(+"5", +"", +" ", +"abc");
console.log(+true, +false, +null, +undefined);
console.log(+[], +[5], +[1, 2], +{});
"5" + "10" is "510", not 15. Convert first: Number(a) + Number(b). The same thing hides in +=: with let s = "5", s += 1 makes "51", while s++ converts and gives the number 6.
In an if, a loop condition, !, &&, or ||, any value is coerced to a boolean. There are exactly 8 falsy values; everything else is truthy:
const falsy = [false, 0, -0, 0n, "", null, undefined, NaN];
console.log(falsy.map(v => Boolean(v)));
console.log(Boolean("0"), Boolean("false"), Boolean(" "), Boolean([]), Boolean({}));
The second line surprises people: the text "0" and "false" are non-empty strings, so they are truthy, and so is every array and object, even empty ones. The double NOT !!value is a common short way to write Boolean(value).
-0 and 0n (BigInt zero), for 8 in total.
console.log(0 || "default", "" || "none");
console.log("hi" && "there", null && "x");
|| returns the first truthy value (or the last value), and && returns the first falsy value (or the last value). They coerce only to decide; what you get back is one of the original values.
== coerces both sides toward a common type before comparing. The results are hard to predict:
console.log(5 == "5", 0 == "", 0 == "0");
console.log("" == "0"); // but both equal 0!
console.log(false == "0", [] == false, [0] == false);
console.log(null == 0, null == undefined);
Look at the first two lines: 0 == "" and 0 == "0", but "" == "0" is false. Two strings are compared as strings, with no conversion. The rules are consistent, but nobody wants to keep them in their head while coding.
=== and !==. They never coerce: different types are simply not equal. The one widely accepted use of == is value == null, meaning "null or undefined" (JS undefined).
If both sides are strings, they are compared alphabetically, character by character. Otherwise both are converted to numbers:
console.log("10" < "9"); // text: "1" comes before "9"
console.log(10 < "9"); // one number: numeric compare
console.log(null >= 0, null > 0, null == 0);
The last line looks impossible: null is at least 0, but not greater than 0, and not equal to 0. The comparison operators convert null to 0, but == has its own rule that null only equals undefined.
const o = {};
o[1] = "a";
o["1"] = "b"; // same key: 1 became "1"
console.log(Object.keys(o), o[1]);
console.log([10, 9, 1, 100].sort()); // sort compares as strings
console.log(`${null} and ${[1, 2]}`);
sort converts items to strings, which is why "100" sorts before "9". Use .sort((a, b) => a - b) for numbers.${} value to a string.A few types throw instead of guessing, which is actually safer:
console.log(1n + 1);
Symbols also refuse: Symbol("a") + 1 is TypeError: Cannot convert a Symbol value to a number.
| Compare with === and !== | No hidden conversion |
| Convert input on purpose: Number(), String() | Your intent is visible in the code |
| Check numbers with Number.isNaN / Number.isFinite | Catches failed conversions |
| Give sort a compare function for numbers | Avoids string ordering |
| Use ?? for defaults when 0 or "" are valid | || treats them as missing |
What does this log?
console.log("3" - - "3", "b" + "a" + +"a" + "a");
6 baNaNa. - "3" is -3, and "3" - -3 is 6. In the second, +"a" is NaN, which gets joined into the string as the text "NaN".Which of these values is falsy?
"0"[]0n"false"0n (BigInt zero) is one of the 8 falsy values. A and D are non-empty strings, and every array is truthy.Two text boxes hold "5" and "10", and a + b shows 510. Fix it.
Number(a) + Number(b) (or +a + +b) gives 15.null, and [] with + and -Boolean() of all 8 falsy values and 5 surprising truthy onesa == b is true but a === b is false[25, 3, 100, 42] with and without a compare function and explain the difference