Back to all terms
ClientAPIrequestresponse
APIintermediate

API Pagination (Cursor vs Offset)

Strategies for breaking large result sets into pages, with cursor-based pagination offering stable results and offset-based offering simplicity.

Also known as: Cursor Pagination, Offset Pagination, Keyset Pagination

Description

API pagination divides large collections into manageable pages to limit response size and database load. Offset-based pagination (e.g., ?offset=40&limit=20) is intuitive and supports random page access but suffers from well-known issues: skipped or duplicated records when the underlying data changes between page requests, and degrading database performance because OFFSET requires scanning and discarding rows. For large datasets, OFFSET 100000 can be extremely slow.

Cursor-based pagination (also called keyset pagination) solves these problems by encoding a pointer to the last item returned -- typically the sort key values -- into an opaque cursor token. The next page is fetched using a WHERE clause (e.g., WHERE created_at > $cursor_timestamp AND id > $cursor_id) rather than OFFSET, which is both stable against insertions/deletions and performant because it uses an index seek rather than a scan. The trade-off is that cursors don't support jumping to arbitrary pages.

In practice, cursor-based pagination is strongly preferred for any dataset that changes frequently or grows large. The cursor should be opaque (base64-encoded) so the server can change its internal format without breaking clients. The response should include a next_cursor field (null when no more pages exist) and a has_more boolean. The Relay connection specification provides a well-established convention for cursor pagination in GraphQL contexts.

Prompt Snippet

Implement cursor-based pagination for all list endpoints using an opaque base64-encoded cursor containing the last record's composite sort key (created_at, id). Return a response envelope with { data: [...], next_cursor: string | null, has_more: boolean }. Support a limit query parameter (default 20, max 100). Generate the WHERE clause as (created_at, id) > ($cursor_ts, $cursor_id) with a matching composite index. Reserve offset pagination only for admin/backoffice UIs that need page-number navigation and operate on small, stable datasets.

Tags

paginationcursoroffsetperformancedatabase