A team enables the React Compiler and deletes their useMemo and useCallback calls. Which performance problems does the compiler still not solve for them?
answer
- it prunes wasted renders, nothing else
- cached, not cheaper
- DOM node count is untouched
- fetching and bundle live outside render
- some useMemo encoded identity, not cost
basics
~20 sThe React Compiler removes only wasted re-rendering of pure components whose inputs did not change. Genuinely changed inputs, expensive DOM volume, data-fetching waterfalls, bundle size, and slow effects or event handlers all cost exactly what they did before.
solid answer
~50 sAuto-memoization attacks one cost: re-running a component and its subtree when nothing it depends on changed. Everything else survives. If the input genuinely changed, the expensive computation still runs — the compiler caches, it does not make the work cheaper. Rendering ten thousand rows still creates ten thousand DOM nodes, so you still virtualize. Fetch waterfalls, oversized payloads and slow endpoints are untouched, and the plugin adds a small amount of code rather than removing any. Work inside event handlers, effects and layout effects is outside the memoized render path entirely. It also cannot fix state that lives too high in the tree when that state actually changes every keystroke — the update is real, so the render is real. And it does not remove the need to measure: the payoff is proportional to how much wasted rendering existed, which in a well-composed app can be close to nothing.
go deeper
Know the boundary in one sentence: the compiler avoids repeating render work when nothing changed, and it does nothing about network requests, bundle size, or how many DOM nodes you create.
Distinguish caching from cheapening — a genuinely changed input still pays full price — and place event handlers, effects and layout work outside the memoized render path.
Show a diagnosis order: profile the real interaction, decide whether the renders were wasted or genuine, and only then choose between structural change, virtualization, deferring work, or fixing data loading. Treat the compiler as a floor, not a result.
Push back on the compiler as a performance strategy: set the budget and the measurement first, so the team can tell whether adoption actually moved an interaction, and keep composition and state placement as the practices that scale beyond what auto-memoization can prune.
## Name the cost the compiler removes One cost, precisely: re-executing a component function and rebuilding its subtree when the values that component reads are unchanged. The compiler caches derived values and JSX elements, and by preserving element identity it lets React bail out of subtrees. That is the whole win. Every performance problem outside that sentence is unchanged. That is worth saying out loud in an interview, because the failure mode being probed is a team that treats the compiler as a general-purpose "make React fast" switch and stops investigating. ## Costs it does not touch **Work whose inputs really changed.** Caching helps when nothing changed. Sort a 50,000-element list keyed off a value that updates on every keystroke and the sort runs on every keystroke, compiled or not. The fix is algorithmic — index it, paginate it, debounce the input, move it off the main thread — not more memoization. **DOM volume.** Mounting and updating thousands of nodes costs layout and paint that no render cache avoids. Windowing the list remains the answer. **Everything about data.** Request waterfalls, over-fetching, missing cache, slow endpoints, oversized JSON: the compiler operates on render, and none of this is render. **Bundle size and startup.** The compiler ships a little more code than you wrote — cache slots and comparisons are code. It is a runtime-render optimisation with a marginal payload cost, so it will not improve time-to-interactive on a heavy bundle. **Effects, event handlers and layout work.** A handler that does expensive work on every keypress runs on every keypress. An effect that re-subscribes too often, a layout measurement forcing synchronous reflow, an animation fighting the main thread — all outside the compiled render path. **Genuinely frequent state updates.** If a value changes on every frame, every consumer of it re-renders on every frame; nothing was wasted, so nothing can be pruned. Restructuring — moving that state down to the smallest component that needs it, or deferring the expensive consumer — is the actual fix. **Uncompiled code at the seams.** A third-party component the compiler did not process, or one it bailed out on, does not gain stable identity from your side of the boundary. ## Memoization you should not delete A subtler point. Not every `useMemo` in a codebase was ever about render cost. Some exist for *identity semantics*: a value used as a `WeakMap` key, an object handed to a non-React library that caches by reference, or a mutable container something else holds onto. Those calls encode a requirement, not an optimisation, and deleting them because "the compiler handles memoization now" can change behaviour. `useRef` is usually the clearer expression of that intent — but re-read the call site before you sweep it away. ## What replaces the deleted work The habit worth keeping is the one that existed before memo hooks: composition. Moving state down, passing an expensive subtree as `children` so it keeps identity, splitting a component so a fast-changing value re-renders less of the tree — these reduce the *amount* of rendering rather than caching its result, and they still pay off in a compiled app. The compiler is a floor under sloppy identity handling, not a substitute for structure. ## How to answer well Start by naming the single cost it removes, then enumerate categories rather than examples: work that genuinely changed, DOM volume, data, bundle, non-render work, and the seams. Close on process — you profile a real interaction before and after, because a well-composed app may show almost no gain, and "we enabled the compiler" is not a performance result until something measured got faster.
- If a component still feels slow after enabling the compiler, what do you check first?Whether the renders are actually wasted. Profile the interaction and look at what changed: if the component's inputs genuinely change on every update, no cache can help and the answer is structural — move the state closer to where it is used, defer the expensive part, or reduce the work itself. Only if inputs are stable and it still re-renders do you ask whether that component compiled at all.
- Are there useMemo calls you would keep even in a fully compiled codebase?Yes — the ones that were never about speed. A value used as a WeakMap key, an object a non-React library caches by reference, or anything whose identity is part of a contract encodes a requirement rather than an optimisation. Often useRef states that intent more honestly, but the point is to read the call site instead of sweeping every memo hook out on principle.
- Does the compiler change how you think about where state lives?It lowers the penalty for getting it slightly wrong, because a cascade of unchanged subtrees is now pruned automatically. It does not change the principle: state that changes often should live as close as possible to what consumes it, since a real change produces a real render that no cache removes.
saying these in an interview costs you the question
- Believes the compiler makes an expensive computation itself faster
- Expects it to fix slow data fetching or large bundles
- Claims virtualization is unnecessary once components are compiled
- Assumes work in event handlers and effects is memoized too
- Reports the rollout as a win with no before-and-after measurement