skip to content

Equality and Conversion

JavaScript ships four sameness algorithms plus an implicit conversion machinery that connects them. This group produces more "explain this output" interview questions than any other corner of the language.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

18

In JavaScript, what does the == (loose equality) operator do that === does not, and what do '5' == 5 and '5' === 5 each evaluate to?

level: juniorimportance: must knowfreq 86%

answer

  1. one converts, the other refuses
  2. same type means no conversion at all
  3. the string side becomes a number
  4. '5' == 5 versus '5' === 5

basics

~20 s

The == operator converts operands to a common type before comparing; === requires matching types and never converts. So '5' == 5 is true, because the string is converted to the number 5, while '5' === 5 is false.

solid answer

~40 s

`==` runs the abstract (loose) equality algorithm: if both operands are already the same type it behaves exactly like `===`, and otherwise it converts one side and retries the comparison. `===` compares the types first and returns `false` immediately when they differ — no conversion ever happens. So `'5' == 5` is `true`, because the string operand goes through ToNumber and becomes `5`, then `5 == 5`; `'5' === 5` is `false`, because a string is never strictly equal to a number. The practical rule is to default to `===` so a comparison says exactly what it means, and to use `==` only in a deliberate, documented case. If you do write `==`, be able to say which operand got converted and into what — that is what the interviewer is actually checking.

code

javascript · 7 lines
javascript
console.log('5' == 5);           // true  - string converted to number
console.log('5' === 5);          // false - different types
console.log(0 == false);         // true  - boolean converted to number
console.log(0 === false);        // false
console.log(null == undefined);  // true  - special rule
console.log(null === undefined); // false
console.log({} == {});           // false - same type, compared by reference

go deeper

for a junior

Be able to say plainly that == converts before comparing and === does not, and to predict '5' == 5 as true and '5' === 5 as false. Say you default to === in your own code.

for a middle

Explain the mechanics: same-type operands delegate to strict equality, a string compared with a number goes through ToNumber, and booleans become 1 or 0. Predict '' == 0 and '' == '0' correctly.

for a senior

Show where loose equality actually causes production bugs — string-typed inputs compared against numbers, comparisons against false that also match 0 and '' — and describe converting explicitly at the boundary instead.

for a principal

Own the codebase-wide position: a lint rule banning == with one narrow carve-out, explicit parsing at system edges so types are known inside, and the argument for why non-transitive equality is a maintainability risk rather than a style preference.

## Two operators, one difference JavaScript has two equality operators. `===` is strict equality: it looks at the types of the two operands and, if they differ, the answer is `false` with no further work. `==` is loose equality, defined in the specification as the abstract equality comparison. It is not "a sloppier ===": it is a small algorithm that tries to bring the two operands to a comparable form and only then compares them. The crucial thing to internalise is that `==` is a *superset* of `===` in one specific sense: **when both operands already have the same type, `==` delegates to strict equality**. `'a' == 'a'`, `1 == 1`, `null == null`, `NaN == NaN` — all of these are decided by the strict rules, so `NaN == NaN` is `false` just as `NaN === NaN` is. Coercion only enters the picture when the two types differ. ## What == does when the types differ The algorithm is short enough to memorise as a sequence of checks: 1. **Same type** → compare strictly, done. 2. **null and undefined** are loosely equal to each other, and to nothing else. Neither is ever converted. 3. **Number vs String** → convert the string with ToNumber, then retry. 4. **Boolean on either side** → convert that boolean with ToNumber (`true` becomes `1`, `false` becomes `0`), then retry. 5. **Object vs a primitive** (number, string, bigint, symbol) → convert the object to a primitive, then retry. 6. Anything left over → `false`. There is also a rule for comparing a BigInt with a Number or a numeric string, which compares mathematical values, so `1n == 1` is `true` while `1n === 1` is `false`. Two consequences fall straight out of this list. First, **booleans and strings both end up as numbers**, which is why `'0' == false` is `true`. Second, **two objects are never coerced against each other** — they are the same type, so step 1 applies and references are compared: `{} == {}` and `[] == []` are both `false`. ## Worked cases ```js console.log('5' == 5); // true — ToNumber('5') is 5 console.log('5' === 5); // false — string vs number console.log(0 == false); // true — ToNumber(false) is 0 console.log('' == 0); // true — ToNumber('') is 0 console.log('' == '0'); // false — same type, compared as text console.log(null == undefined); // true — the special rule console.log(null == 0); // false — null is never converted ``` The pair `'' == 0` (true), `'0' == 0` (true), `'' == '0'` (false) is worth remembering because it shows that `==` is **not transitive**. Transitivity is something people unconsciously assume of an equality operator, and its absence is exactly why loose equality causes bugs that survive review. ## Why interviewers ask this The question separates candidates who reason from the algorithm from candidates who have memorised folklore. Folklore says "`==` compares value and `===` compares value and type" — a phrasing that is close enough to sound right and useless for predicting `'0' == false` or `[] == 0`. The algorithmic answer predicts every case, including the ones that look bizarre. A second reason is that the conversion is asymmetric in practice. In `'5' == 5` the *string* is converted, never the number. Candidates who say "both get turned into strings" will confidently mispredict `'0' == 0` versus `'' == '0'`. ## Where it bites in real code The classic production bug is a value that arrives as a string — a query parameter, a form field, a value read out of a text-based store — and is compared with `==` against a number. It works, so nobody notices the type is wrong, until an empty field arrives and `'' == 0` quietly reports true, or `'0'` is treated as falsy elsewhere in the same function. Another is a comparison against `false` intended as a "did the user answer no" check, which also matches `0`, `''`, `'0'` and `[]`. ## The rule to state out loud Use `===` by default. Convert explicitly — `Number(input) === 5`, `String(id) === key` — so the conversion is visible in the code rather than hidden in the operator. Reserve `==` for cases where you can articulate the coercion, the best-known being `x == null` as a compact check for "null or undefined". Everything else is better written as an explicit conversion plus a strict comparison.

  • If == and === behave identically when the operands share a type, is there any case where === is true but == is false?
    No. `==` starts by delegating to strict equality whenever the types match, and every other branch only adds ways for the comparison to succeed. So `===` returning true always implies `==` returns true; loose equality is strictly more permissive. The reverse is not true: `'5' == 5` is true while `'5' === 5` is false.
  • Is === faster than == because it skips the conversion work?
    Performance is not the reason to prefer `===`. In practice engines optimise both, and when the operand types already match `==` does the same work as `===` anyway. Choose `===` because it is predictable and self-documenting: the comparison fails loudly when the types are wrong instead of silently converting and hiding a type bug.
  • What does == do when both operands are objects?
    Nothing special — they are the same type, so it falls through to strict equality and compares references. `[] == []` and `{} == {}` are both false because each literal creates a distinct object. Coercion of an object only happens when the *other* operand is a primitive.

saying these in an interview costs you the question

  • == compares values while === compares references
  • Both operands are converted to strings before comparing
  • === is preferred mainly because it runs faster
  • == ignores the types entirely, so anything can match
  • Two identical object literals are loosely equal

context

open as a page

In JavaScript, what does the strict equality operator === compare, and how does it behave when its two operands have different types?

level: juniorimportance: must knowfreq 88%

basics

~20 s

=== returns true only when both operands have the same type and the same value. Different types are never converted, so the result is immediately false. Objects and arrays compare by reference, not by contents.

open as a page

In JavaScript, why does '5' + 3 give '53' while '5' - 3 gives 2? Explain what the + operator does with mixed operand types.

level: juniorimportance: must knowfreq 70%

basics

~20 s

Binary + is the only arithmetic operator that is also string concatenation: after converting both operands to primitives, if either one is a string it concatenates, otherwise it adds numerically. Every other arithmetic operator converts to number unconditionally, so '5' - 3 is 2.

open as a page

In JavaScript, which values are falsy — that is, which convert to false when used as an `if` condition — and which commonly-assumed-falsy values are actually truthy?

level: juniorimportance: must knowfreq 85%

basics

~20 s

JavaScript has a fixed falsy list: false, 0, -0, 0n, the empty string, null, undefined, and NaN. Every other value is truthy — including '0', 'false', the empty array, the empty object, and every function.

open as a page

In JavaScript, the expression '0' == false evaluates to true. Walk through the loose-equality conversion steps that produce that result.

level: middleimportance: must knowfreq 58%

basics

~20 s

A boolean operand is converted to a number first, so false becomes 0 and the comparison becomes '0' == 0. The string is then converted with ToNumber to 0, so 0 == 0 is true. Booleans are never compared as booleans.

open as a page

How does JavaScript's Object.is differ from the === operator, and for which values do the two disagree?

level: middleimportance: must knowfreq 58%

basics

~10 s

Object.is behaves exactly like === except for two values: Object.is(NaN, NaN) is true while NaN === NaN is false, and Object.is(-0, 0) is false while -0 === 0 is true. Neither converts types.

open as a page

When JavaScript needs a primitive out of an object, it runs the ToPrimitive operation with a hint. What are the possible hints, and in what order does the engine try valueOf and toString for each?

level: middleimportance: must knowfreq 55%

basics

~20 s

ToPrimitive takes a hint of "number", "string", or "default". For number and default it calls valueOf first, then toString; for string it calls toString first, then valueOf. The first call returning a primitive wins; otherwise a TypeError is thrown.

open as a page

In JavaScript, what value do the `||` and `&&` operators produce — a boolean, or one of their operands? Walk through `'' || 'default'` and `0 && compute()`.

level: middleimportance: must knowfreq 70%

basics

~20 s

Both return one of their operands unchanged, never a coerced boolean. || returns the left operand if it is truthy, otherwise the right one; && returns the left operand if it is falsy, otherwise the right one. The unselected side is never evaluated.

open as a page

A service reads its settings with `const retries = options.retries || 3` and `const prefix = options.prefix || 'app'`. A caller passes `{ retries: 0, prefix: '' }` and both values are silently ignored. Explain the mechanism and how you would fix it.

level: seniorimportance: must knowfreq 60%

basics

~10 s

|| tests truthiness, and 0 and '' are falsy, so both caller values lose to the defaults. Use ??, which falls back only on null and undefined: options.retries ?? 3 keeps an explicit 0.

open as a page

Why does the JavaScript expression [] == ![] evaluate to true? Derive it from the loose-equality algorithm.

level: middleimportance: should knowfreq 44%

basics

~20 s

The ! is applied first and yields false, because an array is truthy. Comparing [] == false converts false to 0, then converts the array to its primitive form, the empty string, which converts to 0 — so 0 == 0 is true.

open as a page

In JavaScript, null == undefined is true, but null == 0 and null == false are both false. Which rule of the loose-equality algorithm explains this?

level: middleimportance: should knowfreq 52%

basics

~20 s

The loose-equality algorithm has an explicit step making null and undefined equal to each other and to nothing else. Neither value is ever converted to a number, so null == 0 and null == false both fall through to false.

open as a page

In JavaScript, [NaN].includes(NaN) returns true but [NaN].indexOf(NaN) returns -1. Why do these two array methods disagree?

level: middleimportance: should knowfreq 46%

basics

~10 s

They use different comparisons. indexOf searches with strict equality, where NaN never matches itself, while includes searches with SameValueZero, which treats NaN as matching NaN. Both treat -0 and 0 as the same value.

open as a page

In JavaScript, why does new Date() + 1 produce a string while new Date() - 1 produces a number?

level: middleimportance: should knowfreq 40%

basics

~20 s

Date is the one built-in whose Symbol.toPrimitive treats the "default" hint as "string". Binary + passes the default hint and gets the date's text form, so it concatenates; subtraction passes the "number" hint and gets the millisecond timestamp, so it subtracts.

open as a page

Why is `if (items)` a broken way to check that a JavaScript array has elements, and what does ToBoolean do with objects in general?

level: middleimportance: should knowfreq 45%

basics

~20 s

ToBoolean classifies every object as true without inspecting it, so an empty array is truthy. if (items) only proves the variable is not null or undefined; emptiness needs items.length === 0, or .size for a Map or Set.

open as a page

Most JavaScript style guides ban ==, yet many carve out an exception for the form x == null. What does that check actually match, and what would you require of a team that allows the exception?

level: seniorimportance: should knowfreq 38%

basics

~20 s

x == null is true for exactly two values, null and undefined, so it is a precise nullish check that preserves 0, empty strings and false. Allow it only as that fixed idiom, enforced by tooling, so any other == still stands out as a defect.

open as a page

You are writing a memoization helper in JavaScript that skips recomputation when the new argument is "the same" as the previous one. Which sameness comparison should it use, and what breaks with each candidate?

level: seniorimportance: should knowfreq 33%

basics

~20 s

Use SameValueZero: strict equality plus a NaN-matches-NaN case. Plain === makes a NaN argument look changed on every call, and Object.is additionally reports -0 as different from 0, which caches rarely want. None of them compares object contents.

open as a page

You are writing a Money class in JavaScript whose instances get logged, concatenated and compared. How do you take control of what those instances convert to, and what should the object do when it receives the "default" hint?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Implement a Symbol.toPrimitive method taking the hint: return formatted text for "string", a number for "number", and for "default" either pick one deliberately or throw, so ambiguous uses like money1 + money2 fail loudly instead of silently concatenating.

open as a page

In JavaScript, ([] + {}) evaluates to the string '[object Object]', but a script line that starts with {} + [] evaluates to 0. Explain both results.

level: middleimportance: nice to knowfreq 30%

basics

~20 s

Two different mechanisms. In expression position both operands convert to primitives — '' and '[object Object]' — and + concatenates. At the start of a statement, {} parses as an empty block, so what remains is unary +[], which converts the empty array to '' and then to the number 0.

open as a page