REST API Pagination Design
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
Pagination divides a large collection into bounded responses and tells the client how to continue. The first design decision is the order, not whether the parameter is named page or cursor. Without a stable order, page boundaries have no reliable meaning. If several orders share createdAt, add a unique tie-breaker such as id, for example ORDER BY created_at DESC, id DESC.
Offset pagination uses a distance such as /orders?offset=40&limit=20 or a page number translated to that distance. It is easy to understand and supports a direct jump to a later page, so it works well for smaller or mostly stable admin lists. Large offsets may be expensive, and inserts or deletes before the current position can shift the boundary between two requests.
Cursor pagination returns a server-issued token for the next position. With newest-first ordering, the token can represent the last (createdAt, id) pair, allowing the next query to continue after it rather than recounting from the front. This is usually steadier for a high-write feed. It does not naturally jump to page 37, and changes to a row's sort values can still affect what a client sees.
A page response should contain the items, the applied limit, and a next link or opaque cursor when more results may exist. The cursor must be used with the same collection, filters, and ordering that created it; the server should reject a mismatch. Page size needs a documented default and maximum. The server may return fewer items than requested, so item count alone should not be the end-of-list signal.
Offset is a good fit when page numbers and random jumps matter more than changes between requests. Cursor pagination is usually a better fit for a large, frequently updated stream. Neither design creates a frozen snapshot automatically, and an exact total can be slow or become stale while the client pages. The API should document ordering, continuation, expiry, total-count behavior, and what concurrent changes can do.
REST API Pagination Design
Direct Answer
Pagination divides a large collection into bounded responses and supplies enough navigation state to continue. Offset pagination is simple and supports page jumps; cursor pagination is more stable for frequently changing ordered data. Either design needs deterministic ordering, limits, navigation links or cursors, and a documented consistency policy.
Deep Dive & Architecture
Without deterministic ordering, page boundaries have no reliable meaning. A database is free to return equal-ranked rows in different orders. A unique final sort key creates a total order and lets the next request resume from an exact position.
Offset expresses distance from the beginning. If a new row is inserted before that distance after page one is read, a row from page one can appear again on page two. If a row is removed, another row can be skipped. A transaction-level snapshot can avoid movement but is rarely kept across independent web requests.
A cursor expresses position rather than distance. It can bind the last sort values, direction, relevant filters, caller scope, and an expiry marker. Expose it as an opaque server-issued handle or an integrity-protected token rather than readable database state. A cursor helps continuation; it never replaces authorization checks for the underlying resources.
Pagination metadata should help the client continue without rebuilding server rules. A next link is especially useful because it packages the route and controls. Exact totals, last-page numbers, and random jumps are product choices rather than mandatory REST fields, and they can conflict with efficient cursor designs.
Code Example
GET /orders?status=paid&limit=2&after=pg_7Qm2vK9x HTTP/1.1
Host: api.example.com
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{"items":[{"id":41},{"id":39}],"limit":2,"nextCursor":"pg_K4p8sT2n"}
The opaque cursor is a server-issued continuation handle. The server validates it against the same collection, filter, order, and caller context, then performs normal authorization for every returned resource.
Check your understanding
Answer these without looking back at the article.
- Why does pagination need a unique tie-breaker in its sort order?
- How can inserts cause duplicates with offset pagination?
- What information should an opaque cursor bind to?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.