DEV Community

Nadeem Ur-Rehman
Nadeem Ur-Rehman

Posted on

Offset Pagination Silently Skips Rows. Your Demo Just Has Not Noticed Yet.

Your pagination works in the demo. Then production arrives with a dataset that moves, and rows start vanishing.

The bug with no stack trace

Here is the failure nobody catches in staging: OFFSET pagination. Page 1 fetches rows 1-20. Someone deletes row 4. Page 2 asks for OFFSET 20, and the rows shift left underneath you. Rows quietly drop off or show up twice, and nothing throws an error. Your UI looks fine. The data is just wrong.

That is the worst kind of bug. No red banner, no failed request, just a support ticket three weeks later asking where their invoice went.

Why does OFFSET even exist? It is simple to implement, simple to explain, and perfectly correct on datasets that never change. The demo dataset never changes. That is the trap.

The fix interviewers want to hear

Cursor pagination. Instead of "give me rows 20 to 40," you say "give me 20 rows after this cursor," where the cursor is an opaque, stable token anchored to the last row you saw. Rows can be added or deleted ahead of it and your page does not shift, because the window is anchored to data, not to a number.

Three rules that survive the whiteboard:

  1. Never use OFFSET on data that moves. Admin list with ten rows? Fine. Anything with concurrent writes, like an order queue or an activity feed? Cursor.
  2. Make the cursor opaque and stable. Base64-encode it. Do not hand clients raw row IDs they can tamper with or guess. Tie it to a sort key that is unique and monotonic, like (created_at, id).
  3. The sort key must be unique. created_at alone collides. Duplicates in the sort key mean rows can still slip between pages.

And the non-obvious angle nobody mentions in interviews: cursors change your API contract. Clients cannot jump to page 47 anymore, and "total pages" becomes expensive or impossible. Design for that up front. Return has_next_page, not page counts, and let product decide whether random access is really worth the lying it requires.

Copy-paste decision rule, for the interview and for real life: if the dataset is write-active, anchor the page to a row (cursor). If it is static, OFFSET is fine, and I will document the assumption.

I broke this down in 40 seconds here: https://www.youtube.com/shorts/czZkXS-1AQo

I turned the full 60-question drill set into The Frontend System Design Interview Playbook ($19): https://bittalk.gumroad.com/l/frontend-interview-playbook

Top comments (0)