skip to content

A React code review turns up `const total = useMemo(() => price * quantity, [price, quantity]);`. Is that worth writing? What does the useMemo call itself cost on every render?

level: middleimportance: should knowfreq 48%

answer

  1. the wrapper is bigger than the work
  2. allocation and comparison every render
  3. cached value is retained
  4. identity can be the real product
  5. stale deps are a correctness bug

basics

~20 s

No. On every render React still allocates the arrow function and the dependency array and compares each dependency, which costs about as much as one multiplication. Write the plain expression instead and save useMemo for expensive work or for a reference that must stay stable.

solid answer

~40 s

That `useMemo` is a net loss. On every single render React still evaluates the arrow function expression, allocates the `[price, quantity]` array literal, then walks it comparing each entry with `Object.is` against the stored one — and only then decides not to run a multiplication that costs essentially nothing. You have added allocation, comparison, a hook slot, and a dependency array someone must keep correct, in exchange for skipping arithmetic. `useMemo` earns its place in two situations: the computation is genuinely expensive, such as sorting or grouping a large list, or the returned reference identity matters because the value is passed to a memoized child or into another hook's dependency array. Neither applies to a product of two numbers, so the review comment is simply "drop the wrapper and compute it inline".

code

javascript · 14 lines
javascript
import { useMemo } from 'react';

export function Invoice({ price, quantity, rows }) {
  // Not worth it: the memo machinery costs more than the multiplication.
  const total = price * quantity;

  // Worth it: sorting a large list, and the array identity is reused.
  const sorted = useMemo(
    () => rows.slice().sort((a, b) => a.name.localeCompare(b.name)),
    [rows]
  );

  return <div>{total} / {sorted.length}</div>;
}

go deeper

for a junior

Know that useMemo is not automatically an improvement, and that wrapping a trivial calculation such as a multiplication adds more code than it saves.

for a middle

Be able to itemise the per-render costs — a new closure, a new dependency array, an Object.is comparison per entry — and contrast them with the work actually skipped.

for a senior

Bring the production angle: the retained cache across many mounted instances, and the fact that a drifting dependency array turns a performance wrapper into a stale-data defect.

for a principal

Own the policy question of what belongs in review guidance: memoization justified by a measurement, not by appearance, so the codebase does not accumulate dependency arrays nobody maintains.

## Memoization is not free, and the bill comes every render The intuition that trips people is that `useMemo` is a switch that says "do less work". What it actually does is *replace* one piece of work with a different, smaller piece of work — plus permanent bookkeeping. If the replaced work was already tiny, the swap loses. Every render of a component containing `useMemo(() => price * quantity, [price, quantity])` does the following, unconditionally: - allocates a fresh closure for the arrow function (it is a new function object each render, whether or not React calls it); - allocates a fresh array for the dependency list; - reads the hook's stored slot for this component instance; - compares each dependency with `Object.is` against the previous array; - returns the stored value. Against that, the work avoided is `price * quantity`. A multiplication is a fraction of a nanosecond. The wrapper is more machinery than the thing it protects, so the honest answer is that the memo makes this render marginally slower, not faster. ## The retention cost people forget There is a second bill: the cached value is **held**. React keeps the memoized result alive on the fiber for that component instance until the dependencies change or the component unmounts. For a number that is nothing. For a derived structure it can be real: ```jsx const index = useMemo(() => buildIndex(hugeDataset), [hugeDataset]); ``` That index stays in memory for the component's whole lifetime even if it is read on one rare branch. Multiply by many mounted instances — every row of a long list holding its own memoized derived array — and memoization becomes a memory-shape decision, not just a CPU one. It is worth naming this in an interview because most candidates only ever discuss CPU. ## What makes a useMemo worth writing **Real computational cost.** Sorting, grouping, filtering or diffing collections large enough to show up in a profile; parsing; building lookup structures. The test is not "does this look complicated" but "did I measure it". If a computation takes microseconds and the component renders a handful of times per interaction, the memo is decoration. **Reference identity as the product.** If the result is an object or array handed to a child wrapped in `React.memo`, or placed in another hook's dependency array, then holding the same reference is the whole point — the computation may be trivial and the memo still pays, because what it prevents is downstream work, not local work. **Not a semantic guarantee.** React documents memoization as an optimization it is permitted to discard, so `useMemo` must never be the mechanism that makes code correct. If a value must be created exactly once, that is a job for a ref or lazy state initialization, not a memo. ## The readability bill The runtime numbers here are small in both directions; the durable cost is maintenance. Every `useMemo` adds a dependency array that must stay in sync with what the function reads. A stale array is not a performance bug, it is a *correctness* bug — the UI shows a value derived from inputs that already changed. Multiplied across a codebase, a habit of memoizing everything raises the number of places where that failure can be introduced, which is why "memoize by default" is a poor default even though each individual wrapper is cheap. ## How to answer this in a review Say it in three moves: name the per-render costs (closure, array, comparison), name what was saved (a multiplication), and give the two conditions under which you would keep it (measured expensive computation, or identity consumed by an equality check). Then propose the concrete change — `const total = price * quantity;` — because reviewers who only say "this is premature optimization" without the mechanism are the ones who lose the argument.

  • Beyond CPU, what is the cost of a useMemo that caches a large derived array?
    Retention. React holds the cached result on that component instance until the dependencies change or it unmounts, so a large derived structure stays in memory even when it is rarely read. In a long list where every row memoizes its own derived data, that adds up to a memory-shape decision rather than a speed one.
  • Someone argues that useMemo is harmless because the overhead is tiny — how do you respond?
    The runtime overhead is indeed small; the durable cost is the dependency array. Each one must stay in sync with what the function reads, and when it drifts the result is stale data on screen — a correctness bug, not a slow render. Adding wrappers everywhere multiplies the places that failure can appear.
  • How do you decide whether a computation is expensive enough to memoize?
    Measure it rather than eyeball it. Profile the component under a realistic interaction and look at how much of the render time that computation actually accounts for, at production data sizes. A computation that is invisible in the profile at real scale does not become worth memoizing because the code looks complicated.

saying these in an interview costs you the question

  • Assumes useMemo is free because it only stores a value
  • Ignores that the arrow function and deps array allocate every render
  • Never mentions the retained cached value
  • Memoizes by default and calls it good practice
  • Thinks a stale dependency array is only a performance problem

context