Intermediate0 questionsFull Guide

useMemo Hook — Complete React Interview Guide

Learn React useMemo with real examples, performance optimization tips, and differences with useCallback. Master memoization and avoid unnecessary re-renders.

The Mental Model

Picture a calculator with a memory button. You punch in a complicated calculation, get the result, and press M+ to store it. Next time someone asks for that same calculation with the same inputs, you just recall the memory — no recalculating needed. But if the inputs change, the memory is stale and you recalculate from scratch. useMemo is that memory button for React components. It stores the result of a computation and returns the stored result on subsequent renders — as long as the dependencies haven't changed. When a dependency changes, the computation runs again and the new result is stored. But here is the thing most developers miss: the memory button has a cost. Every time the component renders, React still checks whether the dependencies changed. That check costs something. For simple computations that are cheaper than a comparison, adding useMemo actually makes things slower, not faster. useMemo has a second, equally important purpose beyond performance: referential stability. When you create an object or array inside a component, you get a brand new reference every render, even if the contents are identical. useMemo lets you return the same reference when inputs haven't changed — which is what makes React.memo and useEffect dependency comparisons work correctly.

The Explanation

What useMemo does

useMemo memoizes the result of a computation. The function you pass is only called when the dependencies change. Between dependency changes, React returns the cached result.

const memoizedValue = useMemo(() => {
  return expensiveComputation(a, b)
}, [a, b])
 
// First render: calls expensiveComputation(a, b), stores result
// Re-render, same a and b: returns cached result immediately
// Re-render, a changed: calls expensiveComputation(newA, b), stores new result

Use case 1 — Expensive computations

// Without useMemo — re-sorts on every render, even unrelated renders
function ProductList({ products, sortBy, searchQuery }) {
  const [count, setCount] = useState(0)  // unrelated state
 
  // This runs even when count changes — wasteful for 10,000 products
  const sorted = [...products].sort((a, b) =>
    sortBy === 'price' ? a.price - b.price : a.name.localeCompare(b.name)
  )
 
  return <ul>{sorted.map(p => <Item key={p.id} product={p} />)}</ul>
}
 
// With useMemo — only re-sorts when products or sortBy changes
function ProductList({ products, sortBy, searchQuery }) {
  const [count, setCount] = useState(0)
 
  const sorted = useMemo(() => {
    console.log('sorting...') // only logs when products or sortBy changes
    return [...products].sort((a, b) =>
      sortBy === 'price' ? a.price - b.price : a.name.localeCompare(b.name)
    )
  }, [products, sortBy])
 
  return <ul>{sorted.map(p => <Item key={p.id} product={p} />)}</ul>
}

Use case 2 — Referential stability for React.memo

React.memo does a shallow comparison of props. If you pass an object or array created inline, the reference changes every render — React.memo can never skip.

const HeavyChart = React.memo(({ config }) => {
  // expensive rendering
  return <Chart {...config} />
})
 
// ✗ Defeats React.memo — new config object every render
function Dashboard() {
  const config = { color: 'blue', type: 'bar', animate: true }
  return <HeavyChart config={config} />  // HeavyChart re-renders always!
}
 
// ✓ Stable reference — React.memo can skip re-renders
function Dashboard() {
  const config = useMemo(() => ({
    color: 'blue', type: 'bar', animate: true
  }), [])  // stable — empty deps, never changes
 
  return <HeavyChart config={config} />  // HeavyChart skips re-renders ✓
}

Use case 3 — Referential stability for useEffect

useEffect uses the same reference comparison for its dependency array. An object created inline changes reference every render — the effect runs every render.

// ✗ Infinite loop — fetchConfig is new every render
function UserDashboard({ userId }) {
  const fetchConfig = { headers: { 'X-User': userId }, timeout: 5000 }
 
  useEffect(() => {
    fetchUserData(userId, fetchConfig)
  }, [userId, fetchConfig])  // fetchConfig is new every render → infinite loop!
}
 
// ✓ Stable reference — effect runs only when userId changes
function UserDashboard({ userId }) {
  const fetchConfig = useMemo(() => ({
    headers: { 'X-User': userId },
    timeout: 5000
  }), [userId])  // new reference only when userId changes
 
  useEffect(() => {
    fetchUserData(userId, fetchConfig)
  }, [userId, fetchConfig])  // ✓ runs only when userId changes
}

The performance trade-off — when NOT to use useMemo

useMemo itself has a cost on every render: allocates memory for the cache, runs the dependency comparison, and retains the old value in memory. For simple computations, the memoization overhead may exceed the computation cost.

// ✗ Over-memoization — useMemo costs MORE than the compute
const fullName = useMemo(() => first + ' ' + last, [first, last])
// String concat is O(n) microseconds. useMemo comparison is O(m) where m = deps.length.
// Net result: slower.
 
// ✓ Compute inline — faster
const fullName = first + ' ' + last
 
// ✗ Memoizing primitive values — pointless
const doubled = useMemo(() => count * 2, [count])
// Numbers are compared by value — React already handles this efficiently
 
// ✓ Worth memoizing
const chartData = useMemo(() => {
  return rawData
    .filter(d => d.date >= startDate && d.date <= endDate)
    .map(d => ({ x: d.date, y: d.value }))
    .reduce(/* ... aggregation ... */)
}, [rawData, startDate, endDate])  // O(n) with significant constant — worth caching

useMemo vs useCallback — the key distinction

// useMemo — memoizes the RESULT of calling a function
const processedData = useMemo(() => expensiveTransform(rawData), [rawData])
// processedData is a value (array, object, number, etc.)
 
// useCallback — memoizes the FUNCTION ITSELF
const handleChange = useCallback(e => setValue(e.target.value), [])
// handleChange is a stable function reference
 
// They are literally equivalent:
useCallback(fn, deps) === useMemo(() => fn, deps)
 
// You could implement useCallback with useMemo:
const stableFn = useMemo(() => (...args) => doSomething(...args), [dep])

Referential equality — why it matters

// JavaScript compares objects by reference, not content
{ a: 1 } === { a: 1 }   // false — different objects in memory
[1, 2, 3] === [1, 2, 3] // false — different arrays
() => {} === () => {}    // false — different functions
 
// React.memo and useEffect deps use Object.is — same as ===
// This is why inline objects/arrays always "change" between renders

The React Compiler — the future of memoization

The React Compiler (shipping in React 19) automatically applies memoization at the compiler level. It analyses your component code and inserts useMemo/useCallback calls where they would be beneficial — without you writing them manually.

// What you write (no memoization):
function ProductList({ products, sortBy }) {
  const sorted = products.slice().sort((a, b) => a[sortBy] - b[sortBy])
  return <ul>{sorted.map(p => <Item key={p.id} product={p} />)}</ul>
}
 
// What React Compiler produces (conceptually):
function ProductList({ products, sortBy }) {
  const sorted = useMemo(() => products.slice().sort((a, b) => a[sortBy] - b[sortBy]), [products, sortBy])
  return <ul>{sorted.map(p => <Item key={p.id} product={p} />)}</ul>
}

With the React Compiler enabled, manually written useMemo calls become largely unnecessary. This is why the advice is: write clean code first, add useMemo only where profiling confirms a bottleneck — the compiler will handle the rest.

Common Misconceptions

⚠️

Many developers think useMemo always makes components faster — but it adds overhead on every render (comparison + cache lookup). For trivial computations, useMemo makes things slower. It's only beneficial when the memoized computation cost clearly exceeds the memoization overhead — which requires profiling to confirm.

⚠️

Many developers think useMemo is only for expensive computations — but its second major use is referential stability: returning the same object/array/function reference when inputs haven't changed. This is what makes React.memo comparisons and useEffect dependency arrays work correctly for non-primitive values.

⚠️

Many developers think useMemo(fn, []) caches forever — it does for the lifetime of that component instance. But when the component unmounts and remounts, the cache is cleared and the computation runs fresh. Two different mounted instances of the same component each have their own independent memo cache.

⚠️

Many developers think useMemo prevents the wrapped function from running on the first render — but useMemo always runs the function on the first render to compute the initial value. There is no "skip first render" option. Memoization only kicks in from the second render onward.

⚠️

Many developers think useMemo is equivalent to a selector library like Reselect — Reselect creates a memoized selector that works outside components, can be shared across instances, and has a configurable cache size. useMemo is component-local, per-instance, and has a cache size of 1. For global computed state, use Reselect or Zustand selectors.

⚠️

Many developers think they should add useMemo to every computed value by default — but this is premature optimization. React's rendering is fast. Write code without useMemo first, profile if you see slowness, and add useMemo only where measurements confirm it helps.

Where You'll See This in Real Code

Large data grids at Flipkart and Meesho filter and sort tens of thousands of product rows. Without useMemo, the entire sort runs on every keystroke, every hover, every unrelated state change. With useMemo keyed to the sort column and filter values, the computation runs only when those inputs change — making the grid feel instant.

Chart libraries like Recharts and Chart.js receive config objects as props. Without memoizing the config, the chart re-renders on every parent render even when the underlying data hasn't changed. The chart library can't tell that { color: 'blue' } this render is the same as { color: 'blue' } last render — it only sees a new reference.

Autocomplete and search-as-you-type components use useMemo to compute filtered results from a local dataset. The filter runs on every keystroke through the search term dependency, but not on other unrelated state changes like loading indicators or modal states.

Permission and role-based UI uses useMemo to compute what features a user can access: const permissions = useMemo(() => computePermissions(user.role, user.overrides), [user.role, user.overrides]). This prevents the permission computation from running on every render cycle, and ensures the permissions object has a stable reference for useEffect dependencies.

Design system component libraries (like Radix, shadcn) memoize style objects internally so that components wrapping native HTML elements don't pass new style prop references on every render — which would force browsers to recalculate CSS needlessly.

Interview Cheat Sheet

  • useMemo(() => compute(a, b), [a, b]) — returns cached result, recomputes only when a or b changes
  • Two use cases: expensive computations AND referential stability for objects/arrays/functions
  • useMemo has overhead — comparison check runs on every render regardless of whether deps changed
  • Only memoize when: computation is expensive (profiler-confirmed) OR you need a stable reference
  • Don't memoize: string concat, basic math, primitive values, function calls that run in microseconds
  • React.memo needs stable props — use useMemo to stabilize object/array/function props
  • useEffect deps need stable references — use useMemo to stabilize config objects passed as deps
  • useMemo vs useCallback: useMemo = memoize the result. useCallback = memoize the function itself.
  • useCallback(fn, deps) === useMemo(() => fn, deps) — they are the same underlying mechanism
  • Cache is per-component-instance, size 1 (only remembers last render's values)
  • Unmount clears cache — remounted component recomputes from scratch
  • React Compiler (React 19) auto-memoizes — manual useMemo calls will become largely unnecessary
💡

How to Answer in an Interview

  • 1.When asked about useMemo, always give both use cases: expensive computations and referential stability. Candidates who only mention performance miss half the picture. The referential stability use case — stabilizing objects for React.memo and useEffect — is what separates mid-level from senior-level answers.
  • 2.The "does useMemo always improve performance" trap question is asked at Google and Atlassian. The answer is no — it adds comparison overhead on every render. Use it when the computation cost clearly exceeds the comparison cost, which requires profiling with React DevTools Profiler to confirm.
  • 3.Connect useMemo to a real debugging scenario: "I've had a situation where useEffect was running on every render even though I thought the dependencies weren't changing. The bug was that I was passing a config object as a dependency — new reference every render. Wrapping it in useMemo fixed the infinite loop." This shows you've hit this in production.
  • 4.The useMemo vs useCallback comparison is a common interview question. State it precisely: "useMemo memoizes the result of calling the function. useCallback memoizes the function itself. They're equivalent — useCallback(fn, deps) is sugar for useMemo(() => fn, deps)."
  • 5.Mention the React Compiler when discussing optimization: "In React 19, the compiler auto-memoizes. So the best practice now is to write clean code without manual useMemo, then add it surgically where profiling shows a real bottleneck — because the compiler will handle most of the common cases automatically."

Practice Questions

No questions tagged to this topic yet.

Related Topics

useState Hook — Complete React Interview Guide
Beginner·4–8 Qs
useEffect Hook — Complete React Interview Guide
Beginner·4–8 Qs
useRef Hook — Complete React Interview Guide
Beginner·4–8 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz