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.