How does JavaScript's Object.is differ from the === operator, and for which values do the two disagree?
answer
- only two values disagree
- one is a not-a-number case
- the other has a sign bit
- NaN matches, zeros stay distinct
basics
~10 sObject.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.
solid answer
~40 s`Object.is`, added in ES2015, implements the spec's SameValue algorithm. Like `===` it performs no conversion, and for every value except two it gives the identical answer — including objects, which it still compares by reference, so it is not a deep-equality check. The two divergences are deliberate: `Object.is(NaN, NaN)` is `true` where `NaN === NaN` is `false`, and `Object.is(-0, 0)` is `false` where `-0 === 0` is `true`. In other words `Object.is` asks "are these literally the same value?", including the sign of zero, while `===` applies the numeric-comparison rules that make `NaN` match nothing and make the two zeros interchangeable. Use it when the sign of zero matters or when you want a `NaN`-aware comparison in one call; keep `===` as the everyday operator.
code
javascript · 11 linesfunction sameValue(x, y) {
if (x === y) {
// 1/0 is Infinity, 1/-0 is -Infinity
return x !== 0 || 1 / x === 1 / y;
}
return x !== x && y !== y; // only NaN is unequal to itself
}
console.log(sameValue(NaN, NaN), Object.is(NaN, NaN)); // true true
console.log(sameValue(-0, 0), Object.is(-0, 0)); // false false
console.log(sameValue(1, '1'), Object.is(1, '1')); // false falsego deeper
Know that Object.is exists and gives the same answer as === for almost everything, with two memorable exceptions: it says NaN equals NaN, and it says -0 does not equal 0.
Explain the mechanics: Object.is implements SameValue, performs no conversion, still compares objects by reference, and can be reimplemented with === plus a 1/x check for zeros and a self-inequality check for NaN.
Demonstrate judgment about when the distinction matters in running systems — where -0 leaks out of arithmetic, why a general-purpose comparison should usually not distinguish it, and how you would document the choice in a shared helper.
Own the consistency argument: a codebase that mixes ===, Object.is and ad-hoc deep comparisons has several definitions of sameness, and you should be able to say where one shared comparison belongs and what it must promise about NaN, -0 and object identity.
## What Object.is is `Object.is(a, b)` is a static method added in ES2015 that exposes the specification's *SameValue* algorithm. SameValue is defined almost identically to strict equality: same type or the answer is `false`, strings by contents, objects by reference, no conversion anywhere. It differs on exactly two number cases, and those two cases are the whole reason the method exists. ```js Object.is('a', 'a'); // true Object.is(1, '1'); // false — no conversion, just like === Object.is({}, {}); // false — still reference comparison Object.is(NaN, NaN); // true <-- differs from === Object.is(-0, 0); // false <-- differs from === ``` ## Divergence one: NaN `NaN` is the numeric result that has no meaningful value — `0/0`, `Number('abc')`, `parseInt('x')`. Floating-point arithmetic defines it as unequal to every value including itself, and `===` faithfully implements that, so `NaN === NaN` is `false`. That is correct as arithmetic and useless as an identity test: if you hold a value and want to know whether it is that same not-a-number, `===` cannot tell you. SameValue takes the identity view instead: two `NaN` values *are* the same value, so `Object.is(NaN, NaN)` is `true`. That gives you a one-call membership-style test, `Object.is(x, NaN)`, which is true for exactly the values `NaN === NaN`-style comparisons can never catch. ## Divergence two: negative zero JavaScript numbers carry a sign bit independent of magnitude, so `-0` and `0` are distinct values that arithmetic really produces: `-1 * 0`, `0 * -5`, `Math.round(-0.2)`. Strict equality ignores that distinction on purpose, because for ordinary numeric work `-0` and `0` should behave the same, and printing them mostly hides the difference (`String(-0)` is `"0"`). SameValue keeps them apart: `Object.is(-0, 0)` is `false` and `Object.is(-0, -0)` is `true`. That is the tool you want when the sign carries meaning — a direction, a delta, a formatted output where `-0` would look like a bug to a user. ## Writing it yourself A classic exercise is to implement `Object.is` with only `===` and arithmetic, which shows exactly where the two divergences live: ```js function sameValue(x, y) { if (x === y) { // === already said equal; the only false positive is +0 vs -0. // 1/0 is Infinity while 1/-0 is -Infinity, so division separates them. return x !== 0 || 1 / x === 1 / y; } // === said not equal; the only false negative is NaN vs NaN, // and NaN is the one value that is not equal to itself. return x !== x && y !== y; } ``` Read that as a two-line summary of the whole topic: strict equality is too generous about zeros and too strict about `NaN`, and SameValue corrects both. ## What Object.is is not It is not a deep or structural comparison. `Object.is([1], [1])` is `false`, exactly like `===`, because both are reference comparisons of two distinct arrays. Candidates who describe `Object.is` as "the thorough equality check" have generalised from the name; it is thorough only about primitive value identity. It is also not a replacement for `===` in normal code. It is a function call rather than an operator, it reads worse in a condition, and its two differences are surprises when you did not want them: comparing a computed delta to `0` with `Object.is` will report a difference for `-0`, which is almost never what a caller meant. The rule of thumb is: `===` by default; `Object.is` when the point of the line is `NaN` identity or the sign of zero. ## Where the language uses SameValue internally SameValue is not only exposed through `Object.is` — the specification uses it in property machinery. When you redefine a property with `Object.defineProperty`, a non-configurable, non-writable data property can only be "redefined" with a value that is the same value; otherwise the call throws. That check is SameValue, not strict equality, so redefining such a property from `0` to `-0` is rejected while a redefinition to the identical value is allowed. ## The bigger picture JavaScript ends up with four sameness comparisons: loose equality, strict equality, SameValue (`Object.is`) and SameValueZero (used by `Array.prototype.includes`, `Set` membership and `Map` keys). SameValue and SameValueZero differ only in the zero case — SameValueZero treats `-0` and `0` as the same while still matching `NaN` with `NaN`. Knowing that `Object.is` sits at the strictest end of that ladder is usually enough to answer any variant of the question.
- If Object.is were not available, how would you detect a negative zero?Divide into it: `1 / x === -Infinity` is true for `-0` and false for `+0`, because `1 / 0` is `Infinity`. A compact test is `x === 0 && 1 / x < 0`. Comparing with `===` alone cannot work, since `-0 === 0` is true, and `String(-0)` is `"0"`, so printing hides it too.
- Should Object.is replace === as a codebase's default comparison?No. `===` is the operator readers expect, it reads naturally in conditions, and its behaviour is what most numeric code wants. `Object.is` differs on exactly two values, and one of those differences — treating `-0` as distinct from `0` — is usually an unwanted surprise in a general comparison. Reach for it when `NaN` identity or zero sign is the actual point.
- Does the language itself use SameValue anywhere besides Object.is?Yes, in property definition. When `Object.defineProperty` redefines a non-configurable, non-writable data property, the call is only allowed if the new value is the same value as the current one, and that check uses SameValue rather than strict equality — so a redefinition from `0` to `-0` is rejected.
saying these in an interview costs you the question
- Calls Object.is a deep or structural equality check
- Says Object.is is just an alias for ===
- Claims Object.is(-0, 0) returns true
- Advises replacing every === with Object.is
- Thinks Object.is coerces types like ==