Skip to main content

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" + 3 gives "53", not 8.
  • Explicit coercion is calling Number(), String(), or Boolean() 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

javascript
// 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); // false

In "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

ValueType
falseBoolean
0Number
-0Number
0nBigInt
""String
nullnull
undefinedundefined
NaNNumber

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.

javascript
"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 0

The 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.

javascript
[] + []; // "" - 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 is

The {} + [] 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 text 0 when count is 0. Use count > 0 && <Component /> instead.
  • URL params: Number(searchParams.get("page")) returns 0 when the param is missing. Check for null before converting.
  • API responses: backends sometimes send "true" as a string. Boolean("true") is true, and so is Boolean("false"). Compare with === "true" for string booleans.
  • Form inputs: input.value is always a string. Multiplication coerces it to a number, but addition concatenates. Call Number() 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

javascript
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 add

input.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

javascript
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

javascript
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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?