Object comparison in JavaScript is a strategy decision: === checks identity, shallow checks fit flat data, and deep checks fit nested data. Learn when each approach is enough, where JSON.stringify breaks, and which edge cases need a real deep-equality helper.
Frontend interview practice question
How would you compare two objects in JavaScript?
Interview quick answer
Interview focus
This JavaScript interview question tests whether you can explain compare two objects in JavaScript: shallow vs deep vs JSON pitfalls, connect it to production trade-offs, and handle common follow-up questions.
- compare two objects in JavaScript: shallow vs deep vs JSON pitfalls explanation without falling back to memorized definitions
- Objects and Equality reasoning, edge cases, and production failure modes
- How you would answer the most likely JavaScript interview follow-up
Use this JavaScript interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.
Full interview answer
How to compare two objects in JavaScript
Use === (or Object.is) when you mean the same object reference. For two separate objects, compare their own top-level keys and values when the data is flat, or use a tested deep-equality implementation when nested values must match. JSON.stringify(a) === JSON.stringify(b) is not a general deep-comparison solution.
Choose the cheapest correct strategy for the contract:
- Identity comparison: do both variables point to the same object?
- Shallow value comparison: do the relevant top-level keys and values match?
- Deep value comparison: do supported nested objects and arrays match recursively?
const a = { id: 1, profile: { city: 'Berlin' } };
const b = { id: 1, profile: { city: 'Berlin' } };
const c = a;
console.log(a === b); // false (different references)
console.log(a === c); // true (same reference)
Approach | What it checks | Best use case | Main risk |
|---|---|---|---|
| Reference identity only | React memoization, state identity checks | Returns false for equal-looking but separate objects |
Shallow compare | Top-level keys + values | Flat config objects, props optimization | Misses differences in nested objects |
Deep compare | Recursive structural equality | Validation, tests, cache keys, diffing | Higher CPU cost and edge-case complexity |
Shallow object comparison in JavaScript
Use a shallow comparison for plain records when the relevant fields are own enumerable string keys and each value is primitive or intentionally compared by reference. The helper below compares top-level values with Object.is; it does not recurse into nested objects.
function shallowEqual(obj1, obj2) {
if (obj1 === obj2) return true;
if (!obj1 || !obj2 || typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (const key of keys1) {
if (!Object.prototype.hasOwnProperty.call(obj2, key)) return false;
if (!Object.is(obj1[key], obj2[key])) return false;
}
return true;
}
When shallow equality is enough
If a filter bar stores only flat primitive fields such as page, sort, and query, a shallow comparison is cheaper and easier to reason about than a recursive deep comparison.
const prevFilters = { page: 1, sort: 'price', query: 'gpu' };
const nextFilters = { page: 1, sort: 'price', query: 'gpu' };
console.log(shallowEqual(prevFilters, nextFilters)); // true
// Good enough for a refetch guard or memo check on flat primitive fields.
Deep object comparison for nested values
Use a scoped recursive comparison when nested arrays and ordinary objects must match by value. The example below supports arrays, Date, RegExp, cycles, and shared-reference topology. It intentionally does not define equality for Map, Set, typed arrays, custom class instances, symbol keys, or non-enumerable properties; production code should make those rules explicit or use a library that matches its data contract.
function deepEqual(a, b, seenA = new WeakMap(), seenB = new WeakMap()) {
if (Object.is(a, b)) return true;
if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
return false;
}
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) return false;
if (a instanceof Date) return Object.is(a.getTime(), b.getTime());
if (a instanceof RegExp) {
return a.source === b.source && a.flags === b.flags && a.lastIndex === b.lastIndex;
}
const aIsArray = Array.isArray(a);
const bIsArray = Array.isArray(b);
if (aIsArray !== bIsArray) return false;
if (!aIsArray) {
const prototype = Object.getPrototypeOf(a);
if (prototype !== Object.prototype && prototype !== null) return false;
}
if (seenA.has(a) || seenB.has(b)) {
return seenA.get(a) === b && seenB.get(b) === a;
}
seenA.set(a, b);
seenB.set(b, a);
if (aIsArray && a.length !== b.length) return false;
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (const key of keysA) {
if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
if (!deepEqual(a[key], b[key], seenA, seenB)) return false;
}
return true;
}
const x = { id: 1, meta: { tags: ['js', 'interview'] } };
const y = { id: 1, meta: { tags: ['js', 'interview'] } };
console.log(shallowEqual(x, y)); // false (nested object references differ)
console.log(deepEqual(x, y)); // true (same nested values)
Can you compare JavaScript objects with JSON.stringify?
Only for a deliberately narrow JSON-data contract. The shortcut can produce the wrong result because:
- Equal key/value sets can serialize differently when their insertion order differs.
- Object properties whose values are
undefined, functions, or symbols are omitted.
NaNand infinities serialize asnull, whileBigIntand circular references throw.
Datebecomes a string, andMap,Set, class instances, or customtoJSON()methods need explicit semantics.
const q1 = { sort: 'price', page: 1 };
const q2 = { page: 1, sort: 'price' };
console.log(JSON.stringify(q1) === JSON.stringify(q2)); // false
const p = { a: 1, b: undefined };
const q = { a: 1 };
console.log(JSON.stringify(p) === JSON.stringify(q)); // true
Object comparison edge cases
- Dates usually need value comparison such as
getTime().
- Functions usually compare by identity, not by source text.
- Cycles and repeated references require consistent pair tracking, not just a visited-object set.
- Maps, sets, typed arrays, prototypes, property descriptors, symbol keys, and non-enumerable properties need an explicit equality contract.
Which object comparison strategy should you use?
- Use
===for object identity checks; it is cheap and exact.
- Use shallow comparison for flat records and render optimizations where nested identity is intentional.
- Use deep comparison only after defining supported types and whether repeated-reference topology matters. Choose a proven library whose contract matches those requirements; for example,
fast-deep-equalis useful when cyclic graphs are out of scope.
- Test the values your contract supports, including
NaN, dates, arrays, functions, cycles, and repeated references.
Example: comparing filter objects
A product filter panel compares previous and next query objects to decide whether to refetch results. A reference check treats separately created but equal-by-value objects as different, while an unconditional deep comparison can add work on every keystroke. For flat filter state, stable object creation plus a targeted shallow comparison is usually the clearer contract.
Common pitfalls
- Using
===when the requirement is value equality.
- Using
JSON.stringifywhere insertion order, omitted values, special types, or cycles matter.
- Deep-comparing large objects in hot render paths without measuring the cost.
Practice the decision
Implement recursive comparison in the JavaScript deep-equality challenge, distinguish equality from copying with shallow versus deep copy, or apply state and data-shape decisions in frontend machine-coding practice.
Use this as one explanation rep, then continue with the JavaScript interview questions cluster or a guided prep path.