Type coercion in JavaScript (implicit vs explicit)
Type coercion is JavaScript converting a value from one type to another, either automatically during an operation or explicitly when you call a conversion function yourself.
Theory
TL;DR
- Implicit coercion happens without you asking.
"5" + 3gives"53", not8. - Explicit coercion is calling
Number(),String(), orBoolean()on purpose. - The
+operator concatenates if either operand is a string. Every other math operator converts to numbers. ==triggers coercion before comparing.===does not.- Eight values are falsy. Everything else is truthy, including
[]and{}.
Quick example
// Implicit: JavaScript decides
"5" + 3; // "53" - string wins with +
"5" - 3; // 2 - string becomes number
true + 1; // 2 - true becomes 1
null + 5; // 5 - null becomes 0
// Explicit: you decide
Number("5"); // 5
String(42); // "42"
Boolean(0); // falseIn "5" + 3, JavaScript sees a string and converts the number. In "5" - 3, subtraction has no string meaning, so the string becomes a number instead.
Three contexts for implicit coercion
JavaScript coerces values in three main situations.
String context (the + operator with a string operand): the other value becomes a string. "5" + null gives "5null". "5" + {} gives "5[object Object]". Anything that touches + when a string is nearby becomes a string.
Numeric context (-, *, /, %, unary -): operands become numbers. "6" * "2" gives 12. null becomes 0. undefined becomes NaN. true becomes 1, false becomes 0.
Boolean context (conditionals, !, ||, &&): values get evaluated as truthy or falsy. Empty string is falsy. Zero is falsy. null, undefined, and NaN are falsy. But [] and {} are truthy. That trips people up constantly.
The eight falsy values
| Value | Type |
|---|---|
false | Boolean |
0 | Number |
-0 | Number |
0n | BigInt |
"" | String |
null | null |
undefined | undefined |
NaN | Number |
Everything else is truthy. "0", "false", [], {} are all truthy. This shows up as a direct question in interviews and as a real bug in production.
Explicit coercion tools
Number(value) converts by the spec rules. Number("") gives 0, Number(null) gives 0, Number(undefined) gives NaN, Number("abc") gives NaN.
parseInt("42px") pulls out 42 and stops at the first non-numeric character. parseFloat("3.14abc") does the same for decimals. Both are useful when parsing user input or CSS values.
String(value) is the safe way to stringify anything. String(null) gives "null", unlike null.toString() which throws a TypeError.
Boolean(value) and the double-negation !! produce the same result. !! is just shorter inside conditions.
Loose vs strict equality
== applies coercion rules before comparing. === compares type and value together, no coercion involved. The strict equality operator (===) is the default choice for most comparisons.
"5" == 5; // true - string becomes number
null == undefined; // true - special rule in the spec
null == 0; // false - null only equals null or undefined
"" == false; // true - both become 0
[] == false; // true - [] becomes "" becomes 0The null == undefined rule is there by design. They are the only two values that equal each other with == and nothing else. Writing if (value == null) catches both null and undefined in one condition, which some teams use intentionally.
Common mistakes
These appear in interviews. Often literally the same examples.
[] + []; // "" - both convert to "", concatenated
[] + {}; // "[object Object]" - [] is "", {} becomes a string
{} + []; // 0 - {} parsed as a block, +[] is unary plus on []
true + true; // 2 - both become 1
"2" + "2" - "2"; // 20 - "2"+"2" is "22", then "22"-"2" is 20
NaN === NaN; // false - NaN is not equal to itself
typeof NaN; // "number" - it really isThe {} + [] result depends on context. At the start of a console line, {} is a block statement, so +[] is unary plus on an empty array, giving 0. Inside an expression like var x = {} + [], you get "[object Object]" instead.
I once spent an hour debugging a React component that rendered 0 instead of nothing. The culprit was count && <Component /> with count coming from an empty array's .length. NaN causes similar surprises when unchecked number conversions flow through arithmetic.
Where this appears in real code
- React conditionals:
count && <Component />renders the text0whencountis0. Usecount > 0 && <Component />instead. - URL params:
Number(searchParams.get("page"))returns0when the param is missing. Check fornullbefore converting. - API responses: backends sometimes send
"true"as a string.Boolean("true")istrue, and so isBoolean("false"). Compare with=== "true"for string booleans. - Form inputs:
input.valueis always a string. Multiplication coerces it to a number, but addition concatenates. CallNumber()explicitly.
Follow-up questions
Q: Why does typeof NaN return "number"?
A: NaN is defined in IEEE 754 as a numeric type. It represents an invalid computation result, like 0/0 or parseInt("abc"). JavaScript follows that spec directly.
Q: What is the difference between null == undefined and null === undefined?
A: null == undefined is true because the spec has a special rule for this pair. null === undefined is false because their types differ. This is one of the few cases where == behaves predictably.
Q: When would you actually use == over ===?
A: The main case is if (value == null), which catches both null and undefined at once. Outside of that pattern, === everywhere.
Q: How does JavaScript convert an object to a primitive during coercion?
A: It calls valueOf() first, then toString(). If neither returns a primitive, a TypeError is thrown. Arrays get their toString() from join(""), which is why [] + [] gives "".
Q: What does [] == ![] evaluate to, and why?
A: true. ![] is false because [] is truthy. Then [] == false coerces both sides: [] becomes "", "" becomes 0, false becomes 0. So 0 == 0 is true. This is exactly why senior devs avoid == in most contexts.
Examples
Explicit conversion in form handling
function calculateTotal(priceInput, quantityInput) {
const price = Number(priceInput.value);
const quantity = Number(quantityInput.value);
if (isNaN(price) || isNaN(quantity)) {
return "Invalid input";
}
return price * quantity;
}
// priceInput.value = "10", quantityInput.value = "3"
// "10" * "3" = 30 - works via implicit coercion anyway
// "10" + "3" = "103" - but + would concatenate, not addinput.value is always a string. Multiplication happens to coerce strings to numbers, so the implicit path produces the right number. But one accidental + and you get concatenation. Explicit Number() makes the intent clear and surfaces NaN before it propagates into a price total.
The + operator trap in React
function CartSummary({ items }) {
const count = items.length;
return (
<div>
{/* Bug: renders "0" as text when count is 0 */}
{count && <p>Items in cart: {count}</p>}
{/* Fix: convert to boolean first */}
{count > 0 && <p>Items in cart: {count}</p>}
{/* Or use a ternary */}
{count ? <p>Items in cart: {count}</p> : null}
</div>
);
}When count is 0, the expression 0 && <p>...</p> short-circuits to 0, and React renders the number 0 in the DOM. This is a real bug that shows up in code review regularly. count > 0 evaluates to a boolean before the &&, so React gets false and renders nothing.
Handling coercion in API responses
async function getUser(id) {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
// API returns: { active: "true", score: "42", role: null }
return {
id: data.id,
active: data.active === "true", // not Boolean("true")
score: Number(data.score) || 0, // 0 as fallback for NaN
role: data.role ?? "guest", // ?? not ||, to allow 0 and ""
};
}Boolean("false") is true because any non-empty string is truthy. For string booleans from an API, compare with === "true" directly. The ?? operator only triggers on null and undefined, not on 0 or "", making it safer than || for numeric defaults.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.