Performance8 min read · Updated 2025-06-01

React Performance: useMemo, useCallback & React.memo Explained

Learn when and why to use React.memo, useMemo, and useCallback. Understand referential equality, spot unnecessary re-renders, and avoid the over-memoization trap.

💡 Practice these concepts interactively with AI feedback

Start Practicing →

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).

📚 Practice These Topics
UseCallback
4–8 questions
Rendering & Performance
4–8 questions
UseMemo
4–8 questions

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Deep Dive
We Built a RAG-Powered AI Question Engine Into a JavaScript Interview Platform — Here's Exactly How It Works
12 min read
Build Systems
Monorepo with Turborepo vs Nx: The Complete Comparison (2025)
9 min read
Core Concepts
map() vs forEach() in JavaScript: Which One to Use and Why It Matters
7 min read