We're exposing a public REST API that returns a large, frequently-changing list of orders. How would you design the pagination, and why?
system-design · Mid level · software-engineering
What the interviewer is really asking
Assesses whether the candidate can reason about pagination trade-offs against the data shape — picking cursor/keyset over offset for a large, mutating dataset and understanding the database and correctness costs of each — rather than reflexively reaching for page/limit.
What to say
- Tie the choice to the data: offset pagination is fine for small, stable lists but is O(N) in the database (OFFSET 10000 scans and discards 10,000 rows) and drifts when items are inserted or deleted mid-traversal, so a frequently-changing order list wants cursor or keyset pagination.
- Explain how a cursor works: encode the position of the last item (e.g. a stable sort key like created_at plus a tiebreaker id) into an opaque token; the next page is a WHERE clause on that key, which hits an index and stays O(log N) no matter how deep you page.
- Name the trade-off you accept: cursors can't jump to an arbitrary page number, so if the product genuinely needs 'go to page 50' you keep offset for shallow paging and document the limit, or expose total counts separately rather than on every page.
What to avoid
- Defaulting to page-number/offset pagination for a large dataset without acknowledging the deep-page scan cost or the duplicate/skip problem when rows shift.
- Returning an exact total count on every page when it forces an expensive full scan, instead of treating count as optional or approximate.
- Leaking internal database details (raw row offsets, primary keys) into the cursor as a plain readable value, so clients build dependencies on it and you can't change the sort.
Example answers
Strong: Orders change constantly, so I'd use keyset pagination: sort by created_at with id as a tiebreaker, and the cursor is an opaque base64 token encoding the last (created_at, id) seen. The next page is WHERE (created_at, id) < (:ts, :id) ORDER BY created_at DESC, id DESC LIMIT n, which uses the index and costs the same on page 1 or page 1000 — no scanning rows I throw away. The cost is I can't jump to an arbitrary page, but for a feed-style order list nobody does that. I'd return a next_cursor field and stop returning it when there's no more data.
Weak: I'd add page and page_size query params and use LIMIT and OFFSET, and return the total count so the client can render page numbers.