The Developer's Quarterly · React PerformanceVol. I · January 2026

React Window:Render Thousands of Rows Without Killing Performance

When a large list slows your app to a crawl, the problem is usually too many DOM nodes, not React itself, and windowing is the fix.

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.

Basic example
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.

Variable heights
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

Long feeds Activity streams, notification centers, and timelines.
Search results Large result sets that would otherwise flood the DOM.
Data tables Large row counts that make naive rendering sluggish.
Infinite scroll Combine windowing with incremental fetching for very large datasets.

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.

Passing row data
<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.