0
0
GraphQLquery~15 mins

Cursor-based pagination in GraphQL - Deep Dive

Choose your learning style9 modes available
Overview - Cursor-based pagination
What is it?
Cursor-based pagination is a way to split large lists of data into smaller parts, called pages, using a unique marker called a cursor. Instead of counting pages by numbers, it uses these cursors to remember where the last page ended. This method helps fetch data efficiently and smoothly, especially when data changes often or is very large. It is commonly used in APIs like GraphQL to load data bit by bit.
Why it matters
Without cursor-based pagination, loading large data sets can be slow and unreliable, especially if data changes between requests. Traditional page-number pagination can skip or repeat items when data updates. Cursor-based pagination solves this by using a stable position marker, making data loading faster and more consistent. This improves user experience in apps and reduces server load.
Where it fits
Before learning cursor-based pagination, you should understand basic pagination concepts and how APIs fetch data. After this, you can explore advanced GraphQL features like Relay connections and how cursor pagination integrates with filtering and sorting.
Mental Model
Core Idea
Cursor-based pagination uses a unique position marker to fetch the next set of data, ensuring smooth and reliable navigation through large or changing lists.
Think of it like...
Imagine reading a book with a bookmark. Instead of remembering the page number, you keep the bookmark where you stopped. When you come back, you start reading exactly from that spot, no matter if pages were added or removed before it.
┌───────────────┐
│ Data List     │
├───────────────┤
│ Item 1        │
│ Item 2        │
│ Item 3        │ ← Cursor here
│ Item 4        │
│ Item 5        │
└───────────────┘

Fetch next page using cursor pointing at Item 3 to get Item 4 and Item 5.
Build-Up - 6 Steps
1
FoundationUnderstanding basic pagination
🤔
Concept: Learn what pagination is and why we split data into pages.
Pagination means dividing a large list of items into smaller chunks or pages. For example, a website might show 10 products per page instead of all at once. This helps users load and view data faster and easier.
Result
You understand why we don't load all data at once and what a page means in data fetching.
Knowing why we paginate helps you appreciate the need for efficient ways to handle big data.
2
FoundationIntroduction to cursors
🤔
Concept: A cursor is a unique marker that points to a specific item in a list.
Instead of using page numbers, cursor pagination uses a cursor to mark the last item seen. This cursor is often a unique ID or encoded string representing the position in the list.
Result
You can identify what a cursor is and how it marks a position in data.
Understanding cursors is key to grasping how cursor-based pagination tracks progress.
3
IntermediateHow cursor pagination works in GraphQL
🤔Before reading on: do you think cursor pagination uses page numbers or unique markers? Commit to your answer.
Concept: Cursor pagination fetches data after a given cursor, not by page number.
In GraphQL, cursor pagination uses arguments like 'after' with a cursor value to fetch the next set of items. The server returns edges with nodes and cursors, allowing clients to request the next page starting after the last cursor.
Result
You see how queries use 'after' cursors and how responses include cursors for the next page.
Knowing that cursor pagination relies on unique markers rather than page numbers helps avoid common bugs with changing data.
4
IntermediateBenefits over offset pagination
🤔Before reading on: do you think offset pagination or cursor pagination handles data changes better? Commit to your answer.
Concept: Cursor pagination avoids problems caused by data changes during paging.
Offset pagination uses page numbers and skips, which can cause missing or repeated items if data changes. Cursor pagination uses stable cursors, so even if data is added or removed, the next page starts exactly where the last ended.
Result
You understand why cursor pagination is more reliable for dynamic data.
Recognizing the limitations of offset pagination explains why cursor pagination is preferred in many real-world apps.
5
AdvancedImplementing Relay-style connections
🤔Before reading on: do you think Relay connections include total counts or just cursors? Commit to your answer.
Concept: Relay connections standardize cursor pagination with edges, nodes, and pageInfo.
Relay-style pagination returns a connection object with edges (each with a node and cursor) and pageInfo (hasNextPage, endCursor). This structure helps clients navigate pages and know if more data exists.
Result
You can read and write GraphQL queries using Relay connections for cursor pagination.
Understanding Relay connections prepares you for advanced GraphQL pagination patterns used in production.
6
ExpertHandling cursor encoding and security
🤔Before reading on: do you think cursors are plain IDs or encoded strings? Commit to your answer.
Concept: Cursors are often encoded to hide internal details and prevent tampering.
To avoid exposing database IDs or internal structure, cursors are encoded (e.g., base64). This encoding also helps keep cursors opaque and secure. Servers decode cursors to find the correct position when fetching data.
Result
You understand why cursors are encoded and how to implement encoding/decoding.
Knowing cursor encoding prevents security leaks and ensures stable pagination even if internal data changes.
Under the Hood
Cursor-based pagination works by storing a unique identifier (cursor) for each item, often based on a stable column like a timestamp or ID. When a client requests the next page, it sends the last cursor received. The server decodes this cursor to find the exact position in the data and fetches the next set of items after it. This avoids counting or skipping rows, which can be slow or inconsistent with changing data.
Why designed this way?
Traditional offset pagination was simple but caused problems when data changed between requests, leading to missing or duplicated items. Cursor pagination was designed to provide a stable, efficient way to page through data without relying on counting or offsets. Encoding cursors hides internal details and prevents clients from manipulating queries incorrectly.
Client Request ──▶ Server
  │                     │
  │  sends cursor       │
  │────────────────────▶│
  │                     │
  │          fetch data after cursor
  │                     │
  │◀────────────────────│
  │  returns data + new cursor
  ▼                     ▼
Myth Busters - 4 Common Misconceptions
Quick: Does cursor pagination always return the same number of items per page? Commit yes or no.
Common Belief:Cursor pagination always returns a fixed number of items per page, just like page numbers.
Tap to reveal reality
Reality:Cursor pagination returns up to the requested number of items, but the actual count can vary due to filtering or data changes.
Why it matters:Assuming fixed page sizes can cause UI bugs or incorrect assumptions about data completeness.
Quick: Is the cursor just a simple row number? Commit yes or no.
Common Belief:A cursor is just a row number or offset in the list.
Tap to reveal reality
Reality:A cursor is a unique identifier or encoded value representing a position, not a simple number.
Why it matters:Treating cursors as offsets can break pagination when data changes or when IDs are not sequential.
Quick: Can you use cursor pagination without encoding cursors? Commit yes or no.
Common Belief:Cursors can be plain IDs without encoding, and it won't cause issues.
Tap to reveal reality
Reality:Using plain IDs exposes internal data structure and can allow clients to manipulate queries, causing security or stability problems.
Why it matters:Not encoding cursors risks data leaks and unstable pagination behavior.
Quick: Does cursor pagination always perform better than offset pagination? Commit yes or no.
Common Belief:Cursor pagination is always faster and better than offset pagination.
Tap to reveal reality
Reality:Cursor pagination is better for large or changing data, but for small static datasets, offset pagination can be simpler and sufficient.
Why it matters:Choosing cursor pagination unnecessarily can add complexity without benefits.
Expert Zone
1
Cursors must be based on stable, unique columns to avoid skipping or repeating items when data changes.
2
Combining cursor pagination with sorting requires careful cursor design to reflect the sort order.
3
Relay-style connections include pageInfo fields that help clients understand pagination state beyond just cursors.
When NOT to use
Avoid cursor pagination for small datasets or when users need direct access to arbitrary pages by number. Use offset pagination or keyset pagination alternatives when sorting is complex or cursors cannot be stable.
Production Patterns
In production GraphQL APIs, cursor pagination is often implemented with Relay connections, encoding cursors as base64 strings of unique IDs or timestamps. Servers optimize queries using indexes on cursor columns. Clients use pageInfo to show loading spinners or 'load more' buttons.
Connections
Keyset pagination
Cursor-based pagination is a form of keyset pagination using unique keys as cursors.
Understanding keyset pagination helps grasp why cursor pagination is efficient and stable compared to offset pagination.
Stateful bookmarks in user interfaces
Both use a saved position to resume progress later.
Knowing how bookmarks work in reading apps clarifies how cursors mark positions in data streams.
Linked lists in computer science
Cursor pagination resembles traversing a linked list node by node using pointers.
Seeing cursors as pointers helps understand why jumping directly to a page number is harder than moving stepwise.
Common Pitfalls
#1Using page numbers with cursor pagination arguments.
Wrong approach:query { items(page: 2, first: 10) { edges { node } } }
Correct approach:query { items(after: "cursor123", first: 10) { edges { node cursor } pageInfo { hasNextPage endCursor } } }
Root cause:Confusing cursor pagination with offset pagination and trying to use page numbers instead of cursors.
#2Exposing raw database IDs as cursors.
Wrong approach:cursor: "123"
Correct approach:cursor: "YXJ0aWNsZV8xMjM=" // base64 encoded string
Root cause:Not encoding cursors leads to security risks and unstable pagination.
#3Using non-unique or unstable fields as cursors.
Wrong approach:cursor based on 'created_at' timestamp without uniqueness
Correct approach:cursor based on 'created_at' + unique ID combined and encoded
Root cause:Using non-unique cursors causes duplicate or missing items when data changes.
Key Takeaways
Cursor-based pagination uses unique position markers called cursors to fetch data pages reliably.
It solves problems of missing or repeated items common in offset pagination when data changes.
Cursors are often encoded to hide internal details and ensure security.
Relay-style connections standardize cursor pagination in GraphQL with edges and pageInfo.
Choosing the right pagination method depends on data size, stability, and user needs.