Learn the concept
Performance Optimization
List virtualization renders only the items currently visible in the viewport plus a small overscan buffer, replacing thousands of DOM nodes with a handful. This dramatically reduces initial render time, memory usage, and DOM size for large lists.
When rendering large lists (hundreds or thousands of items), creating a DOM node for every item causes significant performance problems: slow initial render, high memory usage, sluggish scrolling, and poor Interaction to Next Paint (INP). Virtualization (also called windowing) solves this by only rendering items visible in the viewport.
transform: translateY() to appear at the correct scroll offsetThis means a list of 10,000 items might only have 20-30 actual DOM nodes at any time.
| Library | Strengths | |---------|----------| | @tanstack/react-virtual | Headless, hooks-based, supports horizontal/grid virtualization, TypeScript-first | | react-window | Lightweight (6KB), simple API, successor to react-virtualized | | react-virtuoso | Auto-measures variable heights, built-in grouping, chat-style reverse scrolling | | react-virtualized | Feature-rich but large (25KB+), supports tables/grids/masonry |
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
function VirtualList({ items }: { items: string[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // Estimated item height in px
overscan: 5, // Render 5 extra items above/below viewport
});
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
{/* Spacer div creates the full scrollable height */}
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{items[virtualItem.index]}
</div>
))}
</div>
</div>
);
}
// Renders 10,000 items with only ~15 DOM nodes
<VirtualList items={Array.from({ length: 10000 }, (_, i) => `Item ${i}`)} />Product listing pages with thousands of items use virtualization to render only visible products, enabling infinite scroll without degrading performance.
Messaging apps virtualize conversation history with reverse scrolling, loading older messages as users scroll up while keeping recent messages in view.
Build a data grid that virtualizes both rows and columns, supporting 10,000+ rows with sortable headers, row selection, and sticky first column.