The Pagination Trap: Why "It Works" Is Not the Same as "It's Correct"
Most pagination bugs are not in the query that fetches the rows. They live in the gap between "it returned 20 items" and "those 20 items are the right 20 items for this user at this moment." This series argues that the engineering problem in AI Create pagination is not implementation — it is verification.
Key Takeaways
- Pagination correctness is a contract problem, not a query problem. The SQL almost always does what you asked; the failure is that you asked the wrong thing.
- The four recurring failure modes are boundary, drift, race, and recompute. Every "impossible" pagination bug I have seen falls into one of these.
- The fix is not more tests. The fix is an explicit verification contract that defines what "the right 20 items" means at every boundary the system crosses.
- AI Create surfaces make this harder than static feeds because the underlying set is not stable — items are added, removed, reordered, and re-scored between page requests.
- If you can state your verification contract in one sentence, your pagination is probably correct. If you cannot, it is probably broken in a way your tests cannot see.
It is 3:14 a.m. The pager says: "AI Create — user reports missing items in paginated results." The engineer on call opens the dashboard, scrolls the feed, and sees exactly what the user describes — twenty items, then a gap, then twenty more, with the items they would have expected to see in the gap nowhere on the page. The query is simple. The test suite is green. The dashboard shows green. The user is right, and the system is wrong.
I have watched this scene play out in a half-dozen engineering organizations. The first time, I assumed the bug was a missing index or an off-by-one. By the third time, I had stopped assuming. The pattern is too consistent to be coincidence. The pattern is: the query works, the test passes, and the page is wrong.
That gap — between "the query works" and "the page is right" — is what this series is about. Not the SQL, not the cursor encoding, not the offset arithmetic. Those are the easy parts. The hard part is verifying that what you returned is what the user should have seen, in a system where "what the user should have seen" is a moving target.
Here is the claim I will defend across the four chapters: most AI Create pagination is correct in the small and broken in the large, and the breakage is almost always a verification failure, not an implementation failure. The bug is rarely that you fetched the wrong rows. The bug is that you did not define what "the right rows" means at every boundary the request crosses — the boundary between pages, the boundary between users, the boundary between when you fetched the page and when the user looked at it, and the boundary between what the database knows and what your scoring layer believes.
Think of pagination as a relay race. Four runners, each carrying a baton. The first runner is the database, which hands you rows. The second is the scoring layer, which ranks them. The third is the cursor, which encodes your position. The fourth is the renderer, which shows them to the user. In a normal software system, the baton is a single integer or a timestamp, and you can see it. In AI Create pagination, the baton is invisible — it is the user's intent, the scoring model, the freshness of the index, and the consistency of the read. Every handoff is a place where the baton can be dropped, and most engineering effort is spent polishing the runners, not the handoff.
The four failure modes I will name — boundary, drift, race, recompute — are four ways the baton gets dropped. None of them are exotic. None of them require a deep bug. They are the price of crossing a boundary without a contract, and they are universal in any system where the result set is not a fixed snapshot.
Consider how an AI Create surface differs from a static feed. A static feed is paginated over a stable set: articles, products, comments. The set changes slowly. You can take a snapshot, page through it, and the user's mental model of "what is on page 2" stays roughly correct for as long as the snapshot is fresh. An AI Create feed is paginated over a set that is being continuously reshaped: new items generated, old items re-ranked, scores updated as the model sees more data, items removed because they were flagged, items promoted because they were well-received. The set is not a list. The set is a process.
Once you accept that the set is a process, the engineering problem changes shape. You are no longer paginating over data. You are paginating over time, and the cursor you hand back is not a position in a list — it is a claim about the state of the system at the moment you read. Whether that claim is honored at the next request is no longer the database's problem. It is yours.
This is the part of the analysis I keep returning to. I came into pagination assuming the engineering was in the indexing strategy, the cursor format, the keyset arithmetic. After a few production incidents, I came to believe the engineering is in the verification contract — the thing you commit to your caller about what the page represents. The cursor is just a serialization of that contract. The query is just an implementation of that contract. The test suite is just a probe of that contract. If the contract is wrong, everything downstream is wrong in a way that the test suite cannot see, because the test suite is testing the contract's implementation, not the contract itself.
The rest of this series will go through the four strategies, the four failure modes, and the four moves that turn "it works" into "it is correct." I will not give you a pagination library. I will give you a way to think about whether the one you have is the one you think it is.
If your verification contract is "give me the next 20 items," you do not have a contract. You have a hope. And the gap between hope and contract is exactly where every pagination bug I have ever seen was born.
Imagine you are the on-call engineer at 3:14 a.m. You are staring at the dashboard. The query is SELECT ... FROM items WHERE ... ORDER BY score DESC, id DESC LIMIT 20 OFFSET 40. The test suite has 200 cases. The production case is not one of them. The user's mental model of "the items I was just looking at" is not encoded anywhere. You are about to discover that pagination is not a query — it is a conversation with the user across time, and the only thing keeping that conversation honest is the contract you wrote when you were not yet tired.
The next chapter walks the four pagination strategies and the failure mode each one hides in plain sight.
---
References:
- *PostgreSQL Documentation, "LIMIT and OFFSET,"* public PostgreSQL docs.
- *Use the Index, Luke! A Guide to Database Performance for Developers,* Markus Winand (a public, widely-cited reference on SQL pagination performance).
- *Designing Data-Intensive Applications,* Martin Kleppmann (public engineering reference on consistency and read models).
- *Charity Majors, "The Era of Bad Buckets,"* public engineering writing on verification failures in distributed systems.
---
A verification contract is not a test. It is the sentence you would write on a Post-it and stick to the query that explains what the page is allowed to mean.