Limited Offer

30% OFF Lifetime Access ($139) with code SYSTEM30

TOPIC #127Intermediate 9 min read

API Pagination: Offset vs Cursor-Based

šŸ’”
Core Architecture Summary

Scale large dataset navigation: Offset/Limit SQL scan penalties, Page Drift anomalies, and high-performance Keyset Cursor pagination.

Key Glossary Concepts in this TopicAll Glossary Terms

Offset Pagination I/O Scan & Drift vs Cursor Index Seek šŸ“œ

Comparing database disk scan overhead, pagination stability, and B-Tree index traversal.

Offset Pagination I/O Scan & Drift vs Cursor Index Seek šŸ“œ
100%
Rendering visual architecture flowchart...

01.1. Offset-Based Pagination: Mechanics & The O(N) Scan Penalty

Offset pagination (also known as page-number pagination) is the most intuitive pagination scheme. The client supplies a page number and page size:

http
GET /v1/posts?page=50000&limit=20

This translates to standard SQL:

sql
SELECT * FROM posts 
ORDER BY created_at DESC 
LIMIT 20 OFFSET 1000000;

The Database Performance Disaster:

Relational database storage engines (PostgreSQL heap files, MySQL InnoDB clustered indexes) cannot jump directly to row number 1,000,000 in O(1) time.

To fulfill OFFSET 1000000:

  1. The database query planner must scan and count through 1,000,000 index tuples or table rows.
  2. It sorts and tracks all 1,000,000 rows in memory/temp disk buffers.
  3. It discards the first 1,000,000 rows.
  4. It finally extracts and returns the subsequent 20 rows.

Disk I/O & Latency Degradation:

  • Page 1 (OFFSET 0): ā‰ˆ 1.5ms
  • Page 100 (OFFSET 2,000): ā‰ˆ 8ms
  • Page 5,000 (OFFSET 100,000): ā‰ˆ 140ms
  • Page 50,000 (OFFSET 1,000,000): ā‰ˆ 1,800ms - 12,000ms (Often causing gateway timeouts / 504 Gateway Timeout).

02.2. The Page Drift Anomaly (Duplicates & Skipped Records)

In any real-time system with concurrent writes (social feeds, chat messages, financial ledgers), offset pagination causes severe data anomalies:

1. Duplicate Record Drift (On Insert):

  1. User requests Page 1 (LIMIT 5 OFFSET 0). The server returns items: [Post A, Post B, Post C, Post D, Post E].
  2. While the user is scrolling, another user publishes Post X at position 1.
  3. User scrolls and requests Page 2 (LIMIT 5 OFFSET 5).
  4. The database offsets 5 rows from the new start: [Post X, Post A, Post B, Post C, Post D, **Post E**, ...].
  5. Post E is returned a second time! The client application renders duplicate content.

2. Skipped Record Drift (On Delete):

If an item on Page 1 is deleted before Page 2 is loaded, all subsequent records shift up by one position, causing the first item of Page 2 to be skipped entirely.

03.3. Cursor-Based Pagination (Keyset / Seek Pagination)

Cursor-based pagination (also called Keyset pagination) eliminates both the O(N) scan penalty and page drift by indexing off the last record retrieved:

The Cursor Concept:

Instead of asking for "Page 50", the client asks for: "The 20 items created immediately before Item X".

http
GET /v1/posts?cursor=eyJjcmVhdGVkX2F0IjoxNzE0MDAwLCJpZCI6MTA0Mn0&limit=20

Compound Index & SQL Query:

Assuming a compound B-Tree index on (created_at DESC, id DESC):

sql
SELECT id, title, created_at 
FROM posts
WHERE (created_at, id) < ('2026-04-12 14:30:00', 1042)
ORDER BY created_at DESC, id DESC
LIMIT 20;

Why Keyset Pagination is O(log N) / O(1):

The database engine performs a direct B-Tree index seek to find the exact point ('2026-04-12 14:30:00', 1042) in O(log N) time, reads the next 20 contiguous leaf blocks, and stops. It never reads or counts earlier discarded rows.

Query latency remains ā‰ˆ 1-3ms whether fetching the 1st page or the 10,000,000th page!

04.4. Production Cursor API Response Structure

To prevent clients from parsing or depending on database column names, encode cursor pointers as opaque Base64 strings:

json
{
  "data": [
    { "id": "post_1041", "title": "System Design Mastery", "created_at": "2026-04-12T14:29:50Z" },
    { "id": "post_1040", "title": "Understanding LSM Trees", "created_at": "2026-04-12T14:28:10Z" }
  ],
  "pagination": {
    "limit": 2,
    "has_more": true,
    "next_cursor": "ZXlKalltbGhkR1ZrWDJGMElqb3hOekUwTURBd0xDSnBaQ0k2TVRBME1Dd3Z",
    "prev_cursor": null
  }
}

Comparison Matrix:

FeatureOffset PaginationCursor / Keyset Pagination
Query ComplexityO(N) Disk scanO(log N) B-Tree index seek
Deep Page LatencyDegrades linearly (seconds)Constant (1-3ms)
Real-Time Data StabilitySevere Page Drift (Duplicates/Skips)100% Stable
Direct Page JumpingYes (page=42)No (Sequential next/prev only)
Total Record CountTrivial (COUNT(*))Expensive (Omitted in high-scale feeds)
Primary Use CaseAdmin panels, desktop tablesMobile feeds, infinite scroll, log streams

āš–ļøArchitectural Trade-offs & Production Realities

Architectural Advantages

  • Constant-time O(1) query execution even on billions of records
  • Completely immune to page drift, duplicate items, and missed records during live inserts/deletes
  • Ideal for infinite scrolling on mobile applications and real-time social feeds

Trade-offs & Constraints

  • Cannot jump directly to an arbitrary page (e.g., "Jump to Page 47")
  • Requires a strict deterministic compound index on unique attributes (e.g., `created_at, id`)
  • Cannot easily display the total number of pages or total count without a costly separate `COUNT(*)` query
Production Implementation in Big Tech
Twitter (X) & Slack• Timeline Feeds & Message History

Twitter uses `since_id` and `max_id` Snowflake cursor parameters for home timeline pagination. Slack utilizes opaque `next_cursor` tokens across conversation history APIs, allowing clients to traverse billions of channel messages without table scans or duplicate message renders.

šŸŽÆ Staff+ Engineering Takeaways

  • Offset pagination (`OFFSET N`) forces the database to read and discard $N$ rows, degrading to unacceptable multi-second latencies at scale.
  • Offset pagination causes duplicate and skipped items when records are inserted or deleted during user scrolling.
  • Cursor pagination uses compound indexed values (`WHERE id < cursor`) for constant $O(1)$ B-Tree seeks.
  • Make cursors opaque Base64 strings to decouple client SDKs from internal database column structures.

Topic Knowledge Assessment 🧠

Step through 3 scenario questions to test your staff-level grasp.

Question 1 of 30 answered
#1

Why does database performance degrade linearly in Offset pagination when accessing deep pages (e.g. OFFSET 2000000 LIMIT 20)?

Rate This Architecture Chapter4.9 / 5.0 (38 ratings)

How clear and staff-actionable was this system breakdown?