Cursor Pagination
Cheat Sheet
Prime Use Case
Essential for infinite scroll UIs, high-frequency data feeds, and large-scale datasets where performance and consistency are paramount.
Critical Tradeoffs
- Eliminates item skipping/duplication during data mutations
- O(1) or O(log N) performance regardless of depth
- Prevents 'Jump to Page X' functionality
- Requires unique, deterministic sort keys
Killer Senior Insight
Cursor pagination shifts the pagination state from the 'index' of the result set to the 'identity' of the data itself, making it the only viable solution for distributed systems where the underlying dataset is a moving target.
Recognition
Common Interview Phrases
Common Scenarios
- Social media activity feeds
- Real-time chat message history
- Audit logs and event streams
- E-commerce product listings with infinite scroll
Anti-patterns to Avoid
- Admin dashboards requiring specific page navigation
- SEO-driven content where 'Page 5' must be a shareable, static URL
- Small, static datasets where Offset pagination is simpler to implement
The Problem
The Fundamental Issue
The 'Offset Drift' problem, where users see duplicate items or skip items entirely when records are inserted or deleted while they are paginating.
What breaks without it
User experience suffers from 'stuttering' feeds (seeing the same post twice)
Database performance degrades linearly (O(N)) as users navigate deeper into the results
Inconsistent data views across different client sessions
Why alternatives fail
Offset pagination requires the database to scan and discard all previous rows before returning the requested window
Offset is stateless; it doesn't know if the item at index 10 is the same item that was at index 10 a second ago
Mental Model
The Intuition
Think of a bookmark in a physical book. Offset pagination is like saying 'Open to page 50'—if someone rips out page 10, the content of 'page 50' changes. Cursor pagination is like saying 'Start reading after the sentence that ends with the word Apple'—no matter how many pages are added or removed before it, you always know exactly where to resume.
Key Mechanics
Unique, ordered identifier (e.g., UUID, Snowflake ID, or Timestamp + ID)
Opaque cursor token (usually Base64 encoded) sent to the client
API response includes 'next_cursor' and 'has_next_page' metadata
SQL 'WHERE' clause filtering on the cursor value instead of 'OFFSET' skipping
Framework
When it's the best choice
- Infinite scrolling or 'Load More' patterns
- Mobile applications with 'Pull to Refresh' capabilities
- High-concurrency environments with frequent writes
When to avoid
- When users need to jump to the middle or end of a list immediately
- When the UI must display the total number of pages
- When the data cannot be sorted by a unique, immutable column
Fast Heuristics
Tradeoffs
Strengths
- Stable results: items don't shift when new data is inserted
- High performance: utilizes database indexes efficiently without scanning skipped rows
- Scalability: works well with distributed databases and sharding
- Reduced server load: avoids expensive 'COUNT(*)' queries for every request
Weaknesses
- No random access: users cannot skip to arbitrary positions
- Implementation complexity: requires careful selection of sort keys and tie-breakers
- Harder to debug: opaque cursors are not human-readable
- Sorting limitations: the cursor must be tightly coupled to the sort order
Alternatives
When it wins
Small datasets or UIs requiring explicit page numbers and 'Jump to Page' functionality.
Key Difference
Uses a numeric skip count (OFFSET) rather than a record-specific pointer.
When it wins
When you want the performance of cursors but want to keep the API parameters transparent and readable.
Key Difference
Uses raw field values (e.g., ?since=2023-01-01) directly in the URL instead of an opaque Base64 token.
Execution
Must-hit talking points
- Mention the Relay Connection Specification as the industry standard for cursor-based APIs
- Explain the importance of 'Opaque Cursors' to prevent clients from depending on internal DB structures
- Discuss 'Tie-breaking': using a secondary unique column (like ID) if the primary sort column (like Timestamp) is not unique
- Address bi-directional pagination using 'before' and 'after' cursors
Anticipate follow-ups
- Q:How would you handle a cursor for a record that was just deleted?
- Q:How do you implement 'Previous Page' logic efficiently?
- Q:What are the security implications of exposing internal IDs in cursors?
- Q:How does this interact with client-side caching libraries like Apollo or React Query?
Red Flags
Using a non-unique column as the sole cursor.
Why it fails: If multiple records share the same value (e.g., same timestamp), the pagination will skip records or enter an infinite loop.
Exposing raw database IDs in the cursor string.
Why it fails: It leaks internal implementation details and makes it difficult to change the underlying data store or sorting logic without breaking clients.
Calculating 'Total Count' on every cursor request.
Why it fails: The performance benefit of cursors is negated if the database still has to perform a full table scan to count all records.