How React useMemo Works: Does It Actually Memoize Values?

React's useMemo hook caches the return value of a computation and only recalculates it when its dependency array changes, using strict equality (Object.is) comparisons on each render.

The useMemo hook is a performance optimization primitive in the React library that memoizes expensive computations to prevent unnecessary recalculations. According to the facebook/react source code, it leverages an internal memo cache slot to store values across renders. Understanding its implementation in packages/react-reconciler/src/ReactFiberHooks.js reveals exactly when and how values are preserved.

How useMemo Works Under the Hood

The implementation spans two critical files in the React codebase.

The Public API in ReactHooks.js

The public-facing hook is defined in packages/react/src/ReactHooks.js. It acts as a thin wrapper that forwards arguments to the current dispatcher:

export function useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
  return dispatcher.useMemo(create, deps);
}

This entry point (lines 143-148) ensures that useMemo is called during render phases, delegating the actual logic to the reconciler's hook system.

The Reconciler Implementation in ReactFiberHooks.js

The real work occurs in packages/react-reconciler/src/ReactFiberHooks.js. The dispatcher maintains a memo cache slot for each useMemo invocation. On every render, the algorithm performs these steps:

  • Reads the previous dependency array (deps) and cached value from the fiber's hook slot.
  • Compares each element of the new deps against the old using Object.is strict equality.
  • If any dependency changed, it re-executes the create callback, stores the new result, and returns it.
  • If all dependencies match, it returns the cached value without invoking create.

This implementation (around lines 1150-1190) ensures that referential equality is maintained across renders when dependencies are stable.

Does useMemo Actually Memoize Values?

Yes—the returned value is cached. When dependencies remain unchanged, useMemo guarantees that the same object reference or primitive is returned on subsequent renders. This behavior is verified in packages/react/src/__tests__/ReactStrictMode-test.js (lines 401-447), where tests confirm identical references across renders.

No—the callback function is not memoized. useMemo does not maintain a stable reference to the create function itself; it only caches the result. If you require a stable function reference, use useCallback, which is internally implemented as useMemo(() => fn, deps).

Strict Mode Double-Invocation Edge Case

In Strict Mode, React deliberately double-invokes the create function during initial mount to help surface side effects. While the memoized value remains stable after both calls, the callback executes twice. This behavior is explicitly tested in ReactStrictMode-test.js and demonstrates an important distinction: the cache stores only the final result, not the process of creation.

Dependency Array Handling

The hook's behavior changes significantly based on how you pass the dependency argument:

  • undefined or omitted: When deps is omitted, useMemo treats this as "no dependency list" and recreates the value on every render, effectively behaving like a normal function call with no caching.
  • Empty array []: The value is computed once after the first render and never recomputed, mimicking a constant for the component's lifetime.
  • Populated array [a, b]: The value recalculates only when specific dependencies change via Object.is comparison.

Practical Code Examples

The following examples demonstrate common patterns using the useMemo hook.

Expensive Calculation Caching:

import React, { useMemo, useState } from 'react';

function ExpensiveTree({ size }) {
  // buildHugeTree runs only when size changes
  const tree = useMemo(() => buildHugeTree(size), [size]);
  return <pre>{tree}</pre>;
}

Preserving Referential Equality for Props:

function List({ items }) {
  // Maintains stable reference for React.memo children or useEffect dependencies
  const filtered = useMemo(() => {
    return items.filter(i => i.active);
  }, [items]);

  return <ItemList data={filtered} />;
}

Observing Strict Mode Behavior:

function Demo() {
  const value = useMemo(() => {
    console.log('create called');
    return Math.random();
  }, []); // Logs twice in Strict Mode, but value is identical both times

  return <div>{value}</div>;
}

Summary

  • useMemo caches the result of a function, returning the same reference while its dependency array remains shallow-equal.
  • The hook uses Object.is comparisons to detect changes in the dependency array between renders.
  • It does not cache the function itself; use useCallback for stable function references.
  • In Strict Mode, the creation callback may run twice during development, but the memoized value remains stable.
  • Passing undefined for deps causes recalculation every render, while [] calculates only once.

Frequently Asked Questions

Does useMemo cache the function or the result?

useMemo caches only the return value of the function, not the function itself. The create callback receives no memoization and is discarded after execution. If you need to memoize a function definition to maintain referential equality (for example, to pass to child components or useEffect dependencies), use the useCallback hook instead.

Why does my useMemo callback run twice in development?

React Strict Mode intentionally double-invokes certain functions during initial mount to help detect side effects. In packages/react/src/__tests__/ReactStrictMode-test.js, tests confirm that while the create callback executes twice, the cached value returned by useMemo remains identical after both invocations. This behavior only occurs in development mode.

What happens if I omit the dependency array in useMemo?

If you omit the deps argument or pass undefined, React treats this as having no dependency list and recalculates the value on every render. This negates any performance benefit. To memoize a value that never changes, pass an empty array [].

When should I use useMemo instead of useCallback?

Use useMemo when you want to cache the result of an expensive computation (objects, arrays, or derived values). Use useCallback when you need a stable function reference to pass to child components or to satisfy dependency arrays in useEffect. Technically, useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s https://instagit.com/install.md

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →