Missing Pagination (Unbounded Result Sets)
Missing pagination returns entire collections at once, causing huge payloads, slow queries, and out-of-memory failures as data grows. Paginate from the start, prefer cursor-based pagination, enforce page-size limits, and offer filtering and sorting.
Missing pagination is exposing a collection endpoint that returns every matching record in one response, with no limit. It works fine with ten rows in development and falls over with a million in production.
Why It Happens
Returning the whole list is the simplest implementation, and early on the data is small. Pagination adds design decisions — page size, cursors versus offsets, stable ordering — that are easy to defer. Internal endpoints "just for us" skip it, then become load-bearing as data grows. Nobody revisits the endpoint until it starts timing out.
Why It Hurts
As the dataset grows, response payloads grow without bound. Queries scan and serialize ever more rows, slowing the database and the API. Memory usage spikes as the server materializes the full result set, risking out-of-memory crashes. Clients struggle to render or transfer huge responses, especially on mobile. The endpoint's performance degrades silently with data volume until it becomes an outage. Offset pagination added late can also be slow and inconsistent under concurrent writes.
Warning Signs
- A list endpoint has no
limit/cursorand returns everything. - Response times grow steadily with table size.
- The service shows memory spikes when listing large collections.
- Clients time out or run out of memory consuming a response.
Better Alternatives
Paginate from the start. Prefer cursor-based (keyset) pagination for large or frequently changing datasets: it is stable under inserts and performs consistently because it seeks by an indexed key rather than counting offsets. Enforce a maximum and a sensible default page size so callers cannot request unbounded results. Provide filtering and sorting so clients fetch only what they need. Return pagination metadata or next-page cursors in a consistent shape.
How to Refactor Out of It
Add pagination to unbounded endpoints, choosing cursor-based for large collections and offset only for small, stable ones. Set a hard server-side cap on page size even if the client asks for more. Ensure the underlying query has an index supporting the sort/cursor column. For backward compatibility on a live API, introduce paginated behavior behind a version or a new parameter, then migrate clients. Add monitoring on payload size and query time so regressions surface early. Bounded responses keep the endpoint fast and stable no matter how the data grows.