Detailed explanation
I optimise a React app by measuring a slow user interaction first, rather than adding memoisation everywhere. React DevTools Profiler shows which components commit updates and how much rendering work they take; browser performance tools reveal JavaScript execution, layout, painting, and network bottlenecks. I reproduce the problem with realistic data and check production-like conditions because development checks, including Strict Mode, can distort observations. The right fix depends on whether the issue is initial loading, repeated rendering, expensive computation, or too many DOM nodes.
For unnecessary rendering, I first improve state placement and component boundaries. Keeping frequently changing state close to its consumers prevents unrelated ancestors and siblings from being pulled into updates. Broad context updates can also cause excessive rendering, so separating contexts by responsibility or using selector-based subscriptions can help. React.memo can skip a child render when its props are unchanged, comparing each prop with Object.is by default. However, the child's own state changes and consumed context updates still trigger renders. New object, array, or function props can defeat that comparison, making stable references useful on measured hot paths.
I use useMemo to cache an expensive calculation, such as transforming a dataset, and useCallback when a stable function reference benefits a memoised child or another dependency-sensitive API. Both require correct dependencies; neither should be needed for correctness. Memoisation adds overhead, so I verify that it actually improves performance. For large lists, virtualisation renders only the visible window and a small buffer, reducing reconciliation, DOM, and layout work. Stable keys identify items across insertions and reordering; they preserve component identity but do not independently prevent renders.
For loading performance, route-level code splitting with dynamic imports and React.lazy defers code until needed, while Suspense provides a loading fallback. I also inspect oversized dependencies, images, and request waterfalls. For expensive non-urgent updates, useTransition or useDeferredValue can keep urgent interactions responsive, though they do not make the underlying calculation cheaper. Finally, I repeat the same measurement after each change and check correctness. Interviewers value this evidence-driven sequence more than a list of optimisation APIs.
Key points to remember
- Profile the actual bottleneck and compare before-and-after measurements.
- Localise state and narrow context subscriptions to limit update propagation.
- Use React.memo, useMemo, and useCallback selectively, with correct dependencies.
- Virtualise long lists and use stable, data-derived keys.
- Split code for faster loading; defer non-urgent updates for responsiveness.
Common mistakes candidates make
- Memoising every component and calculation without measuring the benefit.
- Omitting dependencies to keep references stable, causing stale values or callbacks.
- Assuming stable keys prevent renders, or that transitions reduce computation cost.
Likely follow-up questions
What is the difference between React.memo, useMemo, and useCallback?
React.memo can skip rendering a component when its props are unchanged. useMemo caches a calculation's result, while useCallback caches a function reference rather than its execution result.
Why can a memoised component still render?
Its own state or a context it consumes may have changed. A parent can also pass a new object or function reference, causing the default prop comparison to fail; memoisation is an optimisation, not a rendering guarantee.
How would you optimise a table containing 10,000 rows?
I would profile it, virtualise the rows, and use stable row IDs as keys. If filtering or sorting is expensive, I would measure that separately and consider memoisation, server-side processing, or a worker depending on the workload.
Interview tip: Structure your answer as measure, identify the bottleneck, apply a targeted fix, and verify the result, supported by one concrete example.
