A hundred thousand rows, and twenty of them drawn.
A hundred thousand orders is not an unusual amount of data — it is one busy year. Put every row in the document and the tab allocates hundreds of megabytes and scrolling stops being smooth. Every “the admin panel got slow” complaint I have been shown was some version of this. Switch between the two below and watch the counter.
- Rows in the DOM
- 0 of 0 in the data
- Last paint
- 0 ms time to build the visible rows
- Matching
- 0 after search and sort
- Filter + sort
- 0 ms over the whole set
Every row is now in the document. The node counter above is the honest number, and this is roughly the point at which a real admin panel starts getting described as “heavy”.
How it is designed
The browser is fast; a hundred thousand elements are not
Nothing here is a framework problem. Layout, style resolution and memory all scale with the number of elements, so the fix is not a faster renderer — it is fewer elements. Draw the twenty rows that are on screen, and put a spacer of the full height behind them so the scrollbar still describes the real dataset.
Three numbers have to agree, or it tears
The row height in the stylesheet, the row height in the maths, and the height of the spacer. If they drift, the rows detach from the scrollbar and the list appears to slip as you drag. The row height is one constant referenced by both, which is the only way to keep them honest.
Overscan, or you see the seam
Drawing exactly the visible rows leaves a blank band during a fast flick, because the scroll event and the paint do not land on the same frame. Six extra rows above and below cost nothing and remove it entirely.
One paint per frame, not one per scroll event
Scroll fires far more often than the screen redraws. Rebuilding on every
event is how a virtual list ends up slower than the thing it replaced, so
the handler sets a flag and lets requestAnimationFrame do
the work once.
Sorting copies; filtering does not mutate
The view is derived from the source array and never replaces it. A sort that reorders the underlying data in place is a bug waiting for the second feature that assumes insertion order.
What this deliberately is not
There is no library here, and for a table this shape there does not need
to be. Reach for one when you need variable row heights, sticky groups
or column virtualisation as well — the moment the maths stops being
index × height, hand-rolling stops being the cheap
option.