1Step-by-Step Breakdown
The Core Idea: Position by Value, Not by Count. Rather than saying "skip the first N rows" (offset pagination's approach), cursor pagination says "give me rows that come after THIS specific value" — the cursor encodes the last-seen row's sort key, and the next page's query filters directly on that value instead of counting through preceding rows.
Why This Solves the Performance Problem. A cursor-based query filters directly on an indexed column's value, which the database can seek to efficiently regardless of how deep into the dataset that value falls — unlike offset pagination, query performance for a cursor-based "page 5000" is essentially identical to "page 1," since there's no preceding-row scan required.
Why This Solves the Consistency Problem Too. Because a cursor references a specific value rather than a position, a row inserted or deleted elsewhere in the dataset between two page fetches doesn't shift anything — the cursor still correctly identifies "everything after this exact value," making cursor pagination immune to the skip/duplicate issue offset pagination has under concurrent writes.
Encoding the Cursor: Opaque to the Client. A well-designed cursor is an opaque, base64-encoded token the client passes back verbatim, without needing to understand or construct its internal structure — this lets the server change the cursor's internal encoding later without breaking any client that treats it as an opaque string.
A Complete Cursor Pagination Implementation. The response includes both the current page of results and an encoded nextCursor (derived from the last row returned) — the client simply passes that nextCursor value back as the cursor query parameter to fetch the subsequent page, with no page-number concept involved at all.
The Tradeoff: No Arbitrary Page Jumping. Cursor pagination's core limitation is genuine: a client cannot jump directly to "page 15" the way offset pagination allows, since each cursor only knows how to move to the immediately next (or previous) page relative to itself — a UI needing arbitrary page-number navigation (a numbered page list) is a poor fit for pure cursor pagination.
When Cursor Pagination Is the Right Choice. Cursor pagination is the clear right choice for infinite-scroll UIs (social feeds, activity logs), very large or rapidly-changing datasets where offset's consistency problems matter, and any high-traffic endpoint where deep-page performance genuinely needs to stay flat regardless of dataset size.
What is the primary reason cursor-based pagination maintains roughly constant query performance regardless of how deep into a dataset a client is paging, unlike offset pagination?
- →The database seeks directly to the cursor's indexed value, without needing to scan and discard preceding rows
- →Cursor pagination automatically caches every previous page's results
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Consistent, Flat Performance From Cursor Pagination Benefits Users on Slower Connections Scrolling Deep Into Content
A user with a slower connection or older device scrolling deep into an infinite-scroll feed benefits directly from cursor pagination's consistent performance at any depth, avoiding the progressively slower load times that offset pagination would otherwise introduce the further they scroll.
SEO Implications
- 1
Cursor Pagination Keeps Response Times Flat for High-Traffic, Deeply-Paged Endpoints
For a high-traffic endpoint serving a large, actively-growing dataset, cursor pagination's constant-time performance characteristic (regardless of page depth) directly protects server response time and page-speed-related search ranking signals, compared to offset pagination's degrading performance at scale.
Best Practices
Encode cursors as opaque tokens the client passes back verbatim, never exposing their internal structure directly
This decouples clients from the cursor's internal implementation, letting the server change that structure later without breaking any existing client integration.
Choose cursor pagination specifically for infinite-scroll UIs, very large or rapidly-changing datasets, and high-traffic endpoints needing flat performance at scale
This matches the technique to the use cases where its genuine benefits (constant performance, consistency under concurrent writes) clearly outweigh its core limitation of not supporting arbitrary page-number jumping.
Frequent Bugs
An infinite-scroll feature's load time noticeably degrades the deeper a user scrolls, or occasionally shows a duplicated item as more content loads.
This is the classic signature of offset-based pagination being used for a use case (infinite scroll on a large or actively-changing dataset) that cursor-based pagination is specifically designed to handle well. Migrate the endpoint to cursor pagination for consistent performance and immunity to the concurrent-write consistency issue.
Real-World Examples
Migrating an Activity Feed From Offset to Cursor Pagination
A social activity feed using offset pagination showed increasingly slow load times as users scrolled deep into their history, with response times climbing from under 50ms on the first page to over 2 seconds by the 50th page of infinite scroll. Migrating to cursor-based pagination, using a composite (createdAt, id) cursor matching the feed's existing sort order and a supporting database index, brought response times down to a consistent, flat 40-60ms regardless of how deep into the feed a user had scrolled — since the database could now seek directly to the requested cursor position instead of scanning through and discarding all preceding rows.
// After migration: consistent response time at any scroll depth
WHERE (created_at, id) < (?, ?) ORDER BY created_at DESC, id DESC LIMIT 20