Custom typeof
beginnerReimplement typeof with Object.prototype.toString and learn why typeof null === 'object'.
Implement Object.is in JavaScript, understand how it differs from === for NaN and -0, and know when React's dependency comparison relies on it.
Implement
Object.is(a, b)without usingObject.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 NaN | false | true |
+0 vs -0 | true | false |
| Everything else | identical | identical |
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.
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;
}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!)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.-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.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.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.===. 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.x !== 0 as the whole zero check and forgetting the 1/x === 1/y comparison (returns false for is(0, 0)).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.Object.is is "a stricter ===" — it's different, not stricter: it's stricter for -0 but looser for NaN.console.log(
Object.is(NaN, NaN),
NaN === NaN,
Object.is(0, -0),
0 === -0
);Map/Set use?" — SameValueZero: NaN matches NaN (so set.has(NaN) works), but +0 and -0 collide.Object.is for hook deps and state bailout; shallow comparison of props in React.memo uses Object.is per key.Number.isNaN." — (x) => x !== x — same trick, smaller question.-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.==, 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'.
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.
Walk the prototype chain by hand, then meet Symbol.hasInstance — and learn where instanceof lies to you.