[Lv3] Virtual Scrolling: Rendering Large Lists Efficiently
Virtual scrolling keeps DOM size small by rendering only the visible window plus a buffer.
Situation
Large tables with frequent updates can generate tens of thousands of DOM nodes, causing:
- Slow initial render
- Scroll stutter
- High memory usage
- Expensive updates during real-time events
Core idea
Instead of rendering all rows, render only:
- Visible rows
- Small overscan before and after viewport
As scroll position changes, recycle row containers and update displayed data window.
Basic implementation (fixed row height)
const rowHeight = 48;
const viewportHeight = 480;
const visibleCount = Math.ceil(viewportHeight / rowHeight);
const startIndex = Math.floor(scrollTop / rowHeight);
const endIndex = startIndex + visibleCount + 6; // overscan
<div style={{ height: totalRows * rowHeight }}>
<div style={{ transform: `translateY(${startIndex * rowHeight}px)` }}>
{rows.slice(startIndex, endIndex).map(renderRow)}
</div>
</div>
Variable row height considerations
For dynamic content:
- Measure row heights lazily
- Maintain prefix-sum offsets
- Use binary search to map
scrollTopto index
If row heights vary heavily, library support is usually safer.
Interaction pitfalls and fixes
- Keep stable keys to prevent unnecessary remount
- Memoize row components
- Debounce heavy side effects from scroll events
- Preserve scroll anchor when list updates
When to avoid virtual scroll
- Small datasets (complexity may not be worth it)
- Scenarios requiring all DOM nodes for browser-native operations
- Highly irregular layouts that are difficult to measure
Interview-ready summary
I apply virtual scrolling when row count is high and rendering cost dominates. The key is windowed rendering with overscan, stable keys, and careful update strategy so scroll remains smooth under frequent data changes.