By Rahul — Google Frontend Engineer
Rule 1: Measure Before Optimizing
Never optimize blindly. Use React DevTools Profiler to identify what is actually slow. Most performance problems come from unnecessary re-renders, not slow computations.
Preventing Unnecessary Re-renders
React.memo
// Without memo: re-renders every time parent renders
const ExpensiveList = ({ items }) => {
return items.map(item => <Item key={item.id} data={item} />);
};
// With memo: only re-renders when items prop changes
const ExpensiveList = React.memo(({ items }) => {
return items.map(item => <Item key={item.id} data={item} />);
});useMemo and useCallback
function SearchResults({ query, data }) {
// BAD: filters on every render
const filtered = data.filter(item => item.name.includes(query));
// GOOD: only recalculates when query or data changes
const filtered = useMemo(
() => data.filter(item => item.name.includes(query)),
[query, data]
);
// BAD: new function reference every render breaks memo on child
const handleClick = (id) => selectItem(id);
// GOOD: stable reference
const handleClick = useCallback((id) => selectItem(id), []);
return <MemoizedList items={filtered} onClick={handleClick} />;
}Code Splitting
// BAD: entire admin panel loaded on home page
import AdminPanel from './AdminPanel';
// GOOD: loaded only when needed
const AdminPanel = React.lazy(() => import('./AdminPanel'));
function App() {
return (
<Suspense fallback={<Loading />}>
<AdminPanel />
</Suspense>
);
}Virtualization for Long Lists
// Rendering 10,000 items = 10,000 DOM nodes = slow
// Virtualization renders only visible items (~20 DOM nodes)
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }) {
const parentRef = useRef(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} style={{ height: 400, overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map(row => (
<div key={row.key} style={{
position: 'absolute',
top: row.start,
height: row.size,
}}>
{items[row.index].name}
</div>
))}
</div>
</div>
);
}State Management
// BAD: all state at the top, every change re-renders everything
function App() {
const [theme, setTheme] = useState('dark');
const [user, setUser] = useState(null);
const [cart, setCart] = useState([]);
// Changing theme re-renders Cart component
return (
<Header user={user} />
<Cart items={cart} />
);
}
// GOOD: colocate state, use context for truly global state
// Or use a state manager like Zustand with selectorsProduction Checklist
- Profile first — identify actual bottlenecks
- Virtualize lists over 100 items
- Code-split routes and heavy components
- Memoize expensive computations
- Use production builds (React dev mode is 10x slower)
- Avoid anonymous objects/arrays as props:
style={{color: 'red'}}creates new object every render
Summary
Measure first. Prevent unnecessary re-renders with memo, useMemo, useCallback. Code-split aggressively. Virtualize long lists. Colocate state. These patterns handle 95% of React performance issues.