skip to content

In React, what is the difference between useMemo(factory, deps) and useCallback(fn, deps), and how are the two related?

level: juniorimportance: must knowfreq 80%

answer

  1. one caches a value, one a function
  2. does the hook call what you pass?
  3. useCallback never invokes its argument
  4. same cell, same comparison, different shape
  5. useCallback(fn, deps) is useMemo(() => fn, deps)

basics

~10 s

useMemo calls the factory you pass and caches its return value; useCallback caches the function itself without ever calling it. They are one mechanism: useCallback(fn, deps) behaves exactly like useMemo(() => fn, deps).

solid answer

~40 s

Both hooks cache something on the component and hand back the previous copy for as long as the dependency array still compares equal; the difference is what gets cached. `useMemo(factory, deps)` invokes `factory` during render and remembers the value it returned — a computed result, or an object you want to keep referentially stable. `useCallback(fn, deps)` never invokes `fn`; it remembers that function value itself, so whatever receives it — a prop, another hook's dependency list — sees the same identity across renders. That makes `useCallback(fn, deps)` exactly equivalent to `useMemo(() => fn, deps)`; it exists purely because wrapping a function in a second arrow reads badly. The classic slip is `useCallback(handleClick(), [])`, which calls the handler during render and memoizes its return value instead of the function.

code

javascript · 10 lines
javascript
import { useCallback, useMemo } from 'react';

function Row({ id, onPick }) {
  const a = useCallback(() => onPick(id), [id, onPick]);
  const b = useMemo(() => () => onPick(id), [id, onPick]);
  // a and b are equivalent shapes: neither has called onPick yet
  const c = useMemo(() => onPick(id), [id, onPick]);
  // c is different: onPick already ran during render
  return null;
}

go deeper

for a junior

Be able to say in one sentence that useMemo remembers a computed value while useCallback remembers a function, and to spot useCallback(fn(), []) as a bug because it calls the function.

for a middle

Explain that both hooks share one per-call-site cache and derive the equivalence useCallback(fn, deps) === useMemo(() => fn, deps). Say clearly which one runs code during render and which one does not.

for a senior

Show that you know why identity is the point: name the consumers that compare by reference — props on a child and other hooks' dependency lists — and describe what the stable reference buys them rather than treating the wrapper as free.

for a principal

Own the codebase-level position. Decide when a stable function identity is genuinely part of a component's or hook's published contract versus incidental, since every wrapper adds a dependency list that reviewers must keep honest.

## The two signatures `useMemo` takes a zero-argument function (the *factory*) and a dependency array, calls the factory, and returns whatever it produced. `useCallback` takes a function and a dependency array and returns that same function value back to you. ```js const sorted = useMemo(() => rows.slice().sort(byName), [rows]); const onPick = useCallback((id) => select(id), [select]); ``` In the first line React runs `rows.slice().sort(byName)` while rendering and stores the sorted array. In the second line React does not call anything — it stores the arrow you handed it and gives it back. ## They are one mechanism, not two Each hook call site owns a small cell on that component instance: the last dependency array, and the last cached result. On a later render React compares the new dependency array against the stored one; if every entry matches, it returns the stored result and does not call the factory again. If anything differs, it runs the factory (for `useMemo`) or takes the new function (for `useCallback`), stores it, and returns it. Because a function is just a value, caching a function is caching a value, and the equivalence falls straight out: ```js useCallback(fn, deps) // is the same as useMemo(() => fn, deps) ``` The outer arrow in that `useMemo` form is the factory; it returns `fn` without calling it. `useCallback` is sugar over exactly that shape. There is no separate caching machinery, no different comparison, and no different guarantee behind one versus the other. ## Why a separate hook then Ergonomics. Functions are the single most common thing people want to keep referentially stable, and `useMemo(() => () => doThing(id), [id])` is a double arrow that readers routinely misparse. `useCallback` also types better in TypeScript: the returned type is simply the type of the function you passed, whereas the `useMemo` form has to be read through the wrapper. ## What "the same" means downstream The reason anyone reaches for either hook is that JavaScript compares objects and functions by reference. A fresh arrow written in the component body is a brand-new value on every render, so anything comparing by reference sees a change. Two consumers care about that: props received by a child that compares its props, and dependency arrays of other hooks that list the function or object. `useMemo` and `useCallback` are how you hold identity steady for those consumers — the value itself is unchanged either way; only its identity is at stake. ## Where the caching lives The cache belongs to one hook call in one component instance. Render the same component twice on the page and each instance has its own cell — there is no shared, global memo table keyed by arguments, and nothing is reused across component types. When a component unmounts, its cells go away with the rest of its state. ## The mistakes reviewers actually see **Calling instead of passing.** `useCallback(makeHandler(), [])` invokes `makeHandler` during render and memoizes the returned value. If you genuinely want the result of a call, that is a `useMemo`. `useCallback` should always be given a function *expression* or an existing function reference. **Reaching for `useCallback` when the value is not a function.** Objects, arrays and configuration blobs go through `useMemo`. Passing a non-function to `useCallback` "works" — it caches the value — but it lies to every reader. **Assuming `useCallback` prevents the arrow from being created.** The arrow literal in your component body is evaluated on every render regardless; `useCallback` only decides which of the two functions — the fresh one or the stored one — is handed back. **Reading `useMemo`'s factory as running lazily or asynchronously.** It runs synchronously, inline, during rendering, before your component returns. ## First render On mount there is nothing cached, so `useMemo` always calls the factory and `useCallback` always stores the function it was given. Neither hook can skip work the first time; both only ever pay off from the second render onward.

  • If the two are equivalent, why did React ship useCallback at all?
    Readability and typing. The `useMemo` form needs a double arrow — `useMemo(() => fn, deps)` — which readers misparse as calling `fn`, and TypeScript infers it through the wrapper. `useCallback` states the intent directly and returns exactly the function type you passed. There is no behavioural difference to gain.
  • What happens if you omit the dependency array entirely from useMemo?
    The factory runs on every render and the cache never hits, so you have paid for the hook and gained nothing. Unlike an effect, an absent array does not mean "run once" — it means "no cached result is ever reused". The React hooks lint rule flags it.
  • What do these hooks return on the very first render?
    On mount there is nothing stored yet, so `useMemo` always calls the factory and returns its result, and `useCallback` stores and returns the function you passed. Both only start skipping work from the second render on, which is why neither helps a component that mounts once and never updates.

saying these in an interview costs you the question

  • Says useCallback calls the function during render and caches its result
  • Writes useCallback(handleClick(), []), invoking the handler instead of passing it
  • Treats the two as unrelated hooks with different caching mechanisms
  • Believes the memo cache is shared across instances of the same component
  • Says useMemo returns a memoized function rather than a memoized value

context