Backend
Pagination
Splitting a large result set into pages so responses stay small and fast. Offset pagination (skip 40, take 20) is simple and jumpable but drifts when rows are inserted and slows at deep offsets; cursor pagination (rows after this key) is stable and consistently fast but cannot jump to page seven. Choose by how users actually navigate.
Purpose
Pagination exists because unbounded queries do not survive contact with production: a list endpoint that returns everything gets slower and heavier every week until it times out. Paging bounds the cost of one request — for the database, the network and the client — and turns "all the rows" into a sequence of predictable, cacheable chunks.
When to Use It
Offset (LIMIT 20 OFFSET 40) fits admin tables and search results where users jump to arbitrary pages and the data changes slowly. Cursor/keyset (WHERE created_at < $cursor ORDER BY created_at DESC LIMIT 20) fits feeds and infinite scroll: no skipped or repeated items when rows are inserted mid-scroll, and the query cost stays flat no matter how deep the user goes.
Trade-offs
Offset's two failure modes grow with the data: the database still walks past every skipped row (page 5,000 reads 100,000 rows to return 20), and concurrent inserts shift items between pages. Cursors fix both but give up random access and "page N of M" — and an exact total count is its own expensive query, which is why large systems show "many" instead of a number.
Implementation
Key the cursor on an indexed, unique ordering (a timestamp plus ID tie-breaker), encode it as an opaque string so clients cannot construct or misread it, and return it alongside the items (nextCursor). Cap the page size server-side, and make the sort order explicit — an unordered paged query is nondeterministic by definition.