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.
Use this JavaScript interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.
How would you compare two objects in JavaScript?Frontend interview answer
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 docs wording
- Objects and Equality reasoning, edge cases, and production failure modes
- How you would answer the most likely JavaScript interview follow-up
The core idea
Object comparison in JavaScript is not one single operation. You choose the cheapest comparison strategy that matches your intent:
- Identity comparison: are both variables pointing to the exact same object?
- Shallow value comparison: are top-level keys and values equal?
- Deep value comparison: are nested objects/arrays equal by content?
Most interview mistakes happen when people use === but expect deep value equality, or when they deep-compare everything even though a flat shallow check would have been enough.
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 comparison helper
Use shallow compare when object values are primitives or when nested objects are intentionally compared by reference.
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;
}
Business case: shallow is often enough
If a filter bar stores only flat primitive fields like page, sort, and query, a shallow comparison is cheaper and easier to reason about than a recursive deep compare.
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 comparison helper (handles cycles)
For nested data, deep comparison is safer. A robust helper should handle arrays, dates, and circular references.
function deepEqual(a, b, seen = new WeakMap()) {
if (Object.is(a, b)) return true;
if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
return false;
}
if (a.constructor !== b.constructor) return false;
if (a instanceof Date) return a.getTime() === b.getTime();
if (a instanceof RegExp) return String(a) === String(b);
if (seen.get(a) === b) return true;
seen.set(a, b);
if (Array.isArray(a)) {
if (!Array.isArray(b) || a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (!deepEqual(a[i], b[i], seen)) return false;
}
return true;
}
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], seen)) 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)
Why JSON.stringify is not a universal solution
The stringify trick is fast to write, but it breaks in real systems:
- Key order can differ between objects with same logical meaning.
undefined, functions, and symbols are dropped.
- Circular references throw errors.
Date,Map,Set, and class instances need special handling.
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
Follow-up limits
- Dates usually need value comparison such as
getTime(). - Functions usually compare by identity, not by source text.
- Cycles require visited-pair tracking or a proven library.
Production guidance
- Use
===for identity checks (cheap and exact).
- Use shallow compare for flat objects and render optimization.
- Use a proven library (for example
fast-deep-equal) when deep equality must be reliable and fast.
- In tests, include edge cases:
NaN, dates, arrays, functions, and circular references.
Practical scenario
A product filter panel compares previous and next query objects to decide whether to refetch results. A strict reference check misses equal-by-value objects, while naive deep checks hurt performance on every keystroke.
Common pitfalls
- Using
===when your intent is value equality. - Using
JSON.stringifywhere key order,undefined, or cycles matter. - Running deep comparison on large objects inside hot render loops instead of using the cheapest strategy that fits the business intent.
Choose comparison depth by use case, then benchmark. In UI code, prefer stable object creation plus targeted shallow checks; in validation/tests, use robust deep equality.
Use this as one explanation rep, then continue with the JavaScript interview questions cluster or a guided prep path.