beginnerRare4 min read · Updated Jul 18, 2026

Implement Object.is from Scratch

Implement Object.is in JavaScript, understand how it differs from === for NaN and -0, and know when React's dependency comparison relies on it.


The problem

Implement Object.is(a, b) without using Object.is.

The implementation is four lines. The question is really a trivia probe into JavaScript's three equality notions — ==, ===, and SameValue — and whether you know the two exact places where === and Object.is disagree:

Comparison===Object.is
NaN vs NaNfalsetrue
+0 vs -0truefalse
Everything elseidenticalidentical

Where you see it in production

This isn't an academic curiosity — React uses Object.is to compare hook dependencies and state updates (useEffect deps, useState bailouts). That's why NaN in a dependency array doesn't cause an infinite re-render loop, and it's the strongest real-world hook for this question. Map and Set key equality (SameValueZero) is the close cousin: like Object.is for NaN, but treats +0 and -0 as equal.

Implementation

function is(x, y) {
  if (x === y) {
    // x === y says +0 and -0 are equal — distinguish them.
    // 1/+0 === Infinity, 1/-0 === -Infinity, so compare the signs
    // via division. For any nonzero x, this branch returns true.
    return x !== 0 || 1 / x === 1 / y;
  }
  // x !== x is true only for NaN — the one value not equal to itself.
  return x !== x && y !== y;
}

Dry run

is(25, 25);        // 25 === 25 → x !== 0 → true
is(+0, -0);        // 0 === -0 → x is 0 → 1/0 (Inf) === 1/-0 (-Inf)? → false
is(-0, -0);        // → 1/-0 === 1/-0 → -Inf === -Inf → true
is(NaN, NaN);      // NaN === NaN false → NaN !== NaN && NaN !== NaN → true
is(NaN, 5);        // false → x !== x true, but y !== y false → false
is({}, {});        // different references → false (no structural equality!)

The trivia behind the trick

  • Why is NaN !== NaN? IEEE 754 defines it that way: NaN means "invalid result," and two invalid results aren't the same quantity. x !== x is therefore the classic NaN test — it's exactly how Number.isNaN can be polyfilled.
  • Why does -0 even exist? IEEE 754 signed zeros preserve the sign of an underflowed value. -0 behaves like 0 almost everywhere — except 1 / -0 === -Infinity, which is precisely the observable difference the implementation exploits.
  • Why not Math.sign? Math.sign(+0) and Math.sign(-0) return +0 and -0 — you'd be back where you started. Division is the standard trick.

Edge cases interviewers probe

  • is(0, -0)false, but is(0, +0)true.
  • is("", ""), is(null, null), is(undefined, undefined) → all true — the zero-check x !== 0 only diverts actual numeric zero; every other equal pair short-circuits to true.
  • Objects compare by reference, same as ===. If the interviewer wants structural comparison, that's a different question: deep equal.
  • is(-0, 0 * -1)true-0 arises from real arithmetic, not just literals.

Common mistakes

  • Writing x !== 0 as the whole zero check and forgetting the 1/x === 1/y comparison (returns false for is(0, 0)).
  • Using isNaN(x) instead of x !== x — global isNaN coerces (isNaN("foo") is true), which would make is("foo", "bar") return... well, test it and find out. Number.isNaN or self-inequality are the safe forms.
  • Claiming Object.is is "a stricter ===" — it's different, not stricter: it's stricter for -0 but looser for NaN.

Test yourself

Predict the outputWhat does this code print?
console.log(
  Object.is(NaN, NaN),
  NaN === NaN,
  Object.is(0, -0),
  0 === -0
);

Follow-up questions

  • "Which equality do Map/Set use?" — SameValueZero: NaN matches NaN (so set.has(NaN) works), but +0 and -0 collide.
  • "Where does React use this?"Object.is for hook deps and state bailout; shallow comparison of props in React.memo uses Object.is per key.
  • "Polyfill Number.isNaN."(x) => x !== x — same trick, smaller question.
  • "When would -0 actually bite someone?" — formatting ((-0).toString() is "0" but JSON.stringify(-0) is "0" while String(-0) hides the sign), chart axes flipping on 1/x, and sort comparators returning -0.
  • "Four equality algorithms in the spec — name them." — loose ==, strict ===, SameValue (Object.is), SameValueZero (Map/Set/includes). Bonus points for knowing Array.prototype.indexOf uses === but includes uses SameValueZero, so only includes finds NaN.

  • Reimplement typeof with Object.prototype.toString and learn why typeof null === 'object'.

  • Object.assign

    intermediate

    Copy enumerable own properties across sources, with getter evaluation and null-target errors.

  • Serialize values by hand: undefined, functions, cycles, and all the special cases JSON defines.

  • Custom instanceof

    intermediate

    Walk the prototype chain by hand, then meet Symbol.hasInstance — and learn where instanceof lies to you.