skip to content

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%

answer

  1. same type first, no conversion
  2. objects compared by reference
  3. two number cases stand out
  4. NaN never matches, -0 matches 0

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.

solid answer

~40 s

Strict equality first compares the types of the two operands: if they differ the result is `false` with no conversion at all, so `'1' === 1` and `null === undefined` are both false. When the types match, primitives compare by value — same string contents, same boolean, same number — while objects, arrays and functions compare by reference, so two separately created objects with identical properties are never strictly equal. Two number cases are worth memorising: `NaN === NaN` is `false`, because a not-a-number value equals nothing, not even itself, and `-0 === 0` is `true`, even though those are distinguishable values. Those two exceptions are exactly why the language also offers `Object.is`. In everyday code `===` is the default comparison precisely because its result depends only on the operands you can see.

code

javascript · 11 lines
javascript
console.log(1 === 1);            // true
console.log('1' === 1);          // false — different types
console.log(null === undefined); // false
console.log(NaN === NaN);        // false
console.log(-0 === 0);           // true

const a = { v: 1 };
const b = a;
a.v = 2;
console.log(a === b);            // true — same reference
console.log({ v: 2 } === { v: 2 }); // false — two objects

go deeper

for a junior

Be ready to say plainly that === compares type first and never converts, and that objects compare by reference so two identical-looking literals are not equal. Memorise the two odd cases: NaN never matches itself, -0 matches 0.

for a middle

Explain the mechanics: the type check happens before any value comparison, strings compare code unit by code unit, and object comparison is pure identity. Be able to name where the language applies strict equality implicitly, such as switch case selection.

for a senior

Show the production angle: know which numeric values sneak through a === guard incorrectly, and be able to design a comparison for a cache, a diff or a dedup step that states its treatment of NaN, -0 and object identity rather than inheriting it by accident.

for a principal

Own the codebase-wide policy: mandate === by lint, define one shared comparison helper for the cases it cannot cover, and argue why scattering ad-hoc deep-equality implementations across a codebase produces subtly different notions of sameness in different layers.

## The operator in one rule The strict equality operator `===` (the specification calls the operation *IsStrictlyEqual*) answers one question: are these two operands of the same type, and, given that, the same value? It never converts anything. That property is what makes it predictable — the result depends only on the two operands as written, not on a conversion you have to reconstruct in your head. The steps are short: 1. If the operands have different types, return `false`. Stop. 2. Numbers: if either side is `NaN`, return `false`; `+0` and `-0` are treated as equal; otherwise compare the numeric values. 3. Strings: equal when they have the same length and the same code units in the same order. 4. Booleans, `null`, `undefined`: equal when they are the same value (`null === null`, `undefined === undefined`). 5. Symbols: equal when they are the same symbol value. 6. Objects (including arrays and functions): equal only when both operands are the *same* object — the same reference. ## "Different types" is a hard stop This is the part interviewers actually probe. There is no leniency, no "but they look the same": ```js '1' === 1; // false — String vs Number 1n === 1; // false — BigInt vs Number null === undefined; // false — two distinct types, two distinct values true === 1; // false — Boolean vs Number ``` Each of those is `false` for one reason only: the types differ, so the operator returns without looking at the values. If you want the number five out of a string, you convert it yourself (`Number(input) === 5`), which puts the conversion in the source where a reader can see it. ## Objects compare by reference JavaScript has no built-in structural comparison. When both operands are objects, `===` asks whether they are the same object in memory: ```js const a = { v: 1 }; const b = a; // same object, second name const c = { v: 1 }; // a different object with equal contents a === b; // true a === c; // false a.v = 2; a === b; // still true — mutation does not change identity [] === []; // false — two fresh arrays ``` Beginners often expect `a === c` to be true because the contents match, and expect `a === b` to become false after mutating `a`. Both intuitions are wrong: contents are irrelevant, identity is everything. Comparing contents means writing it yourself — walk the keys, compare each value, and decide up front how you want to treat nested objects, missing keys and the two odd numbers below. ## The two number surprises Strict equality is exact for every value except two number cases, and they pull in opposite directions. `NaN === NaN` is `false`. `NaN` is the result of an arithmetic operation with no meaningful numeric answer (`0/0`, `Number('abc')`, `Math.sqrt(-1)`), and floating-point arithmetic defines it as unequal to everything, itself included. A useful side effect: `x !== x` is `true` for exactly one value, `NaN`, which is how hand-written checks detect it. `-0 === 0` is `true`. Negative zero is a real, distinct value that arithmetic can produce (`-1 * 0`, `Math.round(-0.2)`), but strict equality deliberately ignores the sign of zero so ordinary numeric code does not have to care. ```js NaN === NaN; // false -0 === 0; // true ``` Those two disagreements are the entire reason the language grew additional sameness comparisons: `Object.is`, which flips both answers, and the SameValueZero rule used by `Array.prototype.includes`, `Set` and `Map`, which flips only the `NaN` one. ## Places the language applies === for you You inherit strict-equality behaviour even when you do not type the operator: ```js switch (NaN) { case NaN: console.log('never runs'); default: console.log('this runs'); } [NaN].indexOf(NaN); // -1 — indexOf compares with strict equality [-0].indexOf(0); // 0 — because -0 === 0 ``` `switch` selects a clause with strict equality, so a `case NaN:` label is dead code and a `case -0:` label matches a plain `0`. `Array.prototype.indexOf` and `lastIndexOf` compare the same way, which is why they cannot find `NaN`. ## Practical guidance Default to `===` everywhere. It is the comparison whose result you can predict by reading the line, and linters flag its absence for that reason. Reach past it only for the two cases it is documented not to handle: use a `NaN`-aware comparison when a numeric pipeline can produce `NaN`, and use `Object.is` when the sign of zero genuinely carries meaning. And remember that no built-in comparison inspects object contents — structural comparison is something you write or import, not something an operator gives you.

  • Two objects have identical properties — what does === return, and how would you compare their contents instead?
    It returns `false` unless both names point at the same object; JavaScript has no built-in structural comparison. To compare contents you walk the keys yourself or use a deep-equality helper, and you decide explicitly how it should treat nested objects, missing versus `undefined` keys, and the `NaN` and `-0` cases that the primitive comparisons disagree about.
  • Where does the language apply strict equality without you writing the operator?
    `switch` selects its case clause with strict equality, and `Array.prototype.indexOf` and `lastIndexOf` search with it. So `case NaN:` can never be selected, `[NaN].indexOf(NaN)` is `-1`, and a `case -0:` label matches a plain `0` because `-0 === 0`. Any construct built on strict equality inherits both quirks.
  • Is === preferred over == for performance reasons?
    No — do not argue performance; engines handle both fine and the difference is not why style guides mandate `===`. The reason is predictability: strict equality performs no conversion, so the result is determined entirely by the two operands you can see on the line, which makes the code readable and lint-checkable.

Strict equality is a passport check, not a resemblance test: two people who look identical still fail, and the same person with a new haircut still passes.

saying these in an interview costs you the question

  • Says === converts operands, just more carefully than ==
  • Claims === compares objects property by property
  • Insists NaN === NaN evaluates to true
  • Thinks -0 === 0 evaluates to false
  • Believes === is slower because it checks types

context