Decision guide

Offset vs Cursor vs Keyset Pagination

How should a collection endpoint paginate?

Side by side

CriterionOffset / limitOpaque cursorExplicit keyset
Deep-page performanceDegrades with offsetConstantConstant
Stable under inserts/deletesNo (skips/duplicates)YesYes
Jump to arbitrary pageYesNoNo
Total countNatural (but costly)OptionalOptional
Client complexityLowestLowMedium
Implementation flexibilityLowHigh (opaque)Low (exposed keys)
Index requirementSort columnComposite sort + idComposite sort + id

When to choose each

Offset / limit

?page=3&per_page=50 — skip N rows, take M.

Choose when

  • Small or admin datasets
  • Users must jump to page N
  • Data rarely changes while browsing

Avoid when

  • Large tables (deep pages are slow)
  • Feeds that change constantly

Opaque cursor

Server returns next_cursor encoding the position; clients pass it back.

Choose when

  • Public APIs and feeds
  • Infinite scroll
  • You want freedom to change the implementation

Avoid when

  • Users need random access to page numbers

Explicit keyset

Client passes the last seen sort values (?after_created=…&after_id=…).

Choose when

  • Internal exports and sync jobs
  • Clients that already track sort keys

Avoid when

  • Public APIs (leaks implementation, harder to evolve)

Verdict

Default to opaque cursors for anything user-facing or large; keep offset for small admin tables where page numbers matter. Always cap page size and include a unique tiebreaker in the sort.

Learn more

Sources