API Pagination Best Practices: Offset, Cursor, and Keyset Compared
api-designpaginationbackendperformancecomparison

API Pagination Best Practices: Offset, Cursor, and Keyset Compared

CCodeWithMe Editorial Team
2026-06-11
10 min read

A practical comparison of offset, cursor, and keyset pagination to help you choose the right API design for scale, consistency, and client UX.

Choosing an API pagination strategy is one of those decisions that looks small early on and becomes expensive to change later. This guide compares offset, cursor, and keyset pagination in practical terms: how they behave under load, how they affect consistency, how hard they are to implement, and where each one fits best. If you are designing a REST API, revisiting an older endpoint, or trying to debug duplicate or missing records in list responses, this article will help you pick a pagination model you can live with as your data and traffic grow.

Overview

Pagination is the mechanism an API uses to split large result sets into smaller, predictable chunks. Instead of returning every record at once, the server returns a subset plus enough information for the client to request the next page.

At a glance, the three common strategies are:

  • Offset pagination: the client asks for a page by saying “skip N records, then return the next M.”
  • Cursor pagination: the client asks for the next page by sending back a token from the previous response.
  • Keyset pagination: the client asks for records after or before a known sort key, such as created_at or id.

All three approaches can work. The right choice depends less on fashion and more on your dataset, sorting needs, consistency requirements, and user experience.

Here is the short version:

  • Use offset pagination when simplicity matters more than perfect consistency and your dataset is moderate.
  • Use cursor pagination when you need stable traversal across changing data and want a client-friendly API for infinite scroll or large feeds.
  • Use keyset pagination when performance and stable ordering matter, and your sort order can be tied to indexed columns.

Many teams start with offset because it is easy to understand, then move toward cursor or keyset once they hit scale, write-heavy workloads, or user-visible paging bugs.

How to compare options

The cleanest way to compare pagination strategies is to evaluate them against the problems your API actually has to solve. Before picking one, answer these questions.

1. How large can the result set become?

If your largest collection will stay small, offset may remain perfectly acceptable. But if you expect millions of rows, deep offsets usually become less efficient because the database still has to process and skip records before returning the next slice.

2. Is the underlying data changing frequently?

If new rows are constantly inserted, updated, or deleted, offset pagination can produce shifting pages. A user may see duplicates or miss records between requests because the meaning of “row 101” changes as the dataset changes. Cursor and keyset approaches are generally better when consistency across requests matters.

3. What sort order do you need?

Pagination only works well when ordering is explicit and stable. If your API supports arbitrary sorting on many fields, offset may be easier to offer broadly. Keyset works best when the sort order is narrow and predictable, such as newest first by created_at and then id as a tiebreaker.

4. Do clients need random access to pages?

If your UI needs page 1, page 2, page 25, and a visible page count, offset is the most natural fit. Cursor and keyset are designed more for sequential navigation than for jumping to an arbitrary page number.

5. How important is implementation simplicity?

Offset pagination is easy to explain, easy to test, and easy to expose in documentation. Cursor pagination introduces token design, validation, and encoding concerns. Keyset pagination requires careful thinking about indexes, tie-breaking, and comparison operators.

6. What are your performance constraints?

If low-latency list endpoints are critical and the dataset is large, cursor and keyset strategies tend to age better. They are usually designed around an ordered index and avoid deep scans associated with large offsets.

7. Do you need bidirectional navigation?

Going “next” is straightforward in all models. Going “previous” can be trickier with cursor and keyset, especially if you want stable behavior while preserving sort order. This is solvable, but it should be designed intentionally rather than added later as an afterthought.

A useful comparison lens is this: offset optimizes developer convenience early, while cursor and keyset optimize query behavior and consistency later.

Feature-by-feature breakdown

This section compares offset, cursor, and keyset pagination on the dimensions that matter most in production APIs.

Offset pagination

Typical request:

GET /articles?limit=20&offset=40

Typical SQL shape:

SELECT *
FROM articles
ORDER BY created_at DESC, id DESC
LIMIT 20 OFFSET 40;

Strengths:

  • Very easy for clients and developers to understand.
  • Works well with traditional page-based UIs.
  • Supports random page access more naturally than cursor-based systems.
  • Quick to implement for internal tools, admin dashboards, and smaller datasets.

Weaknesses:

  • Can degrade as offsets grow larger.
  • Prone to inconsistent results when rows are inserted or deleted between requests.
  • Can produce duplicates or gaps in active datasets.
  • Total count calculation can become expensive depending on the query.

Best use cases:

  • Back-office interfaces
  • Reporting screens where exact page numbers matter
  • Low-churn datasets
  • Prototypes and early-stage APIs

Design notes:

  • Always require a deterministic ORDER BY. If values can tie, add a unique secondary field such as id.
  • Cap limit to avoid oversized payloads.
  • Be careful exposing very large offsets if performance is uncertain.

Cursor pagination

Typical request:

GET /articles?limit=20&after=eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0xNVQxMjozMDowMFoiLCJpZCI6MTIzNH0=

In this pattern, the after value is an opaque token generated by the server. It often encodes the last item’s sort position and sometimes additional metadata.

Strengths:

  • More stable for data that changes between requests.
  • Usually better suited to feeds, timelines, and infinite scroll.
  • Hides internal query details from clients by using opaque tokens.
  • Can be designed to scale better than offset pagination.

Weaknesses:

  • More complex to implement and document.
  • Random page jumps are not natural.
  • Debugging can be harder because the pagination state is encoded in a token.
  • Requires careful token validation and expiration policies if relevant.

Best use cases:

  • User activity feeds
  • Public APIs serving large or frequently changing collections
  • Mobile apps and infinite scroll interfaces
  • Endpoints where consistency matters more than numbered pages

Design notes:

  • Keep the cursor opaque. Clients should treat it as a token, not parse it.
  • Base the token on a stable sort order.
  • Include enough information to resume pagination safely, often the last sort key and a tiebreaker.
  • Return pagination metadata clearly, such as next_cursor, prev_cursor, and whether more results exist.

Keyset pagination

Typical request:

GET /articles?limit=20&created_before=2026-01-15T12:30:00Z&id_before=1234

Typical SQL shape:

SELECT *
FROM articles
WHERE (created_at, id) < ('2026-01-15T12:30:00Z', 1234)
ORDER BY created_at DESC, id DESC
LIMIT 20;

Keyset pagination is closely related to cursor pagination. The difference is mostly in how explicit the boundary values are. A keyset approach usually exposes the pagination key directly or conceptually, while a cursor wraps that information in a token.

Strengths:

  • Efficient on large datasets when supported by proper indexes.
  • Stable under inserts and deletes when the ordering keys are well chosen.
  • Avoids the cost of deep offsets.
  • Clear query semantics for append-only or time-ordered data.

Weaknesses:

  • Needs a stable, index-friendly sort order.
  • Not ideal for arbitrary sorting or filters that break index use.
  • More difficult to support generic page-number navigation.
  • Requires careful handling of ties with composite keys.

Best use cases:

  • Large event streams
  • Audit logs
  • Messaging history
  • Any endpoint ordered by unique or near-unique fields such as timestamp plus id

Design notes:

  • Never rely on a non-unique sort field by itself if duplicates are possible.
  • Use a composite order like created_at DESC, id DESC.
  • Back the query with an index that matches the ordering and filtering pattern.

Consistency and correctness

If your API must avoid duplicates and skips as much as possible, keyset and cursor models are usually safer than offset. Offset can still be correct enough for many applications, but it is more sensitive to concurrent writes.

Whatever model you choose, define a stable ordering contract and document it. Pagination bugs often come from unclear ordering rather than the pagination mechanism itself.

Performance and database behavior

Performance depends on schema, indexes, filters, and database engine, but the broad tradeoff is durable:

  • Offset is often simplest but can become less efficient at deep pages.
  • Cursor usually scales better when backed by an ordered query plan.
  • Keyset is often the most efficient for sequential traversal on large datasets.

To verify your choice, inspect actual query plans and test realistic filters. A theoretically good pagination design can still perform poorly if the sort order is not indexed or if your endpoint joins several large tables.

Client experience and API design

From a client perspective:

  • Offset feels familiar because page numbers are easy to display.
  • Cursor feels smooth for “load more” and infinite scroll interactions.
  • Keyset can feel simple in purpose-built systems but less generic for broad third-party consumption.

For REST API best practices, prioritize clarity over flexibility. A smaller, well-defined pagination contract is usually easier to support than a highly configurable one that behaves inconsistently across endpoints.

If you are building frontend consumers for these endpoints, strong error handling matters just as much as the pagination design. For implementation patterns on the client side, see JavaScript Fetch API Error Handling Patterns You Can Reuse Across Projects.

Best fit by scenario

If you do not want a theoretical answer, use these scenario-based recommendations as a starting point.

Scenario 1: Admin dashboard with sortable tables

Pick: Offset pagination.

Reason: internal tools often need page numbers, quick implementation, and support for sorting across several columns. The dataset may still be large, but usability and development speed often matter more than perfect consistency.

Scenario 2: Public feed of posts, comments, or activity

Pick: Cursor pagination.

Reason: feeds change constantly. Users expect “next” to continue smoothly without obvious duplicates or missing items. Opaque cursors also let you change internal details later without breaking clients.

Scenario 3: Audit log or append-only event stream

Pick: Keyset pagination.

Reason: append-heavy data with a natural order is a strong fit for keyset queries. Performance and stability usually outweigh the need for arbitrary page jumps.

Scenario 4: Search results with many filters

Usually pick: Start with offset, then evaluate carefully.

Reason: search systems often need counts, page numbers, and flexible sorting. Cursor or keyset may still work, but the implementation is more nuanced, especially if relevance scores or dynamic ranking are involved.

Scenario 5: Mobile app list endpoint

Pick: Cursor pagination.

Reason: mobile interfaces commonly use incremental loading rather than numbered pages. Cursor tokens also make payload contracts cleaner than exposing multiple comparison parameters.

Scenario 6: Legacy API that already exposes page and per_page

Pick: Keep offset unless you have a clear migration path.

Reason: changing pagination models is a breaking API change for many clients. If the current approach is acceptable, improve ordering, indexing, and limits before replacing the contract outright.

A practical decision rule

  • Need simple page numbers and can tolerate shifting results? Offset.
  • Need stable traversal for changing datasets with client-friendly tokens? Cursor.
  • Need fast sequential access over large ordered data? Keyset.

After choosing, validate the endpoint with a testing checklist that includes ordering, duplicate avoidance, limit enforcement, invalid cursor handling, and edge cases around empty pages. A good companion resource is REST API Testing Checklist: What to Verify Before You Ship. It is also worth documenting status codes for malformed pagination parameters and expired or invalid cursors; for that, see HTTP Status Code Reference for Developers: What Each Error Means and How to Fix It.

When to revisit

Your first pagination choice does not need to be permanent, but it should be revisited intentionally rather than after users start reporting missing records. Review your strategy when one or more of these signals appear:

  • List endpoints slow down as users move deeper into results.
  • Users report duplicates or skipped items in active collections.
  • Your dataset grows from thousands of rows to millions.
  • You introduce infinite scroll, mobile-heavy usage, or streaming-like interfaces.
  • You add complex filters or sorts that no longer match the original design.
  • Third-party consumers need a clearer or more stable contract.

When you revisit pagination, avoid changing everything at once. Work through this checklist:

  1. Audit current behavior. Confirm the endpoint’s sort order, index coverage, count queries, and error handling.
  2. Measure real query patterns. Look at deep-page access, common limits, and whether clients actually need page numbers.
  3. Decide whether migration is worth the compatibility cost. For public APIs, versioning may be safer than replacing parameters in place.
  4. Stabilize ordering first. Even before changing models, ensure every paginated query uses a deterministic order with a tiebreaker.
  5. Document the response contract clearly. Show examples of next-page requests, end-of-results behavior, and invalid parameter responses.
  6. Test concurrency behavior. Insert and delete rows between requests in development to see how the endpoint behaves in realistic conditions.

If you are exposing cursors as encoded strings, remember that they are not security boundaries by themselves. Treat them as transport values that still need validation and expiration rules where appropriate. For debugging encoded values and query strings around API tooling, related references on codewithme.online include the URL Encoder and Decoder Guide for Developers and the JWT Decoder Guide: How to Inspect Tokens Safely and Troubleshoot Auth Issues.

The practical next step is simple: choose one heavily used list endpoint, write down its current sort order, identify whether clients truly need page numbers, and compare that answer against the strengths of offset, cursor, and keyset. You do not need a universal pagination rule for your entire API. You need a deliberate rule for each class of endpoint, supported by stable ordering, sensible limits, and testing that matches production behavior.

That is what makes pagination durable: not picking the most advanced option, but picking the one that fits the data shape, query path, and client experience you actually have today while leaving room to evolve tomorrow.

Related Topics

#api-design#pagination#backend#performance#comparison
C

CodeWithMe Editorial Team

Senior SEO Editor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

2026-06-10T06:08:14.927Z