pattern

Cursor Pagination

also called Keyset Pagination

Paginating by an opaque pointer to the last item seen rather than by numeric offset, giving stable results and constant-time page fetches.

Offset pagination has two defects that worsen with scale. Instability: an item inserted or deleted between requests shifts everything, so page 2 skips or repeats rows. Cost: OFFSET 100000 requires the database to scan and discard 100,000 rows, so deep pages get progressively slower.

Cursor pagination encodes the sort key of the last item — "give me the next 50 after this" — which becomes an indexed range scan of constant cost regardless of depth, and which is unaffected by insertions earlier in the set.

The constraints: the sort must be stable and unique (a timestamp alone is insufficient — tie-break on an ID), and jumping to an arbitrary page number is impossible, which is fine for infinite scroll and APIs and unacceptable for a UI that shows numbered pages.

Encode the cursor opaquely so clients cannot construct one, leaving the sort key free to change.