Most React apps are fast enough without any optimization. The real skill is knowing when performance actually matters and reaching for the right tool instead of wrapping everything in memo and hoping for the best. This guide covers the techniques that make a measurable difference in production -- from memoization and virtualization to the React Compiler that automates most of it for you.
Master react performance optimization
Take the React Performance Deep Dive course with hands-on lessons and challenges.
The default shallow comparison fails when the parent rebuilds the order object on every render. A custom comparator checks only the fields the component actually displays, avoiding unnecessary re-renders without requiring the parent to memoize the entire object.
Filtering and sorting 10,000 products on every keystroke is expensive. useMemo ensures the pipeline only reruns when the inputs (products, search, sortBy, minRating) actually change. Typing in an unrelated input field won't retrigger this computation.
Without useCallback, handleToggle and handleDelete are new function instances on every render, which means every TaskItem re-renders even though memo wraps it. useCallback preserves the same function reference, so only the TaskItem whose task prop actually changed will re-render.
Rendering 10,000 log entries as DOM nodes freezes the browser. TanStack Virtual renders only the ~20 visible rows plus a 10-row overscan buffer. The user scrolls at 60fps because the DOM node count stays constant regardless of dataset size.
Route-based code splitting is the highest-impact optimization for initial load time. Each page becomes its own JS chunk. The NavLink component prefetches the chunk on hover, so by the time the user clicks, the code is already cached. This is the simplest way to cut your initial bundle in half.
The React Compiler (available in React 19) analyzes your components at build time and inserts memo, useMemo, and useCallback where beneficial. You write straightforward code without manual optimization, and the compiler does the work. Use 'use no memo' to opt out components that violate the Rules of React (mutations during render, side effects in the render path). For Next.js, it's a single config flag.
Wrapping every component in memo without profiling first. Developers add memo defensively across the entire codebase, creating overhead from shallow-comparison checks on components that render cheaply or always receive new props anyway.
Profile with React DevTools Profiler. Only apply memo to components that (1) re-render frequently, (2) are expensive to render, and (3) receive props that can realistically be stabilized. A component that always gets new children or inline objects from its parent won't benefit from memo.
Using useCallback without a memoized child consuming it. If the function is passed to a regular (non-memoized) component, wrapping it in useCallback adds overhead with zero benefit since the child re-renders regardless.
useCallback only prevents re-renders when paired with memo on the child. If the child isn't memoized, skip useCallback. The exception is when the callback appears in a useEffect dependency array, where a stable reference prevents the effect from re-running.
Putting all application state into a single React context. Every state change re-renders every consumer -- even consumers that only read a different slice of the context value.
Split contexts by update frequency. Put user/auth data (rarely changes) in one context and real-time data (frequently changes) in another. For fine-grained subscriptions, use external state libraries like Zustand or Jotai that support selectors out of the box.
Memoizing trivial computations with useMemo. Wrapping `count * 2` or string concatenation in useMemo adds the cost of dependency comparison and cache storage for something that takes microseconds to compute.
Reserve useMemo for genuinely expensive work: filtering/sorting large arrays, complex formatting pipelines, or creating objects that serve as stable references for memoized children. Inline arithmetic and string operations should be computed directly.
React performance optimization is about knowing when to act and picking the right tool. Start by profiling with React DevTools -- most components don't need optimization. When they do, reach for memo (components), useMemo (expensive computations), and useCallback (stable function references for memoized children). Virtualize long lists with TanStack Virtual, split bundles with lazy(), and structure your component tree so state changes don't cascade. With React 19, the React Compiler automates most memoization at build time, letting you write clean code without manual optimization boilerplate.
Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.
Interactive lessons and challenges, right in your code editor.
Check the free courses. No credit card.