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 IntersectionObserver on 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. Set rootMargin so 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 hasMore as the authority for whether to keep paging.
  • In-flight guard: a single isLoading flag (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 …
LeaderboardSalaryAccount