Design Infinite Scrolling
Problem Design an infinite-scrolling feed (a restaurant list or similar) that loads more content as the user approaches the bottom, and account for the edge cases that make a naive implementation break.
Requirements
loadNextPage(cursor) -> { items, nextCursor, hasMore }- Trigger detection when the user nears the end of the rendered list
- Exactly-once page loading — no duplicate fetches, no skipped pages
- Explicit loading, error, and end-of-list states
- Bounded memory regardless of how far the user scrolls
Core design
- Trigger: an
IntersectionObserveron a sentinel element below the last item. It fires only when the sentinel enters the viewport, costs nothing while idle, and avoids the scroll-handler approach that fires on every frame and forces layout reads. SetrootMarginso the fetch starts a screen early and the user never sees the spinner. - Cursor-based pagination, never offset. With offset, any insertion or deletion above the window shifts every subsequent row: the user sees a duplicated item or silently misses one. A cursor anchors to a stable position in the result set, so a mutating dataset can't corrupt the sequence. Treat
hasMoreas the authority for whether to keep paging. - In-flight guard: a single
isLoadingflag (or the request promise itself) gates the trigger. Rapid scrolling fires the observer repeatedly and, unguarded, launches N identical requests for the same cursor. - Virtualization/windowing: after a few thousand rows, unbounded DOM nodes are the thing that kills the page. Render only the visible window plus a buffer and recycle nodes, keeping DOM size constant. This is the difference between a feed that survives deep scroll and one that janks to a halt.
- Append, don't replace: new pages concatenate onto the existing list, keyed by stable item IDs so reconciliation reuses rows instead of remounting them.
- State machine per page — idle / loading / error / end — rather than a lone boolean, so a failed page can be retried without resetting the whole feed.
Discussion points
- Race conditions: two page fetches resolving out of order append content in the wrong sequence. Discard responses whose cursor no longer matches the expected one, or serialize fetches entirely.
- Scroll restoration on back-navigation is the classic failure: returning to the feed rebuilds page 1 and dumps the user at the top. Requires caching loaded pages plus the scroll offset, and restoring before paint.
- Trade-off: infinite scroll boosts engagement but destroys the footer, breaks deep-linking to a position, and makes "find the thing I saw" impossible. A "Load more" button or hybrid paging is often the better product answer — worth raising.
- Duplicate items across pages still occur even with cursors when the sort key isn't unique; dedupe by ID on append.
- Accessibility: content appearing without announcement strands screen readers and keyboard users; a live region and a real focus target matter.
- Error handling mid-feed should degrade to an inline retry row, not an empty feed.
- Extension: prefetching page N+1 while N renders, and bidirectional scrolling (loading upward), which needs scroll-anchoring or the viewport jumps as content prepends.
asked …