24. APIs and Idempotency
24.4 How does pagination work (offset vs cursor)?
At the machine: The screen shows items 1-10. You press ▶ for the next 10. If the shop restocks while you're scrolling, the list can shift under you.
In the system: Lists (orders, books, reviews) are too long to return in one response, so APIs return pages. Two ways:
Offset pagination: GET /orders?offset=20&limit=10 → "skip 20, give me 10".
- Simple, and you can jump to page 7.
- Shifting data: if a new order arrives at the top between page 1 and page 2, everything moves down one, so the last item of page 1 appears again on page 2 (deletes make items vanish instead). In the chapter's code, order 91 is shown twice.
- Slow deep pages:
OFFSET 100000makes the database read and throw away 100,000 rows.
Cursor (keyset) pagination: GET /orders?after=91&limit=10 → "give me 10 after the last one I saw".
- The server turns it into
WHERE id < 91 ORDER BY id DESC LIMIT 10, which uses the index, so page 1,000 is as fast as page 1. - Stable: new or deleted rows don't cause repeats or gaps. In the chapter's code, page 2 starts at 90 even though order 101 arrived.
- Can't jump to "page 7"; the cursor is usually an opaque string (encoded sort key and ID) so clients don't build it themselves.
Figure: "skip N" breaks when the list changes; "after this one" doesn't.
Use cursors for feeds, timelines, order histories and any large or changing list; offsets are fine for small, stable lists or admin screens that need page numbers. Always cap the page size.
Memory code: "Don't say 'skip 20'; say 'after this one'."