Virtualisation is one of the most powerful performance techniques in modern front-end development. When you have thousands of items to display — a news feed, a photo grid, a chat history — rendering all of them at once would freeze the browser. Virtualisation solves this by rendering only the items the user can currently see, keeping the DOM lean and the experience smooth. This guide walks you through why this matters, how to implement it, and how to think about it in system design interviews — starting from absolute zero.
The Document Object Model (DOM) is the browser's in-memory representation of your HTML page. Every <div>, <p>, <img>, and <li> is a node in a giant tree. The browser must track every single one.
Imagine you fetch 10,000 tweets and render them all at once:
<!-- Simplified example: rendering all items -->
<ul>
<li>Tweet 1</li>
<li>Tweet 2</li>
<!-- ... 9,998 more ... -->
<li>Tweet 10,000</li>
</ul>
The browser must:
1. **Parse** all 10,000 elements into DOM nodes
2. **Calculate styles** for every node (CSS matching)
3. **Perform layout** — calculate the position and size of each element (reflow)
4. **Paint** — draw pixels for every element
5. **Composite** — combine all layers onto the screen
Even if only 20 items are visible, all 10,000 exist in memory and participate in layout calculations.
### The Four Bottlenecks Explained
<svg viewBox="0 0 800 420" width="100%" xmlns="http://www.w3.org/2000/svg">
<rect width="800" height="420" fill="#f8fafc" rx="12"/>
<text x="400" y="35" text-anchor="middle" font-size="18" font-weight="bold" fill="#0f172a" font-family="sans-serif">Four Bottlenecks of a Large DOM</text>
<!-- Memory -->
<rect x="30" y="60" width="165" height="320" rx="10" fill="#fef2f2" stroke="#fca5a5" stroke-width="2"/>
<text x="112" y="90" text-anchor="middle" font-size="14" font-weight="bold" fill="#dc2626" font-family="sans-serif">1. Memory</text>
<text x="112" y="115" text-anchor="middle" font-size="11" fill="#7f1d1d" font-family="sans-serif">Each DOM node</text>
<text x="112" y="132" text-anchor="middle" font-size="11" fill="#7f1d1d" font-family="sans-serif">stores properties,</text>
<text x="112" y="149" text-anchor="middle" font-size="11" fill="#7f1d1d" font-family="sans-serif">event listeners,</text>
<text x="112" y="166" text-anchor="middle" font-size="11" fill="#7f1d1d" font-family="sans-serif">computed styles.</text>
<text x="112" y="195" text-anchor="middle" font-size="13" fill="#dc2626" font-family="sans-serif">~1–2 KB/node</text>
<text x="112" y="215" text-anchor="middle" font-size="13" fill="#dc2626" font-family="sans-serif">x 10,000 nodes</text>
<text x="112" y="240" text-anchor="middle" font-size="13" font-weight="bold" fill="#991b1b" font-family="sans-serif">= 10–20 MB</text>
<text x="112" y="270" text-anchor="middle" font-size="11" fill="#7f1d1d" font-family="sans-serif">just for DOM,</text>
<text x="112" y="287" text-anchor="middle" font-size="11" fill="#7f1d1d" font-family="sans-serif">before images</text>
<text x="112" y="304" text-anchor="middle" font-size="11" fill="#7f1d1d" font-family="sans-serif">or other assets</text>
<!-- Reflow -->
<rect x="210" y="60" width="165" height="320" rx="10" fill="#fff7ed" stroke="#fdba74" stroke-width="2"/>
<text x="292" y="90" text-anchor="middle" font-size="14" font-weight="bold" fill="#ea580c" font-family="sans-serif">2. Reflow</text>
<text x="292" y="115" text-anchor="middle" font-size="11" fill="#7c2d12" font-family="sans-serif">Changing one</text>
<text x="292" y="132" text-anchor="middle" font-size="11" fill="#7c2d12" font-family="sans-serif">element can force</text>
<text x="292" y="149" text-anchor="middle" font-size="11" fill="#7c2d12" font-family="sans-serif">the browser to</text>
<text x="292" y="166" text-anchor="middle" font-size="11" fill="#7c2d12" font-family="sans-serif">recalculate sizes</text>
<text x="292" y="183" text-anchor="middle" font-size="11" fill="#7c2d12" font-family="sans-serif">of ALL siblings.</text>
<text x="292" y="215" text-anchor="middle" font-size="12" fill="#ea580c" font-family="sans-serif">10,000 items</text>
<text x="292" y="235" text-anchor="middle" font-size="12" fill="#ea580c" font-family="sans-serif">= 10,000 layout</text>
<text x="292" y="255" text-anchor="middle" font-size="12" fill="#ea580c" font-family="sans-serif">calculations</text>
<text x="292" y="285" text-anchor="middle" font-size="11" fill="#7c2d12" font-family="sans-serif">Can block the</text>
<text x="292" y="302" text-anchor="middle" font-size="11" fill="#7c2d12" font-family="sans-serif">main thread</text>
<text x="292" y="319" text-anchor="middle" font-size="11" fill="#7c2d12" font-family="sans-serif">for seconds</text>
<!-- Paint -->
<rect x="390" y="60" width="165" height="320" rx="10" fill="#fefce8" stroke="#fde047" stroke-width="2"/>
<text x="472" y="90" text-anchor="middle" font-size="14" font-weight="bold" fill="#ca8a04" font-family="sans-serif">3. Paint</text>
<text x="472" y="115" text-anchor="middle" font-size="11" fill="#713f12" font-family="sans-serif">The browser must</text>
<text x="472" y="132" text-anchor="middle" font-size="11" fill="#713f12" font-family="sans-serif">turn every node</text>
<text x="472" y="149" text-anchor="middle" font-size="11" fill="#713f12" font-family="sans-serif">into pixels on</text>
<text x="472" y="166" text-anchor="middle" font-size="11" fill="#713f12" font-family="sans-serif">the screen.</text>
<text x="472" y="195" text-anchor="middle" font-size="12" fill="#ca8a04" font-family="sans-serif">Off-screen items</text>
<text x="472" y="215" text-anchor="middle" font-size="12" fill="#ca8a04" font-family="sans-serif">still consume</text>
<text x="472" y="235" text-anchor="middle" font-size="12" fill="#ca8a04" font-family="sans-serif">GPU memory</text>
<text x="472" y="265" text-anchor="middle" font-size="11" fill="#713f12" font-family="sans-serif">Especially bad</text>
<text x="472" y="282" text-anchor="middle" font-size="11" fill="#713f12" font-family="sans-serif">on mobile with</text>
<text x="472" y="299" text-anchor="middle" font-size="11" fill="#713f12" font-family="sans-serif">limited GPU RAM</text>
<!-- Scroll -->
<rect x="570" y="60" width="165" height="320" rx="10" fill="#f0fdf4" stroke="#86efac" stroke-width="2"/>
<text x="652" y="90" text-anchor="middle" font-size="14" font-weight="bold" fill="#16a34a" font-family="sans-serif">4. Scroll Jank</text>
<text x="652" y="115" text-anchor="middle" font-size="11" fill="#14532d" font-family="sans-serif">Scrolling fires</text>
<text x="652" y="132" text-anchor="middle" font-size="11" fill="#14532d" font-family="sans-serif">events rapidly.</text>
<text x="652" y="149" text-anchor="middle" font-size="11" fill="#14532d" font-family="sans-serif">With 10,000 nodes</text>
<text x="652" y="166" text-anchor="middle" font-size="11" fill="#14532d" font-family="sans-serif">each scroll event</text>
<text x="652" y="183" text-anchor="middle" font-size="11" fill="#14532d" font-family="sans-serif">triggers layout</text>
<text x="652" y="200" text-anchor="middle" font-size="11" fill="#14532d" font-family="sans-serif">recalculation.</text>
<text x="652" y="230" text-anchor="middle" font-size="12" fill="#16a34a" font-family="sans-serif">Target: 60 FPS</text>
<text x="652" y="250" text-anchor="middle" font-size="12" fill="#16a34a" font-family="sans-serif">= 16ms per frame</text>
<text x="652" y="280" text-anchor="middle" font-size="11" fill="#14532d" font-family="sans-serif">Large lists blow</text>
<text x="652" y="297" text-anchor="middle" font-size="11" fill="#14532d" font-family="sans-serif">this budget</text>
<text x="652" y="314" text-anchor="middle" font-size="11" fill="#14532d" font-family="sans-serif">immediately</text>
<!-- Bottom label -->
<text x="400" y="400" text-anchor="middle" font-size="12" fill="#64748b" font-family="sans-serif">All four bottlenecks worsen linearly as the node count grows</text>
</svg>
### A Simple Mental Model
Think of the DOM like a restaurant kitchen. If you ask the kitchen to **prep all 10,000 dishes** on the menu at once — even though only 10 customers are ordering — the kitchen grinds to a halt. Virtualisation means: **only prep what is currently being ordered**.
### Measuring the Problem
```javascript
// Bad: renders everything at once
function BadList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}
// How many DOM nodes does this create?
// If items.length = 10,000 → 10,001 DOM nodes (10,000 <li> + 1 <ul>)
// Chrome DevTools → Performance tab → Nodes metric
**Rule of thumb:** The browser starts struggling noticeably around **1,000–1,500 DOM nodes** in a single scrollable list. At 10,000, most devices experience visible jank.
---