Skip to content
API Design Learn/Querying, Errors & Contracts
Browsing as a guest. Sign in to save your progress and earn XP as you complete chapters.

Pagination: Cursor vs. Offset

7 min read

You'll learn to

  • -Implement offset-based and cursor-based pagination, and explain the correctness problem offset pagination has under concurrent writes
  • -Design a pagination response envelope that tells the client whether more pages exist

No collection endpoint should ever return "everything" unbounded - pagination caps response size and gives clients a predictable way to walk through a large result set. The two dominant approaches, offset and cursor, look similar at first glance and behave very differently once real, concurrent writes enter the picture.

Offset Pagination: Simple, With a Real Correctness Bug

Offset pagination - simple, but page 2 can skip or repeat rows
GET /orders?limit=20&offset=0     # first 20 orders
GET /orders?limit=20&offset=20    # next 20 orders

Offset pagination is trivial to implement and lets a client jump directly to "page 5," but it has a genuine correctness problem under concurrent writes: if a row is inserted before the current offset while a client is paging through, every subsequent page shifts by one, causing the client to either skip a row it should have seen or see a row twice. For a slowly-changing admin table this rarely matters in practice; for a live feed with constant writes, it is a real, user-visible bug.

Cursor Pagination: Stable Under Concurrent Writes

Cursor pagination - stable regardless of concurrent inserts
GET /orders?limit=20
HTTP/1.1 200 OK
{
  "data": [ /* 20 orders */ ],
  "next_cursor": "eyJpZCI6MTIwfQ==",
  "has_more": true
}

# the client passes the returned cursor to get the next page:
GET /orders?limit=20&cursor=eyJpZCI6MTIwfQ==

A cursor encodes a stable position (typically the last-seen item's sort key, like an ID or timestamp, often base64-encoded to keep it opaque to the client) rather than a raw row count. Because the next page is defined relative to "after this specific item" instead of "skip N rows," a new insertion anywhere else in the table doesn't shift what the next page returns - the trade-off is that a client can no longer jump straight to "page 5" without walking through the pages before it.

Offset vs. Cursor, at a Glance
Simple, supports jump-to-page, breaks under concurrent writes
Offset
Stable under writes, no jump-to-page, the standard for feeds/infinite scroll
Cursor

The Response Envelope

Whichever approach is used, the response should tell the client explicitly whether more data exists (`has_more`, or a `next_cursor` that is simply absent/null on the last page) rather than making the client infer it from getting back fewer than `limit` items - that inference breaks the moment the result count happens to land exactly on a page boundary.

Never expose raw internal database identifiers as an opaque-looking cursor without actually opacifying them (e.g. base64-encoding a JSON blob) - a "cursor" a client can trivially decode and manipulate isn't really opaque, and clients will inevitably start depending on its internal structure once they notice it.

Interview Signal

You're designing pagination for a social media feed that receives new posts constantly. The interviewer asks why you'd avoid offset pagination here specifically.

Weak Answer

"Offset pagination is generally worse, so I'd always avoid it."

Strong Answer

"For a feed with constant writes, offset pagination has a real correctness bug: if new posts are inserted while a user scrolls, every post shifts position in the underlying order, so the client can end up seeing the same post twice or skipping one entirely between page loads. Cursor pagination avoids this because each page is defined relative to 'after this specific post,' which stays correct regardless of what gets inserted elsewhere. I wouldn't rule out offset pagination everywhere, though - for a rarely-changing admin table where jump-to-page matters more than write-concurrency correctness, it's still a reasonable choice."

Check Yourself1 / 3

What correctness problem does offset pagination have under concurrent writes?

Ready to Build This?

Design Page Turner in the API Design Lab's REST Foundations act.

ScaleDojo Logo
Initializing ScaleDojo