Irbisa · Cheatsheet

Performance

React.memo, useMemo and useCallback, React Compiler, code splitting and lazy, list virtualization, the Profiler, Core Web Vitals, bundle size

Senior Developer · 20 questions

  1. 01

    What are the main ways to make a React app faster, and in what order would you apply them?

    Measure first, then cut work in this order: ship less JavaScript (code splitting, lighter dependencies), render less (state kept low, virtualized lists, memoization where the Profiler shows waste) and keep the main thread free for input (transitions, deferred values).

  2. 02

    What does React.memo do, and when does a memoized component still re-render?

    React.memo wraps a component so React skips re-rendering it when the parent re-renders with props that are shallowly equal (Object.is per prop) to the previous ones.

    interface RowProps {  label: string;  onSelect: (label: string) => void;} const Row = memo(function Row({ label, onSelect }: RowProps) {…
  3. 03

    Find the bug: Chart is wrapped in memo, yet the Profiler shows it re-rendering on every keystroke in the search box.

    const Chart = memo(function Chart({ options, onPointClick }: ChartProps) {  return <canvas data-type={options.type} onClick={() => onPointClick(0)} />;}); function Dashboard({ data }: { data: number[] }) {  const [query, setQuery] = useState('');  return (    <>      <input value={query} onChange={(e) => setQuery(e.target.value)} />      <Chart        options={{ type: 'line', data }}        onPointClick={(i) => console.log(data[i])}      />    </>  );}

    Dashboard creates a new options object and a new onPointClick arrow function on every render, so the shallow comparison in memo always sees changed props and never skips.

  4. 04

    What does memoization cost, and when do React.memo, useMemo or useCallback make things worse?

    Memoization trades CPU for memory and complexity: every render still runs the prop or dependency comparison and keeps the old value alive.

  5. 05

    How can you stop needless re-renders without memoization, by moving state down or passing components as children?

    A state update re-renders only the component that owns the state and its descendants, so keep fast-changing state in the smallest component that needs it.

    // before: every scroll event re-renders ExpensiveTreefunction SlowPage() {  const [scrollY, setScrollY] = useState(0);  return (    <div onScroll={(e) => setScrollY(e.currentTarget.scrollTop)}>      <Header shadow={scrollY > 0} />…
  6. 06

    What is the React Compiler, and does it make useMemo, useCallback and React.memo unnecessary?

    The React Compiler (stable since 1.0 in October 2025) is a build-time Babel plugin that analyses components and hooks and inserts fine-grained memoization automatically.

  7. 07

    How do you find out which components re-render and why, using the React DevTools Profiler?

    Record the slow interaction in the React DevTools Profiler, then read each commit's flame graph: grey bars did not render, coloured ones did, with the time each took.

    function onRender(  id: string,  phase: 'mount' | 'update' | 'nested-update',  actualDuration: number,  baseDuration: number,) {…
  8. 08

    What is code splitting, and how do React.lazy and Suspense load a component on demand?

    Code splitting breaks the bundle into chunks that load when needed instead of up front.

    const SettingsPage = lazy(() => import('./SettingsPage'));const preloadSettings = () => import('./SettingsPage'); export function App() {  const [open, setOpen] = useState(false);  return (…
  9. 09

    Find the bug: the lazy editor flashes its fallback and loses everything typed each time the parent re-renders.

    function NotePage({ noteId }: { noteId: string }) {  const [saved, setSaved] = useState(false);  const Editor = lazy(() => import('./Editor'));   return (    <Suspense fallback={<p>Loading editor...</p>}>      <Editor noteId={noteId} onSave={() => setSaved(true)} />      {saved && <p>Saved</p>}    </Suspense>  );}

    lazy is called inside the component, so every render creates a new component type.

  10. 10

    Find the bug: the chart is loaded with lazy, yet the whole chart library is still in the main bundle.

    // Dashboard.tsximport { lazy, Suspense, useState } from 'react';import { CHART_COLORS } from './RevenueChart'; const RevenueChart = lazy(() => import('./RevenueChart')); export function Dashboard() {  const [show, setShow] = useState(false);  return (    <div style={{ borderColor: CHART_COLORS.primary }}>      <button onClick={() => setShow(true)}>Show revenue</button>      {show && (        <Suspense fallback={<p>Loading chart...</p>}>          <RevenueChart />        </Suspense>      )}    </div>  );}

    Dashboard also imports CHART_COLORS statically from ./RevenueChart, so that module, with the chart library it imports, has to be in the same chunk as Dashboard.

  11. 11

    What is list virtualization (windowing), when do you need it, and how does it work?

    Virtualization renders only the rows visible in the scroll viewport plus a small overscan, positioned inside a spacer as tall as the full list.

    function LogList({ lines }: { lines: string[] }) {  const parentRef = useRef<HTMLDivElement>(null);  const virtualizer = useVirtualizer({    count: lines.length,    getScrollElement: () => parentRef.current,    estimateSize: () => 24,…
  12. 12

    What are the Core Web Vitals (LCP, INP, CLS), and what in a React app usually hurts each one?

    Core Web Vitals are Google's three field metrics, judged at the 75th percentile of real visits: LCP (main content shown, good under 2.5 s), INP (response to input, good under 200 ms, replaced FID in 2024) and CLS (layout stability, good under 0.1).

  13. 13

    What do <link rel="preload">, prefetch and preconnect do, and how do React 19's preload and preinit APIs use them?

    They announce requests before the browser would find them: preload fetches a file the current page needs, prefetch fetches one the next navigation may need at idle priority, and preconnect opens a connection to an origin early.

    import { preconnect, prefetchDNS, preinit, preload } from 'react-dom';import { Link } from 'react-router'; export function CheckoutPage() {  // called during render: with SSR they go out in the HTML before the form needs them  preconnect('https://api.payments.example');…
  14. 14

    How do you load web fonts without invisible text or layout shift?

    Show text in a fallback font at once with font-display: swap (or optional), get the font file early by self-hosting a small WOFF2 and preloading it, and adjust the fallback's metrics with size-adjust so the swap does not move the text.

    /* in <head>, for the one file used above the fold:   <link rel="preload" href="/fonts/inter-latin.woff2" as="font" type="font/woff2" crossorigin> */ @font-face {  font-family: 'Inter';  src: url('/fonts/inter-latin.woff2') format('woff2');…
  15. 15

    When would you move work to a Web Worker in a React app, and how do you talk to it?

    When pure computation blocks the main thread for more than about 50 ms: parsing a large CSV or JSON, sorting or filtering tens of thousands of rows, building a search index, processing an image.

    // sort.worker.ts: runs on its own thread, no DOM and no Reactself.onmessage = (e: MessageEvent<Row[]>) => {  const sorted = [...e.data].sort(compareRows); // the 300 ms part  self.postMessage(sorted);};…
  16. 16

    Why does a click or keystroke feel slow (poor INP) in a React app, and how do you break up long rendering work?

    INP measures the delay from an input to the next paint, and it is poor when a long task blocks the main thread: usually a large synchronous re-render, heavy work in the event handler or third-party scripts.

    function ReportPage({ rows }: { rows: Row[] }) {  const [tab, setTab] = useState<'summary' | 'details'>('summary');  const [isPending, startTransition] = useTransition();   function selectTab(next: 'summary' | 'details') {    // the heavy tab renders in interruptible slices…
  17. 17

    What does the <Activity> component added in React 19.2 do, and how can it make navigation feel faster?

    <Activity mode="hidden"> hides a subtree without unmounting it: React keeps its state and DOM, cleans up its effects and renders updates to it at the lowest priority.

    function Tabs({ tab }: { tab: 'feed' | 'profile' }) {  return (    <>      <Activity mode={tab === 'feed' ? 'visible' : 'hidden'}>        <Feed />      </Activity>…
  18. 18

    How do you measure a React app's performance in production rather than on your own machine?

    Collect real-user metrics (RUM): the web-vitals library reports LCP, INP and CLS from each visitor's browser, you send them with navigator.sendBeacon to an analytics endpoint and watch the 75th percentile per page and device class.

    import { onCLS, onINP, onLCP, type Metric } from 'web-vitals'; function send(metric: Metric) {  const body = JSON.stringify({    name: metric.name,    value: metric.value,…
  19. 19

    How do you find out what makes a React bundle large, and how do you shrink it?

    Measure with a bundle visualizer first (rollup-plugin-visualizer for Vite, webpack-bundle-analyzer, or source-map-explorer on the built source maps), then cut the biggest items: heavy dependencies, code that belongs in lazy chunks and code that tree shaking failed to remove.

  20. 20

    A table with 5,000 rows freezes on every keystroke in its filter box. Walk through how you would diagnose and fix it.

    Reproduce it with CPU throttling, record the keystroke in the React Profiler and the Performance panel to see whether the time goes to React rendering, layout or your own code, then fix the biggest cost, which is usually rendering all 5,000 rows on every key press.