Rendering Performance — Cheat Sheet

FE Lead prep · ref 07 · the runtime half of perf → INP

The frame — one main thread

JS + layout + paint + your handlers all share ONE thread. Busy thread = no input, no paint = jank / bad INP. Long task >50ms · frame budget ~16ms.
Three moves:  1. Render less (memo, virtualize) · 2. Render rarely (debounce/throttle, rAF, transitions) · 3. Render elsewhere (Web Worker, OffscreenCanvas, yield).

Render less — re-renders

const Row = React.memo(({row,onPick})=>…);
const onPick = useCallback(id=>pick(id),[]);
rows.map(r=><Row key={r.id} row={r} onPick={onPick}/>)

Render less — virtualize

useVirtualizer({count, getScrollElement,
  estimateSize:()=>96, overscan:8})
// .getVirtualItems() → just on-screen rows

Render rarely — debounce / throttle / rAF / transitions

ToolFiresUse for
Debounceonce after events go quiet (N ms)search box → call API when typing pauses
Throttleat most once per interval during a streamscroll / resize / mousemove readout
requestAnimationFrameonce per frame, before paintvisual work — scroll-linked anim, sticky header
useTransition / useDeferredValuerender now but non-urgent, interruptiblelaggy type-to-filter — keeps input responsive, drops no keystrokes
One line: debounce = once AFTER quiet · throttle = steady DURING stream · rAF = align to paint · transition = change render priority. They're orthogonal.
useDeferredValue vs debounce — NOT substitutes: deferred = render priority of data you already have (no dropped keystrokes); debounce = how often a side effect / API call fires (drops values). useDeferredValue doesn't suppress effects → fetch still fires per committed value. Autocomplete hitting a server = BOTH (debounce the request + defer the render).

Render elsewhere — off the main thread

Diagnose first (Lesson 04)

Lead one-liners (memorize)

Frame  “One thread does JS, layout, paint AND my click handlers — rendering perf is three moves on it: render less, render rarely, render elsewhere.”
Re-render  “Re-renders fire on state, parent, or context — not props alone. memo/useMemo/useCallback stop the wasted ones but cost too, so I measure first; the React Compiler increasingly does it for me.”
Virtualize  “A 1,000-result list shouldn't be 1,000 DOM nodes — I window it. Cost is find-in-page, a11y, SEO, so for indexed content I'd use content-visibility instead.”
Rarely  “Debounce fires once after typing stops; throttle at a steady rate during a stream; rAF aligns to the frame. For a laggy filter I reach for useDeferredValue — responsive, no dropped keys.”
Elsewhere  “Genuinely heavy CPU work — no memo saves you — goes to a Web Worker so the main thread stays free; if I can't move it, I chunk it and yield.”

Sources: react.dev — render & commit · react.dev — useMemo · react.dev — React Compiler · web.dev — virtualize · web.dev — content-visibility · web.dev — long tasks · react.dev — useTransition · MDN — Web Workers

Don't fail the interview

1. Re-render triggers = state / parent / context. Props alone don't — they change because parent re-rendered, and parent re-render re-renders ALL children.
2. Memoization is NOT free (comparison + memory + stale-dep bugs). Measure first, memoize the hot path. React Compiler (19) auto-memoizes — stop hand-wrapping everything.
3. Never index-as-key on reordering/filtering lists → state sticks to wrong row + breaks reconciliation. Stable ID from data.
4. Big list → virtualize. But it breaks Ctrl-F / a11y / SEO → don't virtualize indexed SSR content; use content-visibility.
5. Debounce ≠ throttle ≠ rAF ≠ transition. Laggy filter → useDeferredValue (no dropped keystrokes), not a 1s debounce. But useDeferredValue does NOT replace debounce for the network — it doesn't suppress effects, so the fetch still fires per value. Server autocomplete = debounce the request AND defer the render.
6. 200ms compute → Web Worker / yield. useMemo just caches — it still runs on the main thread and still freezes.
7. Worker cost = startup + structured-clone serialization across the boundary + no DOM. Don't ping-pong big objects; use transferables / SharedArrayBuffer.