Frontend Performance Optimization: List Virtualization
May 27, 2026
Among the many techniques for improving frontend performance, list virtualization is one of the most common ways to handle long lists of data.
Whether it is a notification list, chat messages, search results, product listings, or a data table in an internal dashboard, everything usually feels smooth at first when there are only a few dozen items. But once the data grows to thousands or tens of thousands of items, the feature may still work and the data may still render correctly, yet the page starts to feel slow. Scrolling becomes janky, and halfway through the list the UI may feel like it cannot keep up with your finger.
There are several ways to approach this kind of performance problem. Virtualization is one of them.
What Problem Does Virtualization Solve?
To understand virtualization, we first need to understand the problem it solves. When the browser fetches data from the backend, the work does not end when the data arrives. The browser still has to turn that data into pixels on the screen, and that has a cost.
If there are 10,000 items on a page and we actually render 10,000 rows, the browser has to create many DOM nodes, calculate styles, lay out the page, paint the result, and keep managing all of that while the user scrolls. The problem is that the user can only see a small number of rows at any given moment, often just a dozen or a few dozen.
The user only needs to see the rows currently inside the viewport, but the browser is being asked to manage the DOM for every row in the list. It needs to know where each element is, what it looks like, how large it is, and which parts need to be repainted. That creates unnecessary work.
A key idea to keep in mind is that data sitting in browser memory is not the same as data rendered into the DOM. In memory, one item may just be an object in an array. Once it becomes part of the UI, however, it enters the browser rendering pipeline: element creation, style calculation, layout, paint, and compositing. One item is cheap. Ten thousand items at once can become visible jank.
Virtualization exists to solve this problem. Its core idea is not to remove data or shorten the list. Instead, it only renders the small slice of content that the user can currently see, or is about to see. The user feels like they are scrolling through a complete list, but the browser is only managing a small section of it at any moment.
What Does Virtualization Actually Do?
So how does virtualization reduce the number of DOM nodes the browser needs to maintain?
Imagine a list container that is 600 pixels tall. Each row is 60 pixels tall, so the user can see around 10 rows at a time. In practice, we usually render a few extra rows above and below the viewport as a buffer to make scrolling feel smoother, so the page may contain around 15 to 20 rendered rows.
Even if the full dataset has 10,000 items, virtualization only asks the browser to manage those 15 to 20 rows at the current scroll position. As the user scrolls down, items that leave the viewport are removed, and new items that enter the viewport are rendered. The user feels like they are browsing the full list, but the DOM only contains the nearby slice.
This is why virtualization is sometimes also called windowing. It is like moving a small window across a large roll of data: wherever the window is, that is the part the page renders.
To implement virtualization, we usually need a few pieces of information:
- The total number of items, so we can calculate the full height of the list. Without this, the scrollbar will not behave correctly, and the user will not know how much content remains below.
- The current scroll position, so we can calculate which items should be rendered right now.
- The correct position for each rendered item. Otherwise, item 500 might appear at the top of the screen and make the list look broken.
From an implementation perspective, virtualization is mainly about managing the visible range.
A Recommended Virtualization Library
When it comes to virtualization libraries, TanStack Virtual is one of the most popular options in the community today. It is easy to use, and more importantly, it works across frameworks. It supports not only React, but also Vue, Svelte, Solid, Lit, Angular, and more. For teams, that means the same concept can carry across projects instead of being tied to a single React component API.
A simplified TanStack Virtual example looks roughly like this:
function MessageList({ messages }) {
const parentRef = useRef(null);
const virtualizer = useVirtualizer({
count: messages.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 64,
});
return (
<div ref={parentRef} style={{ height: 600, overflow: "auto" }}>
<div style={{ height: virtualizer.getTotalSize(), position: "relative" }}>
{virtualizer.getVirtualItems().map((virtualItem) => {
const message = messages[virtualItem.index];
return (
<div
key={message.id}
style={{
position: "absolute",
transform: `translateY(${virtualItem.start}px)`,
height: virtualItem.size,
width: "100%",
}}
>
{message.title}
</div>
);
})}
</div>
</div>
);
}
The API turns a fairly complex idea into a manageable interface. In the example above, count tells the virtualizer how many items exist in total. getScrollElement tells it which container is responsible for scrolling. estimateSize gives it an approximate row height.
getTotalSize creates the full height of the list so the scrollbar behaves as if every item were rendered. getVirtualItems returns only the items that actually need to be rendered at the current scroll position.
With virtualization, the UI looks like a full list, but the DOM only contains the items near the visible area. In other words, virtualization does not make the list shorter. It makes the browser handle only the part the user needs right now.
Not Every List Needs Virtualization
Although virtualization is useful, and TanStack Virtual makes it relatively easy to apply, you should not add virtualization every time you see a long list.
Virtualization trades complexity for performance. It reduces the number of DOM nodes, but it also adds implementation cost. From a software engineering perspective, the important question is not whether you can use it, but whether the scenario is worth it. If the dataset is small, such as a few dozen or a hundred rows, and each row is simple, rendering the list directly is usually fine. Adding virtualization may only make the code harder to understand. A UI that used to be a simple map suddenly needs a scroll container, estimated heights, absolute positioning, and size measurement. Debugging can become harder than the original problem.
Virtualization can also be a poor fit when the page relies on the full DOM being present. For example, users may expect the browser's built-in page search to search the entire list, or they may need to print the full content at once, or an accessibility scenario may require a more complete document structure. Because virtualization only puts items near the viewport into the DOM, off-screen content may not exist in the page at all. In those cases, adding virtualization can introduce bugs.
When deciding whether to virtualize a list, ask these questions:
- Can the number of DOM nodes in this list actually become large?
- Will users scroll frequently, and does scroll smoothness matter to the experience?
- Is the added complexity worth more than the current performance problem?
If the answer is yes to all three, virtualization may be a reasonable choice. But if the data is small, the UI is simple, and there is no obvious performance issue, keeping things simple is usually better.
Support ExplainThis
If you found this content helpful, please consider supporting our work with a one-time donation of whatever amount feels right to you through this Buy Me a Coffee page.
Creating in-depth technical content takes significant time. Your support helps us continue producing high-quality educational content accessible to everyone.