Skip to content

useMemo vs useCallback: Differences, Examples & Interview Q&A

Compare useMemo and useCallback with a working React example, dependency rules, common mistakes, and a render-tracing interview exercise.

Rajesh DhimanFebruary 08, 20265 min read
On this page

What is the difference between useMemo and useCallback?

useMemo caches the result of a calculation between renders. useCallback caches a function reference between renders without calling that function. Both use a dependency list to decide when a cached value can be reused. Neither is needed for every calculation or event handler.

Question useMemo useCallback
What do I get back? The calculated value The function itself
Does React call my function to produce the value? Yes, when calculating No; your code or an event calls it
Typical example A filtered array passed to a memoized list A stable handler passed to that list
What invalidates the cache? A changed dependency A changed dependency

Conceptually, useCallback(fn, deps) corresponds to useMemo(() => fn, deps). Include dependencies in that explanation: the equivalence is about reusing a reference while the dependencies stay equal.

This lesson covers the APIs and interview reasoning. For an optimization decision and a recording exercise, use when to use React.memo, useMemo and useCallback.

A working example using both hooks

Paste this into App.tsx in a client-rendered React application. The example assumes manual memoization; compiler-enabled builds may optimize additional work automatically.

'use client';
 
import { memo, useCallback, useMemo, useState } from 'react';
 
type Question = { id: number; title: string };
 
const QUESTIONS: Question[] = [
  { id: 1, title: 'Explain closures' },
  { id: 2, title: 'Explain React keys' },
  { id: 3, title: 'Compare useMemo and useCallback' },
];
 
const QuestionList = memo(function QuestionList({
  questions,
  onSelect,
}: {
  questions: Question[];
  onSelect: (id: number) => void;
}) {
  return (
    <ul>
      {questions.map((question) => (
        <li key={question.id}>
          <button onClick={() => onSelect(question.id)}>{question.title}</button>
        </li>
      ))}
    </ul>
  );
});
 
export default function App() {
  const [query, setQuery] = useState('');
  const [selected, setSelected] = useState<number | null>(null);
  const [counter, setCounter] = useState(0);
 
  const visible = useMemo(
    () => QUESTIONS.filter((question) =>
      question.title.toLowerCase().includes(query.toLowerCase())
    ),
    [query]
  );
 
  const handleSelect = useCallback((id: number) => setSelected(id), []);
 
  return (
    <main>
      <label>
        Search questions
        <input value={query} onChange={(event) => setQuery(event.target.value)} />
      </label>
      <button onClick={() => setCounter((value) => value + 1)}>
        Counter: {counter}
      </button>
      <p>Selected question: {selected ?? 'none'}</p>
      <QuestionList questions={visible} onSelect={handleSelect} />
    </main>
  );
}

visible is the array returned by useMemo. handleSelect is the function returned by useCallback; React does not call it to select a question. Clicking a row does.

This three-row list is deliberately small for readability. It demonstrates reference behavior, not a measured performance benefit. In production, only keep this complexity if measurement or another consumer of those references justifies it.

Dependencies and reference identity

React compares each dependency with its previous value using Object.is. A newly created object or function normally has a different identity, even if its contents or source look identical.

When the example's counter changes, query stays the same. useMemo can return the previous array, and useCallback can return the previous handler. React.memo can then skip the list's render because its props are unchanged.

When query changes, React recalculates visible. The new array is an intentional prop change, so the list renders matching questions.

Every reactive value used inside a hook's function belongs in its dependency list. The state setter is stable; the example's handler reads no changing state. If you instead read selected inside the handler, account for it as a dependency. See useEffect dependencies and cleanup for related dependency mistakes.

Common mistakes

Assuming useCallback stops all child renders

Without a memoization boundary or compiler optimization, stabilizing a callback alone does not make an ordinary child skip parent-driven rendering. Also, a memoized child can still render because of its own state or the context it consumes.

Omitting dependencies to force a stable reference

An empty list does not mean “ignore changing inputs.” It says the function uses no changing reactive inputs. Missing a dependency can leave it reading an old value. Prefer a functional state update when the next value depends only on the previous value:

const increment = useCallback(() => {
  setCounter((previous) => previous + 1);
}, []);

This replaces a handler inside the example; it is not a standalone component. The updater removes the need to capture counter.

Using memoization to make incorrect code work

A cache is an optimization, not permanent storage. Fix incorrect calculation logic or Effect dependencies before adding memoization. If an Effect needs a helper used nowhere else, defining it inside the Effect can remove the function dependency altogether.

Declaring an optimization worthwhile without measuring

There is no universal item count or millisecond threshold that makes a hook correct for every device. A cheap calculation may still need a stable result for a memoized consumer, while an expensive calculation gains little if all its inputs change each time. Use the profiling exercise to test the actual interaction.

React Compiler and manual memoization

React Compiler can automatically memoize code when it is installed and enabled. React version alone does not establish that it is active. Understanding the two hooks remains useful for existing applications and for explaining reference identity. Check the compiler setup for your framework.

Practice: trace the renders

For the example above, assume no compiler optimization, unchanged context, and no state inside QuestionList. Predict the result of each edit before running it:

  1. Click the counter with both hooks in place.
  2. Replace useMemo with a direct call to QUESTIONS.filter, then click the counter.
  3. Restore useMemo, replace handleSelect with an inline function, then click the counter.
  4. Restore both hooks and change the search query.

Answer and explanation

  1. The parent renders; the memoized list can skip because both props are stable.
  2. Filtering returns a new array, so the list renders.
  3. The handler has a new reference, so the list renders.
  4. The query changes the filtered array, so the list renders the updated results.

Development Strict Mode can invoke render work more than once. Explain which props changed rather than promising an exact console-log count.

Interview follow-up questions

Does useCallback prevent creating a function? No. Your function expression is still evaluated. React can return the previously cached reference when dependencies are unchanged.

Is useMemo guaranteed to keep its cache forever? No. React documents cases where caches are discarded, including initial-mount suspension and development file edits. Code must remain correct if a calculation runs again.

Should I wrap every handler? No. Ask which consumer benefits from its stable identity and whether that benefit warrants the complexity.

Try the larger memoization profiling exercise next, or return to the React roadmap.

API references: useMemo, useCallback, and memo.

Companion Book

Looking for structured, offline interview prep?

Free online lessons • Portable Amazon Kindle / Paperback

Frontend Interview Guide (2025 Edition) cover
Recommended for React & Frontend Rounds

Frontend Interview Guide (2025 Edition)

By Rajesh Dhiman

Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.

View on Amazon ↗Kindle & Paperback
JavaScript Interview Guide cover
Recommended for Core JS Rounds

JavaScript Interview Guide

By Rajesh Dhiman

Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.

View on Amazon ↗Kindle & Paperback

Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.

RD

Written by Rajesh Dhiman

Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.