We use cookies to enhance your experience on the site
CodeWorlds

List virtualization and windowing

Imagine looking through the observation window of a spaceship at billions of stars in a galaxy. You do not see them all at once - you only see those in your field of view. This is exactly how list virtualization works in React: instead of rendering thousands of DOM elements, we render only those currently visible on the screen.

The problem: rendering thousands of elements

When we have a list with thousands of elements (e.g., a planet catalog, server logs, chat messages), rendering them all simultaneously can cause serious performance issues:

1// PROBLEM: Rendering 10,000 elements at once
2function SlowStarCatalog({ stars }) {
3  // This will create 10,000 DOM elements at once!
4  return (
5    <div style={{ height: '600px', overflow: 'auto' }}>
6      {stars.map(star => (
7        <div key={star.id} style={{
8          padding: '12px',
9          borderBottom: '1px solid #333',
10          backgroundColor: '#0a0a1a',
11          color: '#ccc'
12        }}>
13          <strong style={{ color: '#ffd700' }}>{star.name}</strong>
14          <span style={{ marginLeft: '10px' }}>Type: {star.type}</span>
15          <span style={{ marginLeft: '10px' }}>Distance: {star.distance} ly</span>
16        </div>
17      ))}
18    </div>
19  );
20}
21
22// Each of the 10,000 elements:
23// - creates DOM nodes
24// - takes up browser memory
25// - slows down initial rendering
26// - slows down scrolling

The effects of this approach are:

  • Long initial render time - the browser must create thousands of DOM elements
  • High memory usage - each DOM element takes up memory
  • Laggy scrolling - the browser must handle a huge DOM tree
  • Frozen interface - the user waits for the application to respond

The windowing concept

Windowing is a technique that renders only elements visible in the "window" (viewport) of the scroll container. The rest of the elements simply do not exist in the DOM - they are added only when the user scrolls the list.

1// CONCEPT: How windowing works
2
3// Imagine a list of 10,000 elements, each 50px tall
4// The container is 500px tall - we see only 10 elements at a time
5
6// WITHOUT windowing:
7// DOM contains: 10,000 elements = 500,000px of height
8// Memory: huge
9
10// WITH windowing:
11// DOM contains: ~15 elements (10 visible + buffer)
12// "Empty" space simulated by padding/transform
13// Memory: minimal
14
15// How it works:
16// [padding-top: simulates elements above]
17// [element 45] <- visible
18// [element 46] <- visible
19// [element 47] <- visible
20// [element 48] <- visible
21// ...
22// [element 54] <- visible
23// [padding-bottom: simulates elements below]

react-window - virtualization library

react-window
is a lightweight library by Brian Vaughn (author of the original
react-virtualized
) that implements windowing in a simple and efficient way.

FixedSizeList - elements with fixed height

1import { FixedSizeList } from 'react-window';
2
3// Generate 10,000 stars
4const stars = Array.from({ length: 10000 }, (_, index) => ({
5  id: index,
6  name: `Star-${index + 1}`,
7  type: ['White dwarf', 'Red giant', 'Neutron star', 'Supernova'][index % 4],
8  magnitude: (Math.random() * 20 - 5).toFixed(2)
9}));
10
11// Single row component
12function StarRow({ index, style }) {
13  const star = stars[index];
14  return (
15    <div style={{
16      ...style,
17      display: 'flex',
18      alignItems: 'center',
19      padding: '0 16px',
20      borderBottom: '1px solid #1a1a3e',
21      backgroundColor: index % 2 === 0 ? '#0a0a1a' : '#0f0f2a',
22      color: '#ccc'
23    }}>
24      <span style={{ flex: 1, color: '#ffd700' }}>{star.name}</span>
25      <span style={{ flex: 1 }}>{star.type}</span>
26      <span style={{ width: '100px', textAlign: 'right' }}>
27        mag: {star.magnitude}
28      </span>
29    </div>
30  );
31}
32
33function VirtualizedStarCatalog() {
34  return (
35    <div style={{ padding: '20px', backgroundColor: '#0f0f23' }}>
36      <h2 style={{ color: '#00d2ff' }}>Star Catalog (10,000 objects)</h2>
37      <FixedSizeList
38        height={400}         // Container height
39        width="100%"         // Container width
40        itemCount={stars.length}  // Number of items
41        itemSize={50}        // Height of each item (fixed!)
42      >
43        {StarRow}
44      </FixedSizeList>
45    </div>
46  );
47}

FixedSizeList
is the most efficient option because it does not need to measure element heights - it knows them in advance. Use it whenever list items have a fixed, known height.

VariableSizeList - elements with different heights

When list items have different heights (e.g., chat messages, posts), we use

VariableSizeList
:

1import { VariableSizeList } from 'react-window';
2
3const messages = Array.from({ length: 5000 }, (_, i) => ({
4  id: i,
5  sender: ['Captain Nova', 'Engineer Rex', 'Navigator Luna'][i % 3],
6  text: i % 5 === 0
7    ? 'Longer message with mission report - status of propulsion systems, communications, and life support. All parameters within normal range.'
8    : 'Short message #' + (i + 1)
9}));
10
11// Function returning element height based on index
12const getItemSize = (index) => {
13  const message = messages[index];
14  // Longer message = taller cell
15  return message.text.length > 50 ? 80 : 50;
16};
17
18function MessageRow({ index, style }) {
19  const msg = messages[index];
20  return (
21    <div style={{
22      ...style,
23      padding: '8px 16px',
24      borderBottom: '1px solid #1a1a3e',
25      backgroundColor: '#0a0a1a',
26    }}>
27      <strong style={{ color: '#e94560' }}>{msg.sender}:</strong>
28      <p style={{ color: '#ccc', margin: '4px 0 0 0', fontSize: '14px' }}>
29        {msg.text}
30      </p>
31    </div>
32  );
33}
34
35function VirtualizedChat() {
36  return (
37    <VariableSizeList
38      height={400}
39      width="100%"
40      itemCount={messages.length}
41      itemSize={getItemSize}
42    >
43      {MessageRow}
44    </VariableSizeList>
45  );
46}

When to use virtualization vs pagination?

The choice between virtualization and pagination depends on context - just like the choice between a telescope and a star map:

Virtualization is better when:

  • The user needs smooth scrolling (e.g., feed, chat, logs)
  • Data is already loaded (or loaded via streaming)
  • Keeping a "natural" scrollbar is important
  • The list is very long, but elements are simple

Pagination is better when:

  • Data is loaded from the server in pages
  • The user wants to jump to specific pages
  • SEO is important (each page has a separate URL)
  • List items are very complex
1// Pagination - classic approach
2function PaginatedList({ totalItems, pageSize }) {
3  const [page, setPage] = useState(1);
4  const totalPages = Math.ceil(totalItems / pageSize);
5
6  return (
7    <div>
8      {/* Render only items from the current page */}
9      {items.slice((page - 1) * pageSize, page * pageSize).map(item => (
10        <ItemCard key={item.id} item={item} />
11      ))}
12      <div>
13        <button disabled={page === 1} onClick={() => setPage(p => p - 1)}>
14          Previous
15        </button>
16        <span>Page {page} of {totalPages}</span>
17        <button disabled={page === totalPages} onClick={() => setPage(p => p + 1)}>
18          Next
19        </button>
20      </div>
21    </div>
22  );
23}
24
25// Virtualization - modern approach for long lists
26function VirtualizedList({ items }) {
27  return (
28    <FixedSizeList
29      height={600}
30      itemCount={items.length}
31      itemSize={60}
32    >
33      {({ index, style }) => (
34        <div style={style}>
35          <ItemCard item={items[index]} />
36        </div>
37      )}
38    </FixedSizeList>
39  );
40}

Measuring performance improvement

To prove that virtualization works, we can measure the performance difference:

1function PerformanceComparison() {
2  const [mode, setMode] = useState('virtualized');
3  const items = useMemo(
4    () => Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` })),
5    []
6  );
7
8  // Measure render time
9  useEffect(() => {
10    const start = performance.now();
11    // After render
12    requestAnimationFrame(() => {
13      const end = performance.now();
14      console.log(`Render time (${mode}): ${(end - start).toFixed(2)}ms`);
15    });
16  }, [mode]);
17
18  return (
19    <div>
20      <div>
21        <button onClick={() => setMode('normal')}>Normal list</button>
22        <button onClick={() => setMode('virtualized')}>Virtualized</button>
23      </div>
24
25      {mode === 'normal' ? (
26        // 10,000 DOM elements
27        <div style={{ height: 400, overflow: 'auto' }}>
28          {items.map(item => <div key={item.id}>{item.name}</div>)}
29        </div>
30      ) : (
31        // ~15 DOM elements
32        <FixedSizeList height={400} itemCount={items.length} itemSize={35}>
33          {({ index, style }) => <div style={style}>{items[index].name}</div>}
34        </FixedSizeList>
35      )}
36    </div>
37  );
38}
39
40// Typical results:
41// Normal list: ~800-2000ms rendering, ~50MB memory
42// Virtualized: ~5-15ms rendering, ~2MB memory

Summary

List virtualization is one of the most effective performance optimization techniques in React:

  1. Render only visible elements - use
    react-window
    or
    react-virtuoso
  2. FixedSizeList
    for elements with fixed height (most efficient)
  3. VariableSizeList
    for elements with variable height
  4. Measure before and after - use React DevTools Profiler and
    performance.now()
  5. Consider pagination as an alternative when data is loaded from the server

Just like a telescope on a spaceship shows you only a part of the sky at any given moment, virtualization shows the user only a part of the list - but with the smoothness worthy of the best navigation systems.

Go to CodeWorlds