Types, Coercion & Equality
Roughly 15% of a typical loop — and 15% of the mock exam here.
The seven primitives, and the one that lies
JavaScript has seven primitives — string, number, boolean, null, undefined, symbol, bigint — plus objects. typeof reports all of them honestly except null, which returns 'object' because of how values were tagged in the first release. Test for it with value === null; nothing else distinguishes it.
Two more typeof results earn their own mention: typeof NaN is 'number', since NaN is an IEEE-754 double, and typeof function () {} is 'function', the only non-primitive with its own tag. Classes report 'function' too, because class is sugar over a constructor.
For everything else typeof is too coarse. Array.isArray(value) is the array check that survives iframes and workers, where instanceof Array fails because the other realm has its own Array constructor.
Truthiness: memorize the falsy list
Exactly eight values are falsy: false, 0, -0, 0n, '', null, undefined, NaN — plus the legacy document.all. Everything else is truthy, including [] and {}, which is why if (arr) never tells you whether an array has elements.
The practical consequence is the || default bug:
const shown = total || 'none'; // a legitimate 0 becomes 'none'
const fixed = total ?? 'none'; // only null/undefined become 'none'
?? tests nullishness, not truthiness. The same split applies to ||= versus ??=.
== versus ===, without hand-waving
=== returns false whenever the types differ. == converts first, following rules worth knowing rather than memorizing case by case:
null == undefinedis true, and both are loosely equal to nothing else — not0, notfalse, not''.- Object versus primitive: the object is converted with
ToPrimitivefirst.[] == falseis true because[]becomes''becomes0, andfalsebecomes0. - Two objects compare by reference.
{ a: 1 } === { a: 1 }is false for both operators.
Use === everywhere except the idiomatic x == null, which is a compact “null or undefined” test.
The relational operators follow different rules, which produces a classic puzzle: null >= 0 is true while null > 0 and null == 0 are both false. Relational comparison converts null to 0; loose equality has a special case that refuses to.
ToPrimitive: valueOf, toString, and Date
Converting an object to a primitive runs with a hint. For hints "default" and "number", valueOf is tried first, then toString; for hint "string" the order reverses. A Symbol.toPrimitive method, when present, replaces the whole lookup.
Date is the built-in that treats the default hint as "string", which is why date + 1 concatenates while +date yields epoch milliseconds.
Numbers: floating point, NaN, and -0
0.1 + 0.2 === 0.3 is false because binary64 doubles cannot represent either operand exactly; the sum is 0.30000000000000004. Compare with Math.abs(a - b) < Number.EPSILON for small magnitudes, and store money as integer cents or BigInt.
NaN is the only value not equal to itself, so x !== x detects it — as does Number.isNaN(x), which, unlike the global isNaN, does not coerce its argument first. Object.is differs from === in exactly two cases: it treats NaN as equal to NaN, and +0 as distinct from -0.
That distinction matters for collections. Array.prototype.includes, Map keys and Set members all use SameValueZero — NaN matches NaN — while indexOf, which uses strict equality, never finds it.
Parsing and serialization traps
Number('') is 0 but Number(undefined) is NaN; Number('0x1f') is 31 while Number('1_000') is NaN, because numeric separators are a source-code feature only. parseInt is a prefix parser, so parseInt('1e3') is 1 where Number('1e3') is 1000.
JSON.stringify drops undefined and functions from objects (turning them into null inside arrays), serializes NaN and Infinity as null, and throws on BigInt. structuredClone handles Date, Map, Set, typed arrays and cycles, but throws on functions and DOM nodes and drops class prototypes — it clones data, never behavior.
Sample questions
6 of the 30 questions this domain carries in practice mode — expand one to check yourself before drilling.
1. What does typeof null evaluate to?
- 'null', because null is one of the seven primitive types the language defines
- 'object', a first-release bug the language kept for backward compatibility
- 'undefined', since null and undefined share a single internal representation
- a TypeError, because typeof rejects the two empty values by design
Answer: B. null is a primitive, but its original tag bits matched objects. Test for it with value === null; typeof can never distinguish it.
2. Which list contains every falsy value in JavaScript?
- false, 0, -0, 0n, "", null, undefined, NaN
- false, 0, "", null, undefined, NaN, [], {}
- false, 0, "", "0", null, undefined, NaN, document.all
- false, 0, "", null, undefined, NaN, Infinity, -Infinity
Answer: A. Eight values are falsy, plus the legacy document.all. Empty arrays and objects are truthy — [] == false is true only because == converts both sides.
3. An interviewer asks why [] == false is true. The correct account is:
- false converts to 0, [] converts to the primitive "" which converts to 0, and 0 == 0
- an empty array is falsy, so it compares equal to every other falsy value
- == compares the truthiness of both operands, and both sides are falsy here
- arrays inherit no useful valueOf, so the engine falls back to comparing internal identity slots instead
Answer: A. Loose equality never compares truthiness — it converts. [] is truthy, so if ([]) runs its block while [] == false is still true.
4. Why does 0.1 + 0.2 === 0.3 evaluate to false?
- JavaScript rounds every arithmetic result to seven significant digits before storing it
- numbers are stored as binary64 doubles, and 0.1 and 0.2 have no exact binary form
- === compares the source text of the two number literals rather than their values
- floating-point addition is not associative, so the operands are added out of order
Answer: B. The sum is 0.30000000000000004. Compare with Math.abs(a - b) < Number.EPSILON for small magnitudes, or move money to integer cents or BigInt.
5. const d = new Date(); d + 1 yields a string while +d yields a number. The mechanism is:
- the unary plus operator is defined only for Date and calls getTime directly
- Date overrides the + operator through a private slot other objects cannot install
- binary + always concatenates whenever either of its operands is an object, while unary + always converts its operand to a number
- ToPrimitive runs with hint "default" for binary +, and Date is the built-in that treats that hint as "string"
Answer: D. Every other object treats the default hint as number. Unary + passes hint "number", which reaches valueOf and gives the epoch milliseconds.
6. An object defines both valueOf and toString and is used in obj + ''. Which runs first, and what overrides both?
- valueOf runs first; defining Symbol.toPrimitive replaces the whole lookup
- toString runs first because the other operand is a string; Symbol.toStringTag overrides it
- valueOf runs first; only a Proxy get trap can change which method is consulted
- the engine calls whichever returns a primitive, in an order the spec leaves open
Answer: A. For hints "default" and "number" the order is valueOf then toString; for hint "string" it reverses. Symbol.toPrimitive, when present, is called instead.
Drill this domain in practice mode →
Independent community study resource — not affiliated with or endorsed by Oracle, Microsoft or Ecma International. JavaScript is a trademark of Oracle Corporation; TypeScript is a trademark of Microsoft Corporation. All questions and study notes are original, written from MDN, the ECMAScript specification and the TypeScript handbook. Everything runs in your browser; nothing you answer is stored or transmitted.