API design

Pagination API design: interview questions and practical design

Pagination API design controls how a client traverses a changing collection without duplicates, missing records, or unbounded database work.

Written and reviewed by Sahil Srivastav

API designBackend engineeringInterview guide

What it actually is

Pagination API design controls how a client traverses a changing collection without duplicates, missing records, or unbounded database work.

Offset pagination becomes slow and unstable when rows are inserted or deleted before later pages.

The useful interview answer is precise about the boundary: Order by a unique, indexed tuple such as `(created_at, id)`. A timestamp alone ties rows and permits duplicates or skips between requests.

Why it matters in production

Offset pagination becomes slow and unstable when rows are inserted or deleted before later pages.

A good page contract bounds work, has a deterministic order, and tells the client whether more data exists without loading the entire collection.

How it works

Stable ordering

Order by a unique, indexed tuple such as `(created_at, id)`. A timestamp alone ties rows and permits duplicates or skips between requests.

Cursor boundary

Encode the last tuple in an opaque cursor. The next query uses a strict lexicographic predicate, such as `created_at < cursor_time OR (created_at = cursor_time AND id < cursor_id)`.

Snapshot versus live feed

A cursor usually traverses a moving view, not a transaction snapshot. State that guarantee; if a consistent export is required, use a snapshot token or an export job instead.

Implementing it

Implement forward pagination with a unique composite order and an opaque cursor.

Insert and delete records between page requests and check for duplicates and omissions.

Bound page size server-side and return a clear next cursor or null.

Interview questions and how to answer them

Why is a cursor better than offset?

It turns the next page into a seek from the last ordered key, avoiding scans of skipped rows and reducing movement when new rows arrive before the cursor.

What belongs in a cursor?

The complete ordering boundary and enough filter or version context to ensure the next request means the same traversal. Sign or encrypt it if clients must not alter it.

How do you paginate a changing collection?

Use a stable unique order and define that each request sees a moving collection. If exact consistency matters, create a snapshot or asynchronous export.

Answers that lose the round

  • Using offset as the only ordering guarantee.
  • Putting mutable filters into a cursor without validating them on the next request.
  • Returning an offset that lets clients request arbitrary expensive pages.
  • Claiming cursor pagination gives a full historical snapshot.

Practise in a real repository

Explaining a concept and enforcing it in code are different skills, and machine coding rounds test the second. Gronex ships broken backend repositories whose test suites assert the invariant rather than the happy path.

FAQ

Should cursors be readable?

Opaque cursors keep the contract flexible and prevent clients depending on storage details. They can be base64-encoded signed data rather than a database id alone.

How large should a page be?

Set a server maximum based on response size, query cost, and downstream limits; let clients request smaller pages.

Can offset pagination be acceptable?

Yes for small, mostly static collections or administrative screens. State its cost and consistency limits instead of treating it as universally wrong.

More backend concepts