Topics in this subject
React 6 min read Updated 6 Aug 2026

useMemo & useCallback

useMemo caches a value, useCallback caches a function reference—both for referential stability, not raw speed.

🧑‍🏫 Sabse pehle — simple mein samjho#

React har render pe cheezein dobara banata hai — har baar naya hisaab, naya function. Kabhi-kabhi ye mehnga (slow) ya bekaar hota hai. useMemo bolta hai "ye mehnga hisaab ka result yaad rakh lo (cache karo), jab tak inputs same hain dobara mat karo". useCallback wahi cheez hai par ek function ke liye. Jaise ek baar sabzi kaat li to fridge mein rakh do — agar wahi sabzi chahiye to dobara mat kaato. Sirf zaroorat pe use karo, warna over-engineering hai.

function List({ items }) {
  // sorting mehnga hai — items same rahe to dobara sort mat karo
  const sorted = useMemo(() => items.slice().sort(), [items]);

  // ye function har render pe naya na bane, isko yaad rakh lo
  const handleClick = useCallback(() => console.log("hi"), []);

  return <Child data={sorted} onClick={handleClick} />;
}

Yaad rakho: useMemo = result yaad rakho, useCallback = function yaad rakho — sirf jab sach mein zaroorat ho.

Both hooks cache something across renders keyed by a dependency array. useMemo caches a computed value; useCallback caches a function reference. The primary goal is usually referential stability, so that dependency arrays and React.memo children don't see a "new" value every render.

The two hooks#

// useMemo — recomputes only when [a, b] change
const sorted = useMemo(() => bigList.slice().sort(cmp), [bigList]);

// useCallback — same function reference until [query] changes
const onSearch = useCallback((e) => fetchResults(query, e.target.value), [query]);

// Identity: useCallback(fn, deps) === useMemo(() => fn, deps)

🎯 useCallback(fn, deps) is exactly useMemo(() => fn, deps). useCallback exists purely as sugar for memoizing a function without the extra arrow.

Think of Flipkart/Amazon search-as-you-type over a catalog of thousands of products. Filtering and sorting on every keystroke is expensive enough on its own, but that computation must not re-run when something unrelated changes — like a "dark mode" toggle re-rendering the parent page.

// ⚠️ Without useMemo, EVERY re-render (even a dark-mode toggle) re-filters
// and re-sorts thousands of products, even though `products`/`query` didn't change.
function ProductPage({ products }) {
  const [query, setQuery] = useState("");
  const [darkMode, setDarkMode] = useState(false); // toggling this re-renders ProductPage

  const results = products
    .filter((p) => p.name.toLowerCase().includes(query.toLowerCase()))
    .sort((a, b) => b.rating - a.rating);

  return (
    <div className={darkMode ? "dark" : "light"}>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <button onClick={() => setDarkMode((d) => !d)}>Toggle dark mode</button>
      <ResultsList results={results} />
    </div>
  );
}
// 🟢 Only re-filters/re-sorts when `products` or `query` actually change —
// flipping darkMode re-renders the component but skips this computation.
function ProductPage({ products }) {
  const [query, setQuery] = useState("");
  const [darkMode, setDarkMode] = useState(false);

  const results = useMemo(
    () =>
      products
        .filter((p) => p.name.toLowerCase().includes(query.toLowerCase()))
        .sort((a, b) => b.rating - a.rating),
    [products, query]
  );

  return (
    <div className={darkMode ? "dark" : "light"}>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <button onClick={() => setDarkMode((d) => !d)}>Toggle dark mode</button>
      <ResultsList results={results} />
    </div>
  );
}
flowchart TD
    T["darkMode toggled"] --> RE["ProductPage re-renders"]
    RE --> Q{"Filter wrapped in useMemo?"}
    Q -->|"No"| BAD["Re-filter and re-sort thousands of products again ⚠️"]
    Q -->|"Yes, deps products and query"| GOOD{"Did products or query change?"}
    GOOD -->|"No"| SKIP["Reuse cached results 🟢"]
    GOOD -->|"Yes"| RECOMPUTE["Recompute filtered and sorted list"]

Why referential stability matters#

flowchart TD
    P["Parent re-renders"] --> Q{"Is child prop a new reference?"}
    Q -->|"inline {} or () => {}"| NEW["New reference every render"]
    Q -->|"useMemo / useCallback"| STABLE["Stable reference"]
    NEW --> R["React.memo child re-renders anyway ⚠️"]
    STABLE --> S["React.memo child skips render 🟢"]
    STABLE --> T["useEffect deps stay equal → effect doesn't re-run"]

Two concrete payoffs:

  1. Memoized children (React.memo) do a shallow prop compare—a stable reference lets them skip re-rendering.
  2. Dependency arrays of useEffect/useMemo/useCallback compare by Object.is—a stable reference prevents effects from firing every render.
const Child = React.memo(function Child({ onClick }) { /* … */ });

function Parent({ query }) {
  // ⚠️ Without useCallback, onClick is new each render → Child re-renders despite memo
  const onClick = useCallback(() => console.log(query), [query]);
  return <Child onClick={onClick} />;
}

useMemo vs useCallback#

useMemo useCallback
Caches Return value of the function The function itself
Signature useMemo(() => value, deps) useCallback(fn, deps)
Runs the function? Yes, during render (returns result) No, returns fn for later
Typical use Expensive computation, stable object/array prop Stable callback for memoized child or effect dep
Equivalence useMemo(() => fn, deps)

Dependency arrays ⚠️#

  • List every reactive value referenced inside. Omitting deps → stale closures. The react-hooks/exhaustive-deps lint rule enforces this.
  • [] = compute once and never recompute (deps never change).
  • Deps are compared with Object.is; objects/arrays/functions compare by reference, so an unstable dep defeats the memo.
// ⚠️ stale: uses `count` but doesn't list it
const fn = useCallback(() => console.log(count), []);
// 🟢
const fn = useCallback(() => console.log(count), [count]);

Don't wrap everything ⚠️#

These hooks are not free: they cost memory, a deps-array comparison every render, and code complexity. For a cheap computation or a callback passed to a non-memoized DOM element, the memo often costs more than it saves.

🟢 Profile first. Reach for them when (a) the computation is genuinely expensive, or (b) you need referential stability for a memoized child or an effect dependency. Otherwise skip them.

React Compiler note 🎯#

The React Compiler (React 19-era) automatically memoizes values and callbacks at build time by analyzing your components. With it enabled, most manual useMemo/useCallback becomes unnecessary—you write plain code and the compiler inserts memoization. Still understand the hooks for interviews, legacy code, and cases the compiler bails out of.

Interview Q&A#

Q1. What is the difference between useMemo and useCallback? useMemo runs a function and caches its return value; useCallback caches the function reference itself. useCallback(fn, deps) equals useMemo(() => fn, deps).

Q2. What is the main reason to use these hooks—raw performance? Usually referential stability, not raw speed: keeping a stable reference so React.memo children can skip re-renders and effect dependency arrays don't fire every render. Expensive-computation caching is the secondary case.

Q3. Why might useCallback fail to stop a child from re-rendering? Because the child isn't wrapped in React.memo (so it re-renders regardless of prop identity), or because one of the callback's dependencies is itself unstable, changing the reference every render.

Q4. What's the downside of wrapping everything in useMemo/useCallback? Each adds memory overhead, a dependency comparison every render, and code noise. For cheap work or non-memoized consumers the overhead exceeds the benefit—profile before optimizing.

Q5. How does the React Compiler change this advice? It auto-memoizes values and functions at build time, making most manual useMemo/useCallback unnecessary. You still need to understand them for legacy code and compiler bail-out cases.