Lexicon
⌘K
Explore
GuidesNotes
QuestionsWriteMy LibraryBooks
Sign in

Lexicon

a personal knowledge base

← Back to note

Version history

No earlier versions yet — history builds up each time this note is edited.

Current version1 of 1

Web

API pagination patterns

When a collection is too big to return at once, you paginate. There are two main patterns and choosing wrong causes real pain at scale.

Offset pagination

The classic approach: skip N rows, take the next page.

GET /api/posts?limit=20&offset=40
SELECT * FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 40;

Pros: simple, lets you jump to any page number.

Cons: two real problems.

  • Slow on large tables, because the database still walks past every skipped row.
  • Unstable, if a new row is inserted while the user pages, items shift and they see duplicates or gaps.

Cursor (keyset) pagination

Instead of an offset, remember the last item seen and ask for rows after it.

GET /api/posts?limit=20&after=2026-05-01T10:00:00Z
SELECT * FROM posts
WHERE created_at < '2026-05-01T10:00:00Z'
ORDER BY created_at DESC
LIMIT 20;

Pros: fast at any depth (it uses the index), and stable under inserts.

Cons: no jumping to an arbitrary page, and the cursor column must be unique and ordered. In practice you often use a composite cursor like (created_at, id) to break ties.

A typical response shape

{
  "data": [ { "id": 101 } ],
  "page": {
    "nextCursor": "eyJpZCI6MTAxfQ==",
    "hasMore": true
  }
}

Which to pick

Use offset for small admin tables where page numbers matter. Use cursor pagination for feeds, infinite scroll, and anything that grows without bound.