- React
- Tricky React Output Prediction & Execution Ordering
- Output Traps: React.memo & Referential Identity Pitfalls
Output Traps: React.memo & Referential Identity Pitfalls
React.memo uses shallow Object.is comparison on props. Passing inline object literals, arrays, or anonymous functions creates a new memory reference on every render, bypassing memoization. Preserving referential stability requires useMemo, useCallback, or hoisting constants.
Key Learning Objectives
Predict whether a React.memo component re-renders when parent state changes.
Explain why inline objects `{ style: { color } }` and callbacks `() => {}` break shallow equality.
Analyze the shallow comparison algorithm used by React.memo.
Apply useMemo and useCallback to restore referential stability.
The Interview Problem & Context
Predict the exact output when testing a React.memo component receiving an unstable inline object versus a referentially stable memoized object.
1// Simulating React.memo shallow comparison behavior2function testMemoBehavior() {3 const events: string[] = [];45 // Pass 1: Parent renders and passes inline object { theme: 'dark' }6 const prevProps1 = { theme: 'dark' };7 const nextProps1 = { theme: 'dark' }; // New object reference in memory!8 const isShallowEqual1 = Object.is(prevProps1, nextProps1); // false!910 if (!isShallowEqual1) {11 events.push('memo-bypassed'); // Re-renders despite identical contents!12 } else {13 events.push('memo-retained');14 }1516 // Pass 2: Parent passes referentially stable constant or useMemo result17 const stableObject = { theme: 'dark' };18 const prevProps2 = stableObject;19 const nextProps2 = stableObject; // Same pointer reference!20 const isShallowEqual2 = Object.is(prevProps2, nextProps2); // true!2122 if (!isShallowEqual2) {23 events.push('memo-bypassed');24 } else {25 events.push('memo-retained'); // Skips re-render!26 }2728 return events.join(' ');29}3031console.log(testMemoBehavior());
Predict Console & Render Output
memo-bypassed memo-retained
memo-retained memo-retained
memo-bypassed memo-bypassed
memo-retained memo-bypassed
Execution Trace
Step 1 of 2 (Line 12)Distinct object references fail shallow comparison, triggering component re-render.
Deep Architectural Explanation
The Reference Identity Trap
By default, React.memo compares each prop from prevProps and nextProps using shallow comparison (Object.is):
function arePropsEqual(prevProps, nextProps) {
// Compares keys shallowly: prevProps[key] === nextProps[key]
}If you write:
<MemoizedChild user={{ name: 'Alice' }} onClick={() => save()} />Every time the parent renders:
{ name: 'Alice' }is a brand-new object allocated in memory.() => save()is a brand-new function pointer allocated in memory.Object.is(oldObj, newObj)is false.React.memoimmediately invalidates and re-renders the child! The memoization was 100% wasted computation.
Solution:
- Wrap objects in
useMemo. - Wrap callbacks in
useCallback. - Hoist non-state constants outside the component body.
Common Interview Traps & Pitfalls
Wrapping a component in React.memo while continuing to pass inline arrow functions and inline objects in props.
Believing React.memo performs deep equality checks on nested objects.
Using custom comparator functions that perform slow recursive deep-clones.
Senior Interviewer Follow-Up Probes
How does the React Compiler automate memoization without manual useMemo and useCallback?
When is the memory and CPU overhead of React.memo greater than the cost of re-rendering?
How can you verify with React DevTools whether a memoized component skipped re-rendering?
Frequently Asked Interview Questions
Why do inline arrow functions break React.memo?
Every time the parent component renders, JavaScript creates a new function instance in memory with a different reference, causing shallow comparison to return false.
How do you preserve referential stability for functions?
Wrap the callback in useCallback with an appropriate dependency array, or hoist the function outside the component if it does not depend on component state or props.
Can you provide a custom comparison function to React.memo?
Yes. Pass a custom comparator as the second argument: React.memo(MyComponent, (prevProps, nextProps) => boolean). Note that returning true skips rendering.
Why does passing children prop break React.memo?
JSX passed as children (<Comp><span>Hello</span></Comp>) compiles to React.createElement, creating a new element object reference on every parent render.
What is the performance cost of React.memo?
React.memo incurs a memory cost to retain previous props and CPU cost to iterate and compare prop keys. For lightweight components, this check can be slower than a fast re-render.
