Virtualization in React: The Technique for Handling 100,000 Data Rows Without Lag
You have a 50,000-row list to display in React. If you render everything at once, the browser has to build tens of thousands of DOM nodes—CPU and RAM struggle, the UI stutters badly, and Core Web Vitals plummet. Virtualization (or Windowing) solves this with a simple yet extremely effective principle.

Trung Vũ Hoàng
Author
1. What Is Virtualization?
Instead of rendering the entire list, Virtualization renders only the items currently inside the user’s visible area (viewport)—usually 10–20 items. When the user scrolls, old items are removed from the DOM and new items are inserted immediately.
The result: even with 100,000 records in your data, the real DOM still contains only about 10–15 nodes. UI response time drops from seconds to just a few milliseconds.
2. How It Works
A Virtualization library continuously calculates the scroll position to determine which items should be displayed. It also maintains a buffer of a few items outside the viewport—to prevent “blank gaps” when users scroll quickly before new rows have time to render.
From React’s perspective: Virtualization drastically reduces the number of nodes the reconciliation algorithm has to process after each re-render—making it a perfect complement to React’s Diffing mechanism.
3. Implementing with react-window
react-window is the go-to library for Virtualization in React—lighter and faster than its predecessor react-virtualized. Install it:
npm install react-windowExample: a 100,000-item list with fixed row height:
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) => (
<div style={style} className={index % 2 ? 'row-odd' : 'row-even'}>
Item #{index + 1}
</div>
);
const MyList = () => (
<List
height={500} // Viewport height (px)
itemCount={100000} // Total items
itemSize={50} // Row height (px)
width="100%"
>
{Row}
</List>
);With this configuration, only about 10–12 DOM nodes actually exist in the DOM at any time—no matter how large itemCount is.
4. Real-World Use Cases
Two-dimensional data grid
A table with hundreds of columns and thousands of rows—needs virtualization both horizontally and vertically. react-window provides FixedSizeGrid for this case.
Message list (Chat)
Scrolling back to older messages with uneven message heights—use VariableSizeList combined with measured-height caching.
Large searchable dropdown
A select box listing provinces/states, countries combined with a search input—Virtualization ensures the input doesn’t lag even when the list has thousands of options.
5. Important Notes
Use fixed height if possible:
FixedSizeListis much simpler and faster thanVariableSizeList. Design your UI with uniform row heights whenever you can.Cache heights with VariableSizeList: Don’t measure the DOM on every scroll—cache measured results to avoid layout thrashing.
Accessibility (A11y): Ensure screen readers can still navigate the list—test with VoiceOver and NVDA.
Combine with useMemo: Wrap the data passed into the List in
useMemoto avoid recreating arrays and triggering full re-renders.
Conclusion
Virtualization is the line between an amateur web app and a professional product. The principle is simple—render only what the user can see—but the performance impact is extraordinary:
DOM nodes: from tens of thousands down to ~15.
Render time: from seconds down to a few milliseconds.
Memory footprint: significantly reduced, with far less pressure on the Garbage Collector.
Any application that displays a list longer than 100 items should consider using Virtualization.
Frequently Asked Questions
Bài viết liên quan

Zustand Async: 5 Effective Ways to Handle Async in React
Every real-world React app needs to communicate with async APIs. If handled poorly, it’s easy to run into issues like frozen UI, race conditions, memory leaks, or showing stale data. Zustand solves this with an extremely simple syntax—define async actions directly in the store, without complex middleware like Redux Thunk or Saga.

Advanced Promises: all, allSettled, race, any — When to Use Which?
If you only use sequential async/await, your app is wasting performance potential — each request has to wait for the previous one to finish before it can start. Promise’s static methods (all, allSettled, race, any) let you orchestrate multiple async tasks in parallel using the strategy that fits each problem.

Zustand Async: 5 Effective Ways to Handle Async Operations in React
Every real-world React app has to communicate with asynchronous APIs. If managed poorly, your app can run into issues like a frozen UI, race conditions, memory leaks, or showing stale data. Zustand solves this with an incredibly simple syntax — define async actions directly in the store, without complex middleware like Redux Thunk or Saga.