Volume 3

Q156: React.memo, useMemo and useCallback

Difficulty: SeniorFrequency: HighAnswer time: 8-12 minutes

What Interviewers Want To Evaluate

This question checks whether you understand React memoization as a trade-off, not a reflex. Interviewers want React.memo, useMemo, useCallback, shallow comparison, dependency arrays, referential stability, expensive calculations, child render prevention, and when memoization is unnecessary.

Senior answers should explain measurement and correctness before optimization.

Short Interview Answer

React.memo memoizes a component render result by skipping re-render when props are shallowly equal. useMemo memoizes a calculated value between renders. useCallback memoizes a function reference between renders. These tools help when render cost or referential identity actually matters, but they add complexity and can be useless if dependencies change every render or the computation is cheap.

Detailed Interview Answer

React memoization is about reusing work or preserving identity across renders.

The three common tools solve related but different problems:

React.memo: memoize component rendering by props
useMemo: memoize calculated value
useCallback: memoize function reference

They are performance tools, not correctness tools.

React.memo

const UserRow = React.memo(function UserRow({ user }) {
  return <li>{user.name}</li>;
});

React can skip rendering UserRow if its props are shallowly equal to previous props.

This helps when the child is expensive and receives stable props.

Shallow Comparison

React.memo uses shallow prop comparison by default.

same primitive value: equal
same object reference: equal
new object reference: different
new function reference: different

If a parent creates new objects or callbacks every render, memoization may not help.

useMemo

useMemo caches a calculated value.

const filteredUsers = useMemo(() => {
  return users.filter((user) => user.active);
}, [users]);

Use it when the calculation is expensive or when a stable reference is needed for downstream memoization.

useCallback

useCallback caches a function reference.

const handleSelect = useCallback((id) => {
  setSelectedId(id);
}, []);

This is useful when passing callbacks to memoized children or dependency arrays.

It is equivalent to useMemo returning a function.

Dependencies

Dependencies must include reactive values used inside the memoized calculation or callback.

const total = useMemo(() => {
  return items.reduce((sum, item) => sum + item.price * rate, 0);
}, [items, rate]);

Missing dependencies create stale values.

When Memoization Helps

Memoization helps when:

rendering is expensive
calculation is expensive
props are stable
child is memoized
large lists re-render often
dependency identity matters
profiling shows repeated work

It should be based on evidence or a clear known cost.

When It Does Not Help

Memoization may not help when:

the component is cheap
dependencies change every render
new objects are always passed
the child is not memoized
the cache cost is higher than recomputation
the code becomes harder to understand

Unnecessary memoization can make code noisy.

Common Mistakes

A common mistake is wrapping every function in useCallback.

Another mistake is using useMemo to hide expensive work without fixing data flow.

Another mistake is treating memoization as a guarantee. React may still render for reasons outside your assumption.

Senior Trade-Offs

Memoization trades memory and complexity for avoided work.

A senior engineer asks:

what work is expensive?
how often does it repeat?
are dependencies stable?
does memoization improve profiler results?
does the added complexity pay for itself?

Code or Design Example

const ProductRow = React.memo(function ProductRow({ product, onSelect }) {
  return (
    <button onClick={() => onSelect(product.id)}>
      {product.name}
    </button>
  );
});

function ProductList({ products }) {
  const handleSelect = useCallback((id) => {
    console.log(id);
  }, []);

  return products.map((product) => (
    <ProductRow key={product.id} product={product} onSelect={handleSelect} />
  ));
}

The callback identity is stable, so memoized rows have a better chance to skip work.

Debugging Workflow

When memoization seems ineffective:

profile first
check which component rendered
inspect prop identities
check dependency arrays
look for new objects and functions
verify child is memoized
remove memoization that adds no benefit

Interview Framing

Define each tool, then explain shallow comparison, dependencies, and when memoization is worth it.

Revision Notes

Memoization is useful only when stable inputs let React reuse work that would otherwise be costly.

Key Takeaways

Do not memoize by superstition. Memoize where identity and repeated expensive work actually matter.

Follow-Up Questions

  1. What does React.memo do?
  2. What does useMemo do?
  3. What does useCallback do?
  4. What is shallow comparison?
  5. Why do new object references break memoization?
  6. What happens with missing dependencies?
  7. When is memoization unnecessary?
  8. How do you prove memoization helped?
  9. Is useCallback always needed?
  10. How does memoization relate to child renders?

How I Would Answer This In A Real Interview

I would compare React.memo, useMemo, and useCallback, then say they help only when props and dependencies are stable and the avoided work is meaningful. I would mention profiling and remove memoization that does not pay for itself.