React Compiler: automatic memoization
- Last updated
- Prerequisites:
- React rendering model
- useMemo/useCallback and memo() basics
- react
- compiler
- memoization
- performance
- babel
- react-compiler
Read at your depth
The practical view
Until now you manually wrapped values in useMemo and functions in useCallback, and wrapped components in memo() to skip re-renders. The React Compiler (installed as a Babel/SWC plugin, e.g. babel-plugin-react-compiler) analyzes your components and automatically applies memoization wherever it is safe: it memoizes computed values, callback functions, and JSX subtrees, so hand-written useMemo/useCallback/memo become mostly unnecessary. You keep coding normally — no new syntax — and the compiler inserts the caching. Rules of React still apply: no mutating props/state during render, hooks called unconditionally.
The same idea in other frameworks
// Compiler handles this:
const doubled = compute(items); // memoized automatically
const onSave = () => save(draft); // memoized automatically// Angular defaults to OnPush with signals: derived values
// memoize automatically via computed().// Vue's reactivity is granular by default: computed() memoizes
// and the render effect only re-runs for used deps.// Svelte compiles components; $derived memoizes derived state
// and the compiler decides the update graph at build time.Legacy vs modern
Manual useMemo/memo scaffolding vs compiler-auto memoization
Hand-written memoization is error-prone and verbose; the compiler derives the same caching automatically and stays correct as the component evolves.
const list = useMemo(() => items.filter(byQuery), [items, query]);
const onPick = useCallback((id) => pick(id), []);
const Row = memo(TableRow);const list = items.filter(byQuery);
const onPick = (id) => setSelected(id);Interview gotchas
Context
The interviewer wants to distinguish a mechanical understanding ('it does what I do by hand') from understanding the analysis the compiler performs.
The mechanical answer
The compiler does not blindly cache — it performs a reactive-value analysis: for each expression it determines which inputs are reactive (derived from props/state/hooks) and caches only when the computation is pure with respect to those inputs and the inputs can be compared with Object.is. It can also split JSX into memoized subcomponents and reorder computations. Hand-written useMemo caches based on the dependency array you guessed; the compiler's cache is derived from actual data flow, so it stays correct when the component changes — manual memoization goes stale the moment someone edits a dep array incorrectly, while the compiler's output is regenerated from source every build.
Trap
The trap is claiming the compiler removes the need for understanding rendering, or that it makes components 'not re-render'. Re-renders still happen for the parent; the compiler prunes the child work when inputs are unchanged. Another trap: 'it replaces React's reconciliation' — it does not; it optimizes at the component-function level on top of the existing commit pipeline.
Context
This probes whether you know the compiler's constraints — interviewers want to see that you understand it is not magic and has a rules contract.
The mechanical answer
The compiler assumes the Rules of React: hooks are called unconditionally at the top level, and render must be pure. It bails out (skips memoization for that component) when it cannot prove purity — for example, components that mutate refs during render, rely on unstable globals, or use patterns it cannot analyze like dynamically-invoked hooks. Reading mutable values (like ref.current) during render is a red flag because the compiler cannot track when those change, which can produce stale caches. In practice you see 'compiler cannot optimize' warnings in the build and the affected component falls back to normal (still correct) behavior.
Trap
Saying 'it bails out and breaks' is wrong — bailing out means the component renders normally without auto-memoization, never incorrect output. The real risk is with code that already violates the Rules of React: such code was already buggy (stale reads, double renders under StrictMode), and the compiler just surfaces it. Mentioning React Compiler's runtime invariant checks (dev-only) that flag violations is a strong differentiator.