In a virtualized React list — react-window's `overscanCount` prop or TanStack Virtual's `overscan` option — what does overscan do, and what do you trade off by raising it from 3 to 30?
answer
- a buffer beyond the visible edges
- covers the gap between scroll and commit
- every extra row is real DOM
- diminishing returns, linear cost
- library defaults are small for a reason
basics
~20 sOverscan renders extra rows just beyond each edge of the viewport. Those buffer rows hide blank flashes during fast scrolling, but each one is real DOM and real render work, so a large overscan gives back the win virtualization bought.
solid answer
~50 sOverscan is the buffer of rows rendered above and below the visible range. Scroll events and React renders are asynchronous relative to the compositor, so with a window of exactly the visible rows a fast flick can expose un-rendered space before the next commit lands — visible as white bands or rows popping in a frame late. A few overscan rows mean the next rows are already mounted and laid out when they scroll into view. The cost is linear: an overscan of 30 on a viewport that shows 20 rows means roughly 80 mounted rows instead of 26, tripling per-scroll reconciliation and DOM. It has minor secondary effects too — a focused element or an in-flight image just outside the viewport survives a little longer. Typical values are 2-10; tune from recorded scroll frames rather than guessing high.
go deeper
Know the word: overscan is the small number of extra rows rendered outside the viewport, and it exists so fast scrolling does not reveal blank space.
Be able to state the mounted-row formula — visible plus twice the overscan — and explain why the cost grows linearly while the benefit flattens out almost immediately.
Talk about tuning from evidence: recorded scroll traces, dropped frames and per-row render cost, and recognise that persistent blank frames at a small overscan point at the row component, not the buffer.
Frame it as one dial among several — row cost, data readiness, placeholder strategy, scroll-jump handling — and set a team default so every list does not acquire its own hand-tuned magic number.
## What the buffer is for A windowing virtualizer computes its visible range from `scrollTop` when a scroll event fires, then React renders and commits the new slice. That whole chain is asynchronous relative to the compositor, which is already scrolling the page. If the rendered set is exactly the mathematically visible set, any scroll that outruns the render — a trackpad flick, a scrollbar drag, a programmatic jump — exposes container area whose rows have not been created yet. The user sees blank bands, or rows that appear a frame late. Overscan buys slack. Rendering a few extra rows on each side means the virtualizer is always slightly ahead of the viewport, so the pixels being revealed already have real DOM behind them. ## The cost is linear and obvious Mounted rows are `visibleCount + 2 * overscan`. On a viewport that fits 20 rows: - overscan 3 gives about 26 mounted rows - overscan 30 gives about 80 mounted rows Everything that scales with mounted rows scales with that number: element creation, style recalculation and layout, React reconciliation per scroll-driven update, effects mounting and cleaning up per row, and memory. Pushing overscan high is a quiet way to convert a virtualized list back into a slow one — the only difference from the un-virtualized case is a smaller constant. ## Picking a value Think of it as "how many rows can the user reveal between two commits": - **Cheap rows, ordinary scrolling.** 2-5 is plenty, which is why library defaults sit in that range. - **Expensive rows** (charts, images, heavy formatting). Counter-intuitively you often want *less* overscan, not more, because each buffered row costs a lot; instead make the row cheap or render a lightweight placeholder. - **Fast programmatic jumps** (scroll-to-index, PageDown, scrollbar drag). Large overscan does not help — the jump is far bigger than any buffer. Handle it with a placeholder state for rows whose data has not arrived. - **Directional bias.** Some virtualizers let you weight the buffer toward the scroll direction, which is where the exposure risk actually is; a symmetric buffer spends half its rows on the direction you are moving away from. ## Secondary effects worth naming Overscan slightly softens some rough edges, and it is worth being explicit that it only softens them: - A focused row just past the edge is not immediately unmounted, so focus survives small scrolls. This is not a focus-management strategy — a larger scroll still destroys the element. - Images or lazily-loaded row content start fetching marginally earlier, smoothing perceived load. - Native find-in-page still only sees the mounted set, so overscan does not help there in any meaningful way. Do not present it as an accessibility fix. ## The honest framing in an interview Overscan is a latency-versus-work dial with a linear cost curve and sharply diminishing returns. State the default you would start from, say you would raise it only after seeing blank frames in a recorded scroll, and note that if blank frames persist at a modest overscan the real problem is per-row render cost, not the buffer size.
- A team sets overscan to 100 to stop blank flashes. What do you tell them?That they are treating the symptom. 100 on each side means 200 extra mounted rows, most of the way back to an un-virtualized list, and the slow commits will cause the very dropped frames they are trying to fix. Profile a scroll: if a single row's render is expensive, cut that cost or render a cheap placeholder, then return overscan to a small value.
- Does raising overscan help when the user jumps to index 8,000 with a scroll-to-index call?No. The jump lands thousands of rows away, so no fixed buffer covers it — the new window is computed and rendered from scratch either way. What helps is making that first render cheap and showing a skeleton for rows whose data is not loaded, plus requesting data for the target range before or while scrolling.
saying these in an interview costs you the question
- Thinks a larger overscan is always safer or faster
- Calls overscan an accessibility or find-in-page fix
- Cannot say that overscan rows are fully rendered DOM
- Sets overscan by guesswork instead of profiling scroll frames
- Believes overscan prevents rows from ever unmounting