Pagination is essential for APIs returning large datasets. The wrong approach leads to slow queries and inconsistent results. Here's how to choose and implement the right pattern.
Pagination Approaches#
Offset Pagination:
- ?page=2&limit=20
- Simple to implement
- Can skip items on updates
- Slow for large offsets
Cursor Pagination:
- ?cursor=abc123&limit=20
- Stable during updates
- No page jumping
- Efficient for any position
Keyset Pagination:
- ?after_id=123&limit=20
- Uses indexed columns
- Highly efficient
- Limited sorting options
Offset Pagination#
Cursor Pagination#
Keyset Pagination#
Bidirectional Cursor#
GraphQL Relay-Style#
Comparison#
| Feature | Offset | Cursor | Keyset |
|-------------------|-------------|-------------|-------------|
| Random access | Yes | No | No |
| Stable results | No | Yes | Yes |
| Large datasets | Slow | Fast | Fast |
| Implementation | Simple | Medium | Simple |
| Sorting | Flexible | Flexible | Limited |
| Client complexity | Low | Medium | Low |
Best Practices#
Use Offset when:
- Small datasets (< 10,000 items)
- Users need page numbers
- Random page access required
Use Cursor when:
- Large or growing datasets
- Real-time data (new items added)
- Infinite scroll UI
- Consistency matters
Always:
✓ Set maximum page size
✓ Include total count when practical
✓ Document your pagination format
✓ Index sort columns
✓ Handle invalid cursors gracefully
Conclusion#
Cursor pagination is the best default for APIs. It's efficient at any position, handles live data updates, and works well with infinite scroll. Use offset only when users truly need page numbers and datasets are small.
The key is matching your pagination approach to your use case and data size.