๐ Virtualization in Frontend (React)
โ What is Virtualization?
Virtualization (sometimes called windowing) is a rendering optimization technique where the UI renders only the elements visible inside the viewport, instead of rendering all items at once.
๐ฅ Instead of creating 10,000 DOM nodes, you render maybe ~20.
The rest exist โvirtuallyโ in memory.
| Scenario | DOM Nodes Rendered | Memory | FPS | Time to Interactive |
|---|---|---|---|---|
| โ Without Virtualization | 10,000 | ~500MB | ~5 FPS | ~30 seconds |
| โ With Virtualization | ~20 | ~50MB | 60 FPS | <1 second |
๐ Core idea:
Maintain the entire dataset in memory โ render only the subset currently visible in the scroll area.
๐ง Why do we need virtualization?
Bad approach (renders every item):
function ChatMessages({ messages }) {
return (
<div className="messages">
{messages.map(msg => (
<MessageCard key={msg.id} message={msg} />
))}
</div>
);
}
โ Problems:
- DOM explosion (10,000 nodes)
- Massive memory use
- Lags, FPS drops
- Browser crashes on mobile
โ Benefits of Virtualization
- Performance stays constant regardless of data size
- Low memory usage โ only visible items exist in DOM
- Smooth scrolling (60 FPS) even with millions of rows
- Fast initial load โ only a few elements render
- Mobile friendly โ avoids browser memory crash
๐ง How Virtualization Works (Visually)
Viewport (visible area):
โโโโโโโโโโโโโโโโโโโโโโโ
โ Item 48 โ <- Rendered
โ Item 49 โ <- Rendered
โ Item 50 โ <- Rendered
โ Item 51 โ <- Rendered
โโโโโโโโโโโโโโโโโโโโโโโ
โ User is seeing these 4 items now
Items 1โ47: not rendered
Items 52โ10000: not rendered
โก๏ธ Scroll โ remove old items โ render new ones.
๐งฎ Key Calculations (how react-window / react-virtuoso work internally)
const itemHeight = 80; // px
const containerHeight = 600; // px
const scrollTop = 3840; // user scroll position
const visibleCount = Math.ceil(containerHeight / itemHeight); // ~8 items
const startIndex = Math.floor(scrollTop / itemHeight); // start at item 48
const endIndex = startIndex + visibleCount + 1; // overscan
Only items 48โ57 are rendered, not all 10,000.
โ Implementation Levels
1๏ธโฃ Build Virtualization from Scratch (fixed height)
Use for chat, lists, tables, logs where every item height is known.
โ
Only renders visible items
โ
Simple math
function VirtualList({ items, itemHeight, containerHeight }) {
const [scrollTop, setScrollTop] = useState(0);
const visibleCount = Math.ceil(containerHeight / itemHeight);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = startIndex + visibleCount + 1;
const visibleItems = items.slice(startIndex, endIndex);
const offsetY = startIndex * itemHeight;
return (
<div
onScroll={(e) => setScrollTop(e.target.scrollTop)}
style={{ height: containerHeight, overflow: "auto" }}
>
<div style={{ height: items.length * itemHeight, position: "relative" }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, i) => (
<div key={startIndex + i} style={{ height: itemHeight }}>
{item.text}
</div>
))}
</div>
</div>
</div>
);
}
2๏ธโฃ Virtualization with Variable Height Items
Use when item height is unknown (tweets, chat messages with images, markdown).
โจ After rendering, measure the height and update.
(Your code above is already perfect; leaving as-is.)
3๏ธโฃ Production Ready: react-window
Lightweight, battle-tested, perfect for fixed height rows.
import { FixedSizeList } from "react-window";
function MessageList({ messages }) {
return (
<FixedSizeList
height={600}
itemCount={messages.length}
itemSize={80}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<MessageCard message={messages[index]} />
</div>
)}
</FixedSizeList>
);
}
4๏ธโฃ Best option today: react-virtuoso
Handles everything automatically โ dynamic height, infinite scroll, sticky headers.
import { Virtuoso } from "react-virtuoso";
function ChatMessages({ messages }) {
return (
<Virtuoso
data={messages}
style={{ height: "600px" }}
itemContent={(index, message) => (
<MessageCard message={message} />
)}
/>
);
}
๐ Real-World Examples
โ
WhatsApp style chat (scroll to load older messages)
โ
Twitter feed (infinite scroll + pull to refresh)
โ
Gmail inbox (checkboxes + selection + virtualization)
(Your examples above remain unchanged โ they are production ready.)
๐ฅ Best Practices / Gotchas
| Problem | Solution |
|---|---|
| Scroll jumps when loading older messages | Maintain firstItemIndex (Virtuoso handles this) |
| Images inside list change row height | Use resetAfterIndex or Virtuoso auto measuring |
| Items re-render unnecessarily |
memo() your row component |
| Searching / filtering | Debounce search to avoid re-rendering |
TL;DR
Virtualization = render fewer DOM nodes โ better performance.
- Instead of rendering 10,000 DOM nodes
- You render ~20 and recycle them as user scrolls
- Result: smooth 60 FPS scrolling even with huge datasets
And thatโs it โ you now understand virtualization: what it is, why it matters, and how to implement it efficiently in UI. From optimizing DOM nodes to ensuring smooth scrolling, virtualization keeps your UI fast even with thousands of items.
If you havenโt seen the previous parts yet, give them a look. Each part builds on the last, and together theyโll level up your performance game.




Top comments (0)