intermediate 6 min read
useMemo, useCallback & React.memo Internals
`useMemo` caches the result of an expensive calculation; `useCallback` caches a function definition between renders to preserve referential equality when passed to memoized children.
Why it matters in interviews
Premature or incorrect memoization wastes memory and adds comparison overhead, whereas missing referential stability in props breaks `React.memo` and triggers cascading re-renders across large component trees.
Visual & Interactive Explanation
useMemo vs useCallback Decision Matrix
useMemo
Cache computed return value
ReturnsCalculated result value
SyntaxuseMemo(() => compute(a,b), [a,b])
Primary UseExpensive transformations / Derived objects
Strengths
- •Prevents redundant CPU calculation
- •Preserves object referential equality
Trade-offs
- •Memory overhead for cached value and dependencies
useCallback
Cache function reference
ReturnsThe function itself
SyntaxuseCallback((arg) => doWork(arg), [deps])
Primary UseEvent handlers passed to React.memo children
Strengths
- •Prevents child re-renders with React.memo
- •Stable effect dependency
Trade-offs
- •Does not prevent parent component from re-rendering
React.memo
Component-level shallow diff
WrapsReact Component
ComparisonShallow diffs prevProps vs nextProps
Primary UseLarge leaf components with pure rendering
Strengths
- •Skips entire child render subtree
Trade-offs
- •Fails if any prop reference changes
Code Examples & Implementation
Fixing Broken React.memo with useCallback
import React, { useState, useCallback, memo } from 'react';
// Child component wrapped in memo (only re-renders if props change referentially)
const ExpensiveChart = memo(({ onPointClick }: { onPointClick: (id: string) => void }) => {
console.log('Rendering 1,000 chart nodes...');
return <div onClick={() => onPointClick('point-1')}>Interactive Chart</div>;
});
export function ParentDashboard() {
const [count, setCount] = useState(0);
const [filter, setFilter] = useState('all');
// ✅ Stable callback reference across renders of ParentDashboard
const handlePointClick = useCallback((id: string) => {
console.log('Selected point:', id);
}, []); // Empty deps because it doesn't close over parent state
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Re-render Parent ({count})</button>
{/* ExpensiveChart will NOT re-render when count updates! */}
<ExpensiveChart onPointClick={handlePointClick} />
</div>
);
}Interview-Ready Answers
`useMemo` memoizes a computed value: `useMemo(() => fn(), deps)`. `useCallback` memoizes a callback function: `useCallback(fn, deps)`. `useCallback(fn, deps)` is syntactic sugar for `useMemo(() => fn, deps)`.
Official Documentation & Specifications
Follow-up Questions & Deep Dives
🎯 Code Output — What Does This Print?
Question 1medium
const Child = React.memo(({ onClick }: { onClick: () => void }) => {
console.log("Child render");
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const handleClick = () => console.log("clicked");
console.log("Parent render");
return (
<>
<p>{count}</p>
<Child onClick={handleClick} />
<button onClick={() => setCount((c) => c + 1)}>+</button>
</>
);
}
// User clicks + button. What logs?Question 2hard
function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
const expensiveValue = useMemo(() => {
console.log("computing...");
return count * 2;
}, [count]);
console.log("render:", expensiveValue);
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={() => setCount((c) => c + 1)}>+</button>
</>
);
}
// User types "abc" (3 keystrokes). What logs?Question 3tricky
function App() {
const [count, setCount] = useState(0);
const doubled = useMemo(() => count * 2, [count]);
const handleLog = useCallback(() => {
console.log("doubled:", doubled);
}, []);
return <button onClick={handleLog}>Log (count={count})</button>;
}
// User clicks + twice (count → 2), then clicks Log button.Common Mistakes & Anti-Patterns
- Wrapping trivial calculations (e.g. `useMemo(() => a + b, [a, b])`) where the hook overhead exceeds the computation
- Omitting dependencies causing stale closures where functions operate on old state
- Using useCallback without wrapping the receiving child in React.memo (rendering is not prevented)
Real-World Architectural Scenario
A large analytics grid with 500 rows re-renders whenever a global clock tick updates every second. How do you optimize it?
Rate Your Readiness
Rate how comfortably you can explain this in an interview to update your global readiness gauge:
Rate your confidence: