If you have ever built a React app that needs to display a huge list, thousands of search results, a massive data table, or an endless activity feed, you have probably seen the same thing happen: the UI starts to drag.
The culprit usually is not React. It is the DOM. And one of the cleanest fixes is a technique called windowing, implemented with the lightweight library react-window.
The real problem with long lists
Rendering a list of 10,000 items means the browser has to create, layout, and paint 10,000 DOM nodes even though the user can only see a tiny slice of them. That cost shows up in three predictable ways.
1 Slow initial render
The browser has to mount and paint thousands of elements up front.
2 High memory usage
Off-screen items still consume memory because they remain mounted.
3 Laggy scrolling
The browser has more layout and paint work to do as the list moves.
Small lists can get away with a plain .map(). Large lists usually cannot.
The fix: virtualization
react-window uses virtualization, also called windowing. Instead of rendering every item, it renders only the rows currently visible in the viewport plus a small overscan buffer.
So if your dataset contains 10,000 rows but the user only sees 20 at a time, the DOM only holds roughly those visible rows. As the user scrolls, old rows are recycled out and new rows are mounted in their place.
That is why the library feels fast even with huge datasets: the total data size can be massive while the active DOM stays small.
The four core primitives
| Component | Best use case |
|---|---|
| FixedSizeList | One-dimensional lists where every row has the same height |
| VariableSizeList | Lists where row heights differ |
| FixedSizeGrid | Two-dimensional grids with uniform cell sizes |
| VariableSizeGrid | Grids with variable row or column dimensions |
A basic fixed-size list
The most common entry point is FixedSizeList. You give it the viewport height, total item count, row height, and a row renderer.
import { FixedSizeList } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>Row {index}</div>
);
const MyList = () => (
<FixedSizeList
height={500}
itemCount={10000}
itemSize={35}
width="100%"
>
{Row}
</FixedSizeList>
);
The critical detail is the style prop. React Window uses inline positioning styles to place each visible row. If you do not apply that prop to the row's outer element, the list breaks immediately.
Variable-size rows
When item heights are not uniform, switch to VariableSizeList and provide a function that returns the height for each index.
import { VariableSizeList } from 'react-window';
const itemHeights = [35, 60, 45, 80, 35];
const Row = ({ index, style }) => (
<div style={style}>
Row {index} - height {itemHeights[index]}px
</div>
);
const MyList = () => (
<VariableSizeList
height={500}
itemCount={itemHeights.length}
itemSize={(index) => itemHeights[index]}
width="100%"
>
{Row}
</VariableSizeList>
);
Where it shines
React Window vs React Virtualized
| Dimension | React Window | React Virtualized |
|---|---|---|
| Bundle size | Smaller, around 6 KB | Larger, around 30 KB |
| API shape | Simpler and easier to learn | Broader but more complex |
| Dynamic sizing support | Limited | More advanced |
| Best fit | Most everyday list and grid cases | More specialized layouts and edge cases |
For most teams, react-window is the pragmatic default. If you need heavier features such as masonry-style layouts or more automatic measurement, then a larger abstraction may be worth the tradeoff.
Practical gotchas
1 Always apply style
Without the provided inline style, visible rows stack on top of each other.
2 Memoize expensive rows
Rows re-render during scrolling, so React.memo can reduce unnecessary work.
3 Use itemData
Pass row data through the list instead of closing over large objects in every render.
<FixedSizeList itemData={myItems} ...>
{({ index, style, data }) => (
<div style={style}>{data[index].name}</div>
)}
</FixedSizeList>
Bottom line
react-window solves one narrow problem, but it solves it extremely well. When the bottleneck is too many mounted rows, windowing changes the shape of the problem by keeping the DOM aligned with what the user can actually see.
The next time a large list starts to stutter, do not just blame React. Check how many DOM nodes you are asking the browser to manage and ask whether the list should be virtualized.