JavaScript Type Conversion
JavaScript is a dynamically typed language, which means a value’s type is not fixed at compile time and the engine will often change a value’s type on your behalf. Type conversion is the general process of turning a value of one type (string, number, boolean, object) into another. When you ask for the conversion explicitly, it’s called explicit conversion (or type casting); when JavaScript does it automatically behind the scenes — say, because you used + on a string and a number — it’s called implicit conversion, or coercion. Coercion is one of the most common sources of confusing bugs in JavaScript, so understanding exactly when and how it happens is essential to writing reliable code.
Overview: How Type Conversion Works
JavaScript’s type system recognizes a handful of primitive types — string, number, boolean, bigint, symbol, undefined, and null — plus object (which includes arrays and functions). Whenever an operator or function needs a value of a particular type but receives something else, the engine runs an internal conversion routine. The ECMAScript specification defines these as abstract operations: ToString, ToNumber, ToBoolean, and ToPrimitive.
ToPrimitive is the key operation for objects (including arrays). When an object needs to become a primitive — for example, when you write myArray + 1 — the engine calls ToPrimitive(value, hint), where hint is one of "string", "number", or "default". For a "string" hint, JavaScript tries the object’s toString() method first, then falls back to valueOf(). For a "number" or "default" hint, it tries valueOf() first, then toString(). This is why an array like [1, 2, 3] becomes the string "1,2,3" (its default toString() calls join(",")), and why a plain object becomes the string "[object Object]".
Once a value is primitive, three more conversions matter: ToNumber turns a value into a number (numeric strings are parsed, true becomes 1, false becomes 0, null becomes 0, and undefined becomes NaN); ToString turns a value into text; and ToBoolean turns a value into true or false according to a short, memorable list of falsy values: false, 0, -0, 0n, "", null, undefined, and NaN. Every other value — including every object and array, even an empty one like [], and the string "0" — is truthy.
Syntax
There is no dedicated “cast” syntax in JavaScript; instead you call built-in wrapper functions or rely on operators that trigger coercion.
| Form | What it does |
|---|---|
String(value) |
Explicitly converts value to a string using ToString. |
Number(value) |
Explicitly converts value to a number using ToNumber. |
Boolean(value) |
Explicitly converts value to a boolean using ToBoolean. |
parseInt(value, radix) |
Parses a leading integer out of a string, ignoring trailing non-numeric text. |
parseFloat(value) |
Parses a leading floating-point number out of a string. |
+value |
Unary plus; a shorthand for Number(value). |
!!value |
Double negation; a shorthand for Boolean(value). |
`${value}` |
Template literal interpolation; always applies ToString to value. |
Examples
The first example shows explicit conversion with the wrapper functions and how Number() treats different inputs.
const numStr = "42";
const num = Number(numStr);
console.log(num, typeof num);
const bool = Boolean(0);
console.log(bool);
const str = String(123);
console.log(str, typeof str);
console.log(Number("3.14"));
console.log(Number(""));
console.log(Number("abc"));
console.log(Number(null));
console.log(Number(undefined));
Output:
42 number
false
123 string
3.14
0
NaN
0
NaN
Notice that Number("") is 0 (an empty string is treated as no digits, which parses to zero) while Number("abc") is NaN (non-numeric text cannot be parsed at all). Also note that Number(null) is 0 but Number(undefined) is NaN — these two “nothing” values are not treated the same way by ToNumber.
The second example shows implicit coercion triggered by operators.
console.log(1 + "2");
console.log("5" - 2);
console.log("5" + 2);
console.log(1 + true);
console.log(1 + null);
console.log(1 + undefined);
console.log("5" * "2");
console.log([] + []);
console.log([] + {});
console.log(1 == "1");
console.log(0 == false);
console.log(null == undefined);
console.log(NaN == NaN);
Output:
12
3
52
2
1
NaN
10
[object Object]
true
true
true
false
The + operator is special: if either operand is (or becomes) a string, + performs string concatenation instead of addition, which is why 1 + "2" is "12" but "5" - 2 is 3 (subtraction always coerces both operands to numbers). [] + [] converts both empty arrays to "" and concatenates them into another empty string, which is why that line prints a blank line. Also note the last line: NaN == NaN is false, because NaN is defined to never equal anything, including itself — use Number.isNaN() to test for it.
The third example applies these ideas to a realistic task: turning an array of raw form-input strings into usable values.
function parseFormInput(input) {
const trimmed = input.trim();
const asNumber = Number(trimmed);
if (trimmed !== "" && !Number.isNaN(asNumber)) {
return asNumber;
}
return trimmed;
}
const formValues = ["42", " 3.14 ", "hello", "", "true"];
const parsed = formValues.map(parseFormInput);
console.log(parsed);
const total = parsed
.filter(value => typeof value === "number")
.reduce((sum, value) => sum + value, 0);
console.log(`Total of numeric fields: ${total}`);
console.log(String(parsed));
Output:
[ 42, 3.14, 'hello', '', 'true' ]
Total of numeric fields: 45.14
42,3.14,hello,,true
Each string is trimmed and tested: if it parses cleanly to a number, we keep the number; otherwise we keep the trimmed string as-is. The last line shows String() being applied to the whole array, which triggers ToPrimitive with a "string" hint on the array, joining its elements with commas (note the empty string produces two commas in a row with nothing between them).
Under the Hood: Step by Step
When the engine evaluates 1 + "2", it roughly does the following: (1) it evaluates both operands, getting the number 1 and the string "2"; (2) it applies ToPrimitive to each (a no-op here, since both are already primitives); (3) because at least one operand is a string, the + algorithm converts the other operand to a string via ToString, turning 1 into "1"; (4) it concatenates the two strings, producing "12". Contrast this with "5" - 2: the - operator has no string-concatenation meaning, so it always applies ToNumber to both operands regardless of type, turning "5" into 5 before subtracting.
Loose equality (==) follows a similar but more involved algorithm (the Abstract Equality Comparison): if both operands are the same type, it behaves like ===. If the types differ, it applies a fixed set of coercion rules — for example, null and undefined are only ever loosely equal to each other and to nothing else; a boolean operand is first converted to a number; and a number-vs-string comparison converts the string to a number. This cascading coercion is precisely why == produces results that look inconsistent unless you know the rules, and why most style guides recommend avoiding it.
Common Mistakes
Mistake 1: trusting == to mean “basically equal.” Loose equality’s coercion rules make several unrelated-looking values compare as equal.
console.log(0 == false);
console.log("" == false);
console.log("0" == false);
console.log(null == false);
console.log(undefined == false);
console.log([] == false);
Output:
true
true
true
false
false
true
Notice [] == false is true: the array is converted to a primitive (""), then to a number (0), which then equals false converted to 0. Meanwhile null == false and undefined == false are false, because null/undefined only loosely equal each other. This inconsistency is the whole problem with ==. The fix is to compare with strict equality (===), which never coerces, and to write explicit checks when you actually mean something specific, such as “is this exactly the boolean false“:
function isExplicitlyFalse(value) {
return value === false;
}
console.log(isExplicitlyFalse(0));
console.log(isExplicitlyFalse(false));
console.log(isExplicitlyFalse(""));
console.log(isExplicitlyFalse([]));
Output:
false
true
false
false
Mistake 2: forgetting that + concatenates unconverted strings. A very common bug happens when values from user input (which arrive as strings, e.g. from an HTML form or prompt()) are combined with + expecting arithmetic:
function sumInputs(a, b) {
return a + b;
}
const result = sumInputs("10", "5");
console.log(result);
console.log(typeof result);
Output:
105
string
Instead of the expected sum 15, the strings are concatenated into "105". The fix is to explicitly convert both inputs to numbers before adding:
function sumInputs(a, b) {
return Number(a) + Number(b);
}
const result = sumInputs("10", "5");
console.log(result);
console.log(typeof result);
Output:
15
number
Best Practices
- Prefer
===and!==over==and!=so comparisons never depend on hidden coercion rules. - Convert values explicitly with
Number(),String(), orBoolean()when you know a value’s type needs to change, rather than relying on an operator to do it implicitly. - Use
Number.isNaN(value)instead of the globalisNaN(value), since the global version coerces its argument to a number first and can give surprising results for non-numeric values. - When parsing user input or query-string parameters, convert with
Number()orparseFloat()and immediately check the result withNumber.isNaN()before using it. - Use template literals (
`${value}`) for readable, predictable string conversion instead of+ ""concatenation tricks. - Never rely on an array or object being “falsy” for an empty check —
[]and{}are always truthy; usearray.length === 0orObject.keys(obj).length === 0instead. - Use
typeof,Array.isArray(), orObject.prototype.toString.call()for type checks rather than inferring type from coercion behavior.
Practice Exercises
Exercise 1: Without running any code, predict the output of each of these expressions, then check yourself: console.log("3" + 4 + 5), console.log(3 + 4 + "5"), console.log(true + true), console.log("5" == 5), console.log("5" === 5).
Exercise 2: Write a function sumNumericStrings(list) that takes an array of strings (e.g. ["3", "abc", "10.5", ""]) and returns the sum of only the entries that convert cleanly to a valid number, ignoring the rest. For the example array your function should return 13.5.
Exercise 3: Explain, in your own words, why console.log([1, 2] + [3, 4]) prints "1,23,4" rather than throwing an error or producing an array. Use the ToPrimitive rules from the Overview section to justify your answer.
Summary
- Type conversion can be explicit (calling
String(),Number(),Boolean()) or implicit (triggered automatically by operators like+,-,==). - Objects and arrays become primitives through the
ToPrimitiveabstract operation, which triesvalueOf()/toString()in an order determined by a “string”, “number”, or “default” hint. - The falsy values are exactly:
false,0,-0,0n,"",null,undefined, andNaN— everything else, including empty arrays and objects, is truthy. +concatenates if either operand is a string; other arithmetic operators like-,*, and/always coerce both operands to numbers.==applies a web of coercion rules that are easy to get wrong;===avoids them entirely.- Use
Number.isNaN(), explicitNumber()/String()/Boolean()calls, and length checks instead of relying on coercion side effects.
