Performance Optimization
Lazy-load routes, memoize components and callbacks, and virtualize long lists to keep React apps fast.
Frontend performance comes down to shipping less JavaScript, doing less work on render, and rendering fewer DOM nodes. The four highest-leverage React techniques are code splitting (lazy-loading routes), React.memo (skipping re-renders when props are unchanged), useCallback (stabilizing function identity so memoized children don't re-render), and list virtualization (only rendering the rows currently on screen).
Lazy loading routes uses React.lazy + dynamic import() so each route becomes its own bundle chunk, loaded on demand and wrapped in a <Suspense> boundary with a fallback. This shrinks the initial bundle and speeds up first paint.
React.memo wraps a component so it re-renders only when its props change by shallow comparison. It pairs with useCallback, which memoizes a function so its identity stays stable across renders — without it, a freshly created handler passed as a prop would defeat React.memo on the child.
Virtualized lists (e.g. react-window) render only the visible window of a large list instead of thousands of DOM nodes. For 1000+ rows this is the difference between a janky and a smooth scroll. Reach for these tools when you measure a problem — premature memoization adds complexity without payoff.
Code splitting is lazy class loading / on-demand module initialization — you don't load every JAR into memory at boot, you load what a request actually needs. React.memo and useCallback are like a memoization cache or @Cacheable: skip recomputation when inputs are unchanged. List virtualization is pagination/streaming a result set instead of loading a million-row table into a List at once.
- Code splitting with React.lazy + Suspense turns each route into its own chunk, cutting the initial bundle.
- React.memo only helps if props are referentially stable — combine it with useCallback/useMemo for function and object props.
- Virtualize lists past a few hundred rows; rendering only the visible window keeps the DOM node count flat regardless of data size.
- Optimize what you measure: profile first, then memoize, because needless memoization adds memory and complexity for no gain.
Worked Code
// 1. Lazy loading routes (code splitting)
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Analytics = lazy(() => import('./pages/Analytics'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Suspense>
);
}// 2. React.memo — skip re-render if props unchanged
const ExpenseCard = React.memo(function ExpenseCard({ expense }: Props) {
return <div>{expense.category}: ₹{expense.amount}</div>;
});// 3. useCallback — memoize functions passed as props
function Parent() {
const handleApprove = useCallback((id: number) => {
dispatch(approveExpense(id));
}, [dispatch]);
return <ExpenseList onApprove={handleApprove} />;
}// 4. Virtualized lists (for 1000+ items)
import { FixedSizeList } from 'react-window';
function VirtualizedExpenses({ items }: { items: Expense[] }) {
return (
<FixedSizeList height={600} itemCount={items.length} itemSize={60} width="100%">
{({ index, style }) => (
<div style={style}>
{items[index].category}: ₹{items[index].amount}
</div>
)}
</FixedSizeList>
);
}Interview-Ready Q&A
Code splitting breaks the bundle into chunks loaded on demand, so the initial download and parse cost is smaller and first paint is faster. In React you use React.lazy with a dynamic import() for route or heavy components and wrap them in a <Suspense> boundary with a fallback. Each lazy import becomes its own chunk that the bundler loads only when the component is rendered.
- 1Four levers: lazy-load routes, React.memo, useCallback/useMemo, virtualize lists.
- 2React.memo needs referentially stable props (useCallback/useMemo) to actually skip renders.
- 3Measure before optimizing — premature memoization costs memory and clarity with no benefit.