React Performance: useMemo, useCallback & React.memo
Three tools, one root problem: referential equality.
The Root Problem: JavaScript Equality
const a = { x: 1 }
const b = { x: 1 }
a === b // false — different objects in memory
// Same for functions: (() => {}) === (() => {}) // false — two different function objects
Every render in React creates new function and object literals. When passed as props, they look "new" to a child component even if the values are identical — causing unnecessary re-renders.
React.memo: Skip Re-rendering Unchanged Components
React.memo(Component) wraps a component. React will skip re-rendering it if props haven't changed (shallow comparison).
const ExpensiveList = React.memo(function List({ items, onSelect }) {
return items.map(item => (
<ListItem key={item.id} item={item} onSelect={onSelect} />
))
})
function Parent() { const [query, setQuery] = useState('') const [items, setItems] = useState([...])
// ❌ New function reference on every render — memo is useless return <ExpensiveList items={items} onSelect={(id) => select(id)} /> }
React.memo only helps if the parent passes stable prop references. For objects and functions, that requires useMemo and useCallback.
useCallback: Stable Function References
useCallback(fn, deps) returns the same function reference between renders, only creating a new one when deps change.
function Parent() {
const [items, setItems] = useState([...])
// ✅ Same function reference unless items changes const handleSelect = useCallback((id) => { setItems(prev => prev.filter(i => i.id !== id)) }, []) // no deps — uses functional update, doesn't need items in closure
return <ExpensiveList items={items} onSelect={handleSelect} /> }
Without useCallback, ExpensiveList re-renders on every parent render despite React.memo.
useMemo: Memoize Expensive Computed Values
useMemo(() => computation(), deps) caches the result of an expensive computation.
function ProductList({ products, filterText }) {
// ✅ Only recalculates when products or filterText changes
const filtered = useMemo(
() => products.filter(p => p.name.toLowerCase().includes(filterText)),
[products, filterText]
)
return filtered.map(p => <Product key={p.id} product={p} />) }
Also use useMemo to stabilize object references passed as props or context values:
// ❌ New object on every render — breaks all consumers' memoization <UserContext.Provider value={{ user, setUser }}>
// ✅ Stable reference — only changes when user or setUser changes const value = useMemo(() => ({ user, setUser }), [user, setUser]) <UserContext.Provider value={value}>
When to Actually Use Them
Most components don't need memoization. React is fast by default.
Use React.memo when:
- Component renders often AND its parent re-renders for unrelated reasons
- Component is visually complex or has expensive child trees
Use useMemo when:
- The computation is genuinely expensive (filter/sort thousands of items)
- You need a stable object/array reference for another hook's deps or child props
Use useCallback when:
- You're passing a function to a React.memo'd child
- A function is in a useEffect's dependency array
Do NOT use them for:
- Simple computations — memoization has overhead too
- Every component by default — profile first
- Primitive values —
===already works correctly for numbers and strings
The Complete Pattern
const ExpensiveList = React.memo(({ items, onSelect }) => {
return items.map(item => <Item key={item.id} item={item} onSelect={onSelect} />)
})
function Dashboard({ data }) { const [query, setQuery] = useState('')
// Memoized computation const filtered = useMemo( () => data.filter(d => d.name.includes(query)), [data, query] )
// Stable function reference for memo'd child const handleSelect = useCallback((id) => { console.log('selected', id) }, [])
return ( <> <input onChange={e => setQuery(e.target.value)} /> <ExpensiveList items={filtered} onSelect={handleSelect} /> </> ) }
Interview tip: always say "profile first, optimize second." Reaching for useMemo before identifying a problem signals premature optimization.
Practice performance questions at [JSPrep Pro](/auth).