A Look Behind the Scenes of React & Optimization TechniquesEntry 5 of 10

useMemo (and How It Differs from useCallback)

useMemo(calculate, deps) caches the result of a calculation and recomputes it only when a dependency changes.

Notes

  • Use it for expensive calculations (sorting or filtering large lists, heavy math), or to keep an object or array identity stable for a memo() child.
  • Don't wrap cheap calculations. The dependency comparison has its own cost.
  • Dependencies follow the same rules as useEffect.
  • Never mutate props or state inside it. Array.prototype.sort() sorts in place, so players.sort(...) mutates the prop. Copy first ([...players].sort(...)) or use toSorted() (ES2023).

useCallback vs useMemo:

AspectuseCallbackuseMemo
CachesA functionA value
ReturnsThe same function instanceThe same computed result
Typical useStable callbacks for effects or memo() childrenExpensive derived data
RelationshipuseCallback(fn, deps)useMemo(() => fn, deps) (equivalent)

What / Why / When

What

A cached derived value.

Why

Skips repeated expensive work on re-renders that don't affect the result.

When

The profiler shows a slow calculation in render, or a memo() child receives a derived object/array.

Use case

Checking whether a number is prime, or sorting a player list.

Code compare

React
import { useMemo } from "react";​type PlayerData = { id: string; name: string };​export function PlayerList({ players }: { players: PlayerData[] }) {  const sortedPlayers = useMemo(    () => [...players].sort((a, b) => a.name.localeCompare(b.name)), // copy, don't mutate props    [players],  );​  return (    <ul>      {sortedPlayers.map((p) => (        <li key={p.id}>{p.name}</li>      ))}    </ul>  );}
React
// Inside Counter: recompute only when initialCount changesconst initialCountIsPrime = useMemo(() => isPrime(initialCount), [initialCount]);

The closest equivalent is computed(), which caches its result and recomputes only when a signal it reads changes:

Angular
import { Component, computed, input } from "@angular/core";​type PlayerData = { id: string; name: string };​@Component({  selector: "app-player-list",  template: `    <ul>      @for (p of sortedPlayers(); track p.id) {        <li>{{ p.name }}</li>      }    </ul>  `,})export class PlayerListComponent {  players = input.required<PlayerData[]>();  sortedPlayers = computed(() => [...this.players()].sort((a, b) => a.name.localeCompare(b.name)));}

A pure pipe is the template-level alternative. Pipes are pure by default, so pure: true can be omitted:

Angular
import { Pipe, PipeTransform } from "@angular/core";​@Pipe({ name: "sortedPlayers" })export class SortedPlayersPipe implements PipeTransform {  transform(players: PlayerData[]): PlayerData[] {    return [...players].sort((a, b) => a.name.localeCompare(b.name));  }}
Angular
@for (p of players() | sortedPlayers; track p.id) {<li>{{ p.name }}</li>}

Differences and gotchas

  • Is useMemo like a pipe? Like a pure pipe, yes: both skip recalculation when the input reference is unchanged. But computed() is the closer match, because it lives in the class, can be reused in code, and tracks dependencies automatically.
  • useMemo needs a hand-written dependency array. computed() tracks the signals it reads.
  • Pure pipes suit reusable formatting shared across templates. computed() suits component-specific derived data.

Quick reference

ReactAngular
useMemo(() => calc, [deps])computed(() => calc)
—Pure pipe (template-level)
useCallback(fn, [deps])Class method (always stable)