JavaScript interview question
What is the difference between == and ===, and how do you compare objects in tests?
Short answer
=== compares without type conversion; == applies type coercion first, giving surprises like 0 == '' being true. Both compare objects by reference, not content, so two identical objects are not equal. In tests use deep equality such as toEqual or toStrictEqual, and use Object.is semantics for NaN and -0.
Answer
=== (strict equality) returns true only if both operands have the same type and the same value; it never converts types. == (loose equality) first coerces the operands to a common type using a set of rules that produce surprising results. Use === everywhere, with the one common, deliberate exception x == null, which checks for both null and undefined.
Coercion surprises with ==
0 == ''; // true ('' converts to 0)
0 == '0'; // true
'' == '0'; // false (both strings, compared directly)
false == '0'; // true (both become 0)
null == undefined; // true
null == 0; // false (null only loosely equals undefined)
[1] == 1; // true ([1] -> '1' -> 1)
Note that == is not even transitive (0 == '' and 0 == '0', but '' != '0'). That is reason enough to avoid it.
Objects are compared by reference
Both operators compare objects, arrays and functions by identity, not content:
const a = { id: 1 };
const b = { id: 1 };
a === b; // false: two different objects
a === a; // true
[1, 2] === [1, 2]; // false
This is the source of a common test bug:
// Wrong: compares references, always fails for a freshly parsed body
expect(await response.json()).toBe({ id: 1 });
// Right: deep equality
expect(await response.json()).toEqual({ id: 1 });
Deep equality in test frameworks
- Jest / Vitest / Playwright
expect:toBeusesObject.is(identity for objects).toEqualrecursively compares content and ignores properties whose value isundefined.toStrictEqualalso checksundefinedproperties, sparse arrays and class types. - Partial matching:
toMatchObject({ status: 'paid' })orexpect.objectContaining(...)to assert only the fields you care about, which keeps API tests resilient to new fields. - Node:
assert.deepStrictEqual(a, b)orutil.isDeepStrictEqual(a, b).
NaN and -0
NaN is the only value not equal to itself, even with ===:
NaN === NaN; // false
Number.isNaN(NaN); // true
Object.is(NaN, NaN); // true
Object.is(0, -0); // false, whereas 0 === -0 is true
Object.is is what toBe uses, which is why expect(NaN).toBe(NaN) passes.
What interviewers look for
- Strict versus loose with at least one concrete coercion example.
- Reference equality for objects, and the
toBeversustoEqualconsequence in tests. - Awareness of partial matchers for API assertions.
- Bonus:
NaN,Object.is, and the== nullidiom.
A strong answer covers
- === is strict equality with no type conversion
- == performs type coercion, with examples of surprises
- Objects and arrays are compared by reference
- Uses deep equality in tests (toEqual, toStrictEqual, deepStrictEqual)
- Mentions NaN and Object.is edge cases