← All courses

Pagination & Infinite Scroll

🗓 May 31, 2026 ⏱ 3 min read

Why you must paginate

A feed or search result can have thousands or millions of items. Loading them all is impossible on a phone — it would exhaust memory, take forever, and waste data. Pagination loads items in small pages as the user scrolls, keeping the app fast and light. It’s a fundamental mobile system-design pattern.

Two pagination styles

  • Offset/page-based — request “page 2, 20 per page” (?page=2&limit=20). Simple, but can skip/duplicate items if the list changes between pages.
  • Cursor/keyset-based — the server returns a cursor (“give me items after X”). Stable even when items are inserted, and more efficient on the backend. Preferred for feeds.
// cursor-based request
suspend fun getFeed(cursor: String?): FeedPage =
    api.getFeed(after = cursor, limit = 20)
// response: { items: [...], nextCursor: "abc123" }

The infinite-scroll loop

As the user nears the bottom of the list, load the next page and append it. The trigger is detecting that they’re close to the end.

// when the last visible item is near the end, load more
if (lastVisibleIndex >= items.size - PREFETCH_DISTANCE && !isLoading) {
    loadNextPage(nextCursor)
}

Designing for a great UX

  • Show a loading footer while the next page loads.
  • Prefetch early — start loading before the user hits the very bottom, so new items appear seamlessly.
  • Handle errors per page — let the user retry just the failed page, not the whole list.
  • Deduplicate — the same item can arrive on two pages; use a set of ids (DSA!) to avoid duplicates.
  • Empty & end states — “no results” and “you’re all caught up”.

Pagination + caching + offline

The most robust design combines pagination with the offline-first database: pages are written to the local DB as they load, the UI observes the DB, and on reopen the user instantly sees cached pages while fresh data syncs. Libraries like Android’s Paging 3 with a RemoteMediator formalise exactly this network+database pattern.

Memory and performance

Even with pagination, an endlessly growing in-memory list can bloat. Use lazy lists (RecyclerView/LazyColumn/FlatList) that recycle row views, and consider dropping far-off-screen pages from memory (keeping them on disk) for very long sessions.

Common mistakes

  • Loading the whole dataset at once (slow, memory blow-up).
  • Offset pagination on a changing feed → duplicates/skips; prefer cursors.
  • No dedupe, so repeated items appear.
  • Triggering loads only at the exact bottom (janky) instead of prefetching.
Summary: Paginate large lists — prefer cursor-based paging for stable feeds — and load the next page as the user nears the end, with a loading footer and prefetch. Deduplicate items, handle per-page errors, and combine paging with the offline database (e.g. Paging 3 + RemoteMediator) for an instant, robust experience.