How Does useMemo Prevent Redundant Expensive Calculations?

Intermediate10 min interview
Skills tested:
Explaining that useMemo caches the factory return value and recomputes only when dependencies change via Object.isUnderstanding the dependency array: what goes in it, what [] means, and what happens with a wrong dependencyKnowing useMemo prevents redundant re-runs but cannot help when the dependency driving the computation changesKnowing when to reach past useMemo — algorithmic improvement, Web Workers, or useTransition for inherently slow workVerifying memoization works with a console.log inside the factory

Advertisement

🧩 Scenario

A FibonacciCalculator component lets users increment a number and compute its Fibonacci value. It also has a theme toggle. Without optimization, every theme toggle re-runs the Fibonacci calculation even though the number has not changed. Walk through how useMemo fixes this and what the dependency array is actually doing.

Architecture Walkthrough

Why Unrelated State Triggers Expensive Recalculation

React's component model means any state change triggers a full re-render of that component's function body. When theme changes from 'light' to 'dark', React calls FibonacciCalculator again from the top. Without useMemo, the fibonacci(num) call runs synchronously during this render even though num has not changed at all.

For small values this is imperceptible, but the naive recursive Fibonacci has O(2^n) time complexity — fibonacci(35) requires over 29 million calls. On a mid-range device this can take 200-500ms, blocking the main thread and making the theme toggle feel like a freeze. useMemo breaks the coupling between the state that changed (theme) and the expensive computation that depends on different state (num).

How the Dependency Array Controls Cache Invalidation

The dependency array is React's mechanism for knowing when to discard the cached value and recompute. React compares each dependency using Object.is between the previous and current render. If all are equal, React returns the cached value without calling the factory. If any changed, React calls the factory and stores the new result.

Listing [num] means the Fibonacci calculation re-runs only when num changes — theme toggles hit the cache. An empty array [] would compute once on mount and never again, which would be wrong here since the result must update when num changes. An omitted dependency that the factory uses causes a stale result: the factory captures the old value from the render it was last created in.

When useMemo Is the Wrong Tool

useMemo prevents redundant re-execution across renders, but it does not make a slow computation fast. If a user increments num to 45, fibonacci(45) must run because num changed, and it will still take several seconds. useMemo only helps with the theme toggle scenario.

For computations that are inherently slow even once, the right tools are algorithmic improvement (iterative Fibonacci is O(n)), Web Workers to move computation off the main thread, or useTransition to mark the state update as non-urgent. Profile before reaching for useMemo to confirm the problem is redundant execution rather than the computation itself being too slow.


Key Code Explained

import { useState, useMemo } from 'react';

function fibonacci(n: number): number {
  return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}

function FibonacciCalculator() {
  const [num, setNum] = useState(10);
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  const fib = useMemo(() => {
    console.log('Calculating Fibonacci...'); // logs on mount + when num changes; NOT on theme toggle
    return fibonacci(num);
  }, [num]); // dependency: only recompute when num changes

  return (
    <div className={theme}>
      <h3>Fibonacci of {num}: {fib}</h3>
      <button onClick={() => setNum((n) => n + 1)}>+</button>
      <button onClick={() => setTheme((t) => (t === 'light' ? 'dark' : 'light'))}>
        Toggle Theme
      </button>
    </div>
  );
}


// Verifying cache behavior:
// 1. Mount → log fires once
// 2. Click "Toggle Theme" → log does NOT fire (cache hit, theme not in deps)
// 3. Click "+" → log fires (cache miss, num changed)


// When useMemo is not enough: iterative algorithm for large inputs
function fibonacciIterative(n: number): number {
  if (n <= 1) return n;
  let prev = 0;
  let curr = 1;
  for (let i = 2; i <= n; i++) {
    [prev, curr] = [curr, prev + curr];
  }
  return curr;
}

function FibonacciCalculatorFast() {
  const [num, setNum] = useState(10);
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  // O(n) instead of O(2^n) — useMemo still useful here to skip re-runs on theme toggle
  const fib = useMemo(() => fibonacciIterative(num), [num]);

  return (
    <div className={theme}>
      <h3>Fibonacci of {num}: {fib}</h3>
      <button onClick={() => setNum((n) => n + 1)}>+</button>
      <button onClick={() => setTheme((t) => (t === 'light' ? 'dark' : 'light'))}>
        Toggle Theme
      </button>
    </div>
  );
}

The console.log inside the factory is the most important debugging technique. Mount: logs once. Theme toggle: no log (cache hit). Increment: log appears (cache miss). This pattern verifies in development that memoization is working as intended.


Tradeoffs

ApproachProCon
useMemo with [num]Prevents recalculation on unrelated state changesDoes not help when num changes; computation still blocks main thread
Iterative FibonacciO(n) vs O(2^n); useMemo less criticalRequires algorithm rewrite
Web WorkerTruly off-main-thread; UI stays responsive at large inputsComplex setup; async communication overhead
No optimizationSimplest codeVisible freeze at large inputs on unrelated state changes

What Interviewers Actually Check

  • Whether you know useMemo caches the factory's return value (not the function itself — that is useCallback)
  • Whether you can explain Object.is comparison in the dependency array and predict behavior with [] vs a wrong dep
  • Whether you recognize that useMemo helps with redundant re-runs, not with inherently slow computations
  • Whether you can describe the profiling workflow: confirm unnecessary re-computation, apply useMemo, verify with console.log
  • Whether you know when to reach past useMemo — algorithmic improvement, Web Workers, or useTransition

Follow-Up Questions

  1. The recursive Fibonacci has O(2^n) complexity. At num = 45, useMemo doesn't help because num changed. How would you restructure the solution?
  2. A colleague says "I'll wrap every derived value in useMemo for safety." What is the argument against this?
  3. If you move the fibonacci function outside the component, do you still need useMemo? Does it change the behavior?
  4. You add useMemo to a filtered list depending on a filters object prop, but the filter still runs on every render. What would you investigate?
  5. Your team builds a data visualization dashboard computing aggregated stats over 50,000 rows on every filter change. Users report 2-3 second freezes. Is useMemo the right solution?

Common Candidate Mistakes

  • Using [] as the dependency array when the factory reads num — result is frozen at the initial value
  • Thinking useMemo makes the computation faster rather than preventing redundant runs of it
  • Applying useMemo to every derived value without profiling — factory comparison overhead outweighs the savings on cheap derivations
  • Passing an object or array as a dependency that is re-created inline on every render — Object.is sees a new reference each time and the cache never hits
  • Not verifying that memoization is working — the console.log inside the factory is a one-line confirmation

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you explain what useMemo caches and what the dependency array controls?
  • Can you distinguish redundant execution (useMemo fixes this) from an inherently slow computation (useMemo does not)?
  • Can you use console.log inside the factory to verify cache hits and misses?
  • Can you identify when the dependency array is wrong and describe the resulting bug?
  • Can you name an alternative approach when useMemo is insufficient for the problem?

Summary

useMemo solves the specific problem of redundant computation: an expensive factory function re-running because unrelated state changed, not because its own inputs changed. The dependency array specifies precisely what the factory depends on — React uses Object.is per dependency, and returns the cached result for any render where none of them changed. The key limitation is that useMemo only helps with unnecessary re-runs; when the dependency driving the computation changes, the work must run regardless. For computations that are inherently slow even once, reach for algorithmic improvement (iterative instead of recursive), Web Workers for off-thread execution, or useTransition to keep the UI responsive while the computation runs. Profile first, verify with console.log inside the factory, and apply useMemo as a surgical fix rather than a default wrapping pattern.

Related Videos
Watch these videos to learn more about this topic
Frequently Asked Questions

When should I use useMemo?

When an expensive computation re-runs because unrelated state changed — not because its own inputs changed. Profile first to confirm the computation is a real bottleneck before adding useMemo.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement