Rendering every row of a list with thousands of entries makes the browser stutter. A virtual list looks at the scroll position and renders only the handful of rows currently visible (plus a small buffer) as real DOM elements, treating the rest as if they didn't exist. To fake the full scrollable length, an invisible "spacer" container is sized to the true total height (row count × row height), and only the visible rows are placed inside it via translateY.
The point of the counter in the demo below is that it hovers around 10–20 no matter how far you scroll — the data has 10,000 rows, but the DOM only ever holds as many as fit on screen. Compared with pagination, pagination shows the user an explicit "page" number and jumps between them on click, while a virtual list scrolls smoothly as one continuous list with no page boundaries, swapping rows only internally.
Variable row heights make this much harder — scroll position alone can't tell you which row index you're at, so you need separate logic caching each row's measured height. For screen reader users, giving the container aria-setsize/aria-posinset-style hints about the total count and current position helps make sense of a list that isn't fully in the DOM.