Ten thousand orders
The kitchen's order history has ten thousand rows. Rendered naively, that's ten thousand components and ten thousand DOM nodes before the page can respond — and the field guide's performance chapter is blunt that main-thread JavaScript work is what makes pages feel slow, especially on mid-range phones. A person can see perhaps fifteen rows at a time. Why build the other 9,985?
Two techniques handle almost every slow list. Virtualization (also called windowing) renders only the rows in view. Memoization — memo, useMemo, useCallback — skips re-rendering rows whose inputs didn't change. Today you'll build both, the way react-window does underneath.
How windowing works
The Learning Patterns book describes the trick: a small scrolling container (the window), a big inner element with the full height so the scrollbar is right, and only the visible rows, absolutely positioned at top = index × rowHeight. As you scroll, the window moves and different rows are rendered.
const start = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
const end = Math.min(items.length, Math.ceil((scrollTop + height) / itemHeight) + overscan);
<div className="viewport" style={{ height, overflowY: "auto" }} onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}>
<div className="spacer" style={{ height: items.length * itemHeight, position: "relative" }}>
{rows /* items start…end-1, each at top: i * itemHeight */}
</div>
</div>- Overscan renders a few rows beyond each edge so fast scrolling doesn't flash blank space.
- Rows of unknown height need measuring — that's what VariableSizeList and TanStack Virtual handle.
- Ctrl+F can't find rows that aren't rendered, and screen readers only see the window: consider pagination for content people search.
- Browsers also offer content-visibility: auto in CSS, which skips painting off-screen content while keeping it in the DOM.