Virtualization

Rendering a list with 10 items is fast. Rendering a list with 10,000 items is slow. The browser DOM is not designed to handle thousands of elements. It consumes massive amounts of memory and causes janky scrolling.

Virtualization (or “Windowing”) is a technique where you only render the items that are currently visible in the viewport (plus a small buffer). As the user scrolls, you recycle the DOM nodes, swapping their content.

[!TIP] Key Insight: Whether a list has 100 items or 1,000,000 items, the user can only see about 10-20 of them at once on their screen. Why render the rest?

1. Intuition: The Sliding Window

Imagine viewing a long film strip through a small magnifying glass.

  • The Film Strip: Your data (10,000 items).
  • The Magnifying Glass: Your screen (Viewport).

You only need to see the frames under the glass. As you slide the glass down, the frames above it disappear, and new frames appear below. You never need to unroll the entire film strip at once.

2. Hardware Reality: The DOM Tax

Why not just render 10,000 divs?

  1. Memory: Each DOM node is an object in C++ (inside the browser engine) consuming memory. 10,000 complex nodes can consume 100MB+ of RAM.
  2. Layout Thrashing: Every time you scroll, the browser has to calculate the position of every single element. Calculating layout for 10,000 items on every frame (16ms) is mathematically impossible on mobile CPUs, resulting in < 10 FPS scrolling.

3. Virtualization Mechanism

The Virtual Window

Total Height (50,000px)
Viewport
Rendered Node Exists in DOM
Virtual Item Does NOT exist
DOM Size remains constant even if you scroll to item #9,999.

4. Interactive Demo: The Virtual Scroller

This demo simulates a list with 10,000 items.

  • Total Items: 10,000 (Data in memory)
  • Rendered Nodes: Only ~8 divs are actually in the DOM at any time.

Scroll quickly and watch the “Rendered Index” update while “DOM Nodes” stays flat.

Loading Interactive Demo...

5. How to Implement Virtualization

The Math

  1. Container Height: How tall is the visible window? (e.g., 300px)
  2. Item Height: How tall is each row? (e.g., 60px)
  3. Scroll Position: scrollTop (e.g., 1000px)
// Which index is at the top?
const startIndex = Math.floor(scrollTop / itemHeight); // 1000 / 60 = 16

// How many fit on screen?
const visibleCount = Math.ceil(containerHeight / itemHeight); // 300 / 60 = 5

// End index
const endIndex = startIndex + visibleCount; // 16 + 5 = 21

We only render items 16 to 21.

Libraries

Don’t write this from scratch in production. Use react-window (lighter) or react-virtualized (more features).

react-window Example

This is how you use the most popular library, react-window.

import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style }) => (
  // Style contains top: x, left: 0, width: 100%, height: 35px
  // You MUST pass this style to your element for positioning to work
  <div style={style} className={index % 2 ? 'ListItemOdd' : 'ListItemEven'}>
    Row {index}
  </div>
);

const Example = () => (
  <List
    height={500}          // Height of the window
    itemCount={10000}     // Total items
    itemSize={35}         // Height of each item
    width={300}           // Width of the list
  >
    {Row}
  </List>
);

[!WARNING] Common Gotcha: Forgetting to pass the style prop to your row component. Without it, all 10,000 items will render at the top (stacked on each other) or not render at all.

6. Summary

Term Definition
Windowing Rendering only the visible portion of a large list.
Phantom Height A transparent container set to the full height (totalItems * itemHeight) to ensure the scrollbar looks correct.
Recycling Reusing DOM nodes for new content as they scroll into view.