API Architecture and Integration Design · Designing Robust APIs
Pagination, Filtering and Sorting
Return large collections efficiently and predictably.
Never return unbounded collections. Offset pagination (?page=3&limit=50) is simple and allows jumping to pages but gets slow for deep pages and shifts when data changes. Cursor (keyset) pagination (?cursor=...) is fast and stable for feeds and large tables. Allow-list filter and sort fields, cap page sizes, and return pagination metadata or Link headers.
GET /v1/orders?status=paid&sort=-created_at&limit=50&cursor=eyJjIjoiMjAyNi0xMC0xMFQwOToxMjo0NFoiLCJpIjo4ODEyfQ
HTTP/1.1 200 OK
Link: </v1/orders?status=paid&sort=-created_at&limit=50&cursor=eyJj...>; rel="next"
{"items":[ ... 50 orders ... ],"next_cursor":"eyJjIjoiMjAyNi0xMC0wOV..."}
Common pitfalls
- Unbounded
limit. - Sorting on non-unique keys without a tiebreaker.
- Expensive COUNT(*) on every request.
Key points
- Always paginate
- Cursors for large/changing data
- Allow-listed filters and sorts
Further reading: Use The Index, Luke: No offset
Practice questions
Why does OFFSET pagination get slower for deep pages?
Answer
The database must read and discard all skipped rows before returning the page, so cost grows with the offset.
Why must cursor pagination sort by a unique key?
Answer
Ties would make the 'after this row' position ambiguous, causing skipped or duplicated items between pages.
Open in the app to mark lessons complete, take notes and track your progress.