Offset vs Cursor vs Keyset Pagination: Best Practices for Scalable APIs
How to paginate large datasets efficiently using Offset, Cursor, and Keyset strategies—complete with pros, cons, and real-world use cases

Before we dig deep into the details. Let’s break down on what problem this article really addresses, what solution works best and what are the real world use cases —
🧵 Problem:
GET /itemswith 10,000 records? Brutal.📘 Solution: Explore pros/cons of offset, cursor-based, and keyset pagination.
🧠 Use cases: infinite scroll, large list APIs.
Let’s begin…
1. 🔥 Introduction: Why Pagination Matters
Brief on modern APIs fetching massive datasets: products, users, posts, etc.
Without pagination:
Slow response times
High memory usage
Risk of app crashes
Pagination isn’t one-size-fits-all. Let's compare the three most popular strategies.
2. 🧮 Offset Pagination (LIMIT/OFFSET)
✅ How it works:
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 30;
✔️ Pros:
Simple to implement
Easy to understand for devs
Works well for small datasets
❌ Cons:
Expensive on large datasets (DB still scans all skipped rows)
Risk of duplicate or missing data when records are inserted/deleted during pagination
Not ideal for infinite scroll or real-time apps
🛠️ When to use:
Admin dashboards
One-time exports
Non-realtime tables with moderate data
3. 🧭 Cursor-Based Pagination (a.k.a. Seek Method)
✅ How it works:
SELECT * FROM users WHERE id > 100 ORDER BY id ASC LIMIT 10;
- Instead of skipping rows, it uses a reference ("cursor") to fetch the next set.
✔️ Pros:
Much faster on large datasets
Stable even if records are inserted or deleted
Ideal for real-time pagination or infinite scroll
❌ Cons:
Harder to implement for multi-column sorts
Can’t jump to arbitrary page (e.g., page 500)
🛠️ When to use:
Public feeds (e.g. Twitter, Reddit)
Infinite scrolling
APIs with fast UX requirements
4. 🔑 Keyset Pagination (a refined Cursor Pagination)
"Keyset" is the more academic name for cursor pagination when it involves compound primary keys or sort keys.
✅ How it works:
SELECT * FROM posts WHERE (created_at, id) > (?, ?) ORDER BY created_at, id LIMIT 10;
- Uses multiple fields as cursors to avoid sorting conflicts
✔️ Pros:
Most reliable and performant method
Eliminates page drift completely
Fully stable even during data changes
❌ Cons:
Cannot jump to random page
Requires understanding of ordering logic
Slightly harder to paginate backward
🛠️ When to use:
Large datasets with dynamic insertions
Financial records, event logs, social media feeds
5. 🧪 Real-World Code Comparisons
REST API: Offset Example
GET /products?limit=10&offset=30
REST API: Cursor Example
GET /posts?after=eyJpZCI6MTAwMX0=
afteris a Base64 encoded cursor, like{ "id": 1001 }
MongoDB Example (Cursor style)
db.posts.find({
_id: {
$gt: ObjectId("...")
}
}).limit(10);
6. 🧠 Which One Should You Use?
| Criteria | Offset | Cursor | Keyset |
| Ease of use | ✅ ✅ ✅ | ✅ ✅ | ✅ |
| Performance (large data) | ❌ | ✅ | ✅ ✅ ✅ |
| Supports page jump | ✅ ✅ ✅ | ❌ | ❌ |
| Consistent on changing data | ❌ | ✅ ✅ ✅ | ✅ ✅ ✅ |
| Best for infinite scroll | ❌ | ✅ ✅ | ✅ ✅ ✅ |
7. 🚨 Common Pitfalls to Avoid
Sorting mismatch: Always match
ORDER BYwith your cursor condition.Stateless cursors: Don’t store pagination state server-side — encode it in the cursor.
Unstable fields: Never paginate by non-unique or volatile fields like
name.
8. ✅ Conclusion: Pick the Right Tool for the Job
Offset is easy, but fragile for large or live data.
Cursor is efficient, ideal for real-time apps.
Keyset is rock-solid for large-scale, high-integrity pagination needs.
Choose your pagination based on UX goals, dataset size, and data volatility.
📣 Call to Action:
"Which pagination method do you use in your APIs today? Ever had to switch strategies mid-project? Share your experience below!"






