11 min read

A Friendly Guide to Pagination

Ever found yourself scrolling endlessly through a massive list of search results, social media posts, or online store products? It can feel like diving into an ocean without a map! That feeling of being overwhelmed by too much information at once is exactly what pagination is designed to solve.

Think of pagination like flipping through the pages of a book or magazine. Instead of getting the entire story dumped on you in one go, you get it in nice, digestible chunks – pages! In the digital world, pagination breaks down huge datasets into smaller, more manageable segments or "pages". This makes it easier for you (the user) to browse, and it's also much kinder to the computer systems fetching the data, preventing them from getting overloaded.

Why Does This Matter? (Especially for Backend Folks!)

From a user's perspective, good pagination means a smooth, fast experience. But let's put on our backend developer hats for a moment. Imagine your application needs to show a list of a million user comments. If you try to fetch all million comments from the database in one go, you're asking for trouble! The database query itself will likely take a long time, consuming significant server memory and CPU. Sending that massive chunk of data over the network will be slow, potentially timing out or crashing the user's browser or app. Without pagination, applications handling large datasets become sluggish, unresponsive, and expensive to run. It's not just about user convenience; it's fundamental for building scalable and performant systems. Good pagination keeps database queries efficient, reduces server load, minimizes network traffic, and ultimately keeps the application snappy and reliable.

In this little guide, we're going to explore the most common ways developers handle this page-turning trick. We'll look at the classic offset-based pagination (you've probably seen this as page numbers: 1, 2, 3...), the increasingly popular and robust cursor-based pagination (often powering those "load more" buttons and infinite scrolls), and maybe even touch on a few other clever techniques. Don't worry, we'll keep it friendly and stick to simple examples. Let's dive in!


Method 1: Offset-Based Pagination

Alright, let's start with the method most of us have seen countless times: offset-based pagination. This is the classic approach that gives you those familiar page numbers (1, 2, 3...) at the bottom of search results or product listings.

How it Works: The Simple Skip-and-Take

Imagine you have a huge list of items, say, 100 blog posts. You decide you only want to show 10 posts per page. Offset pagination works with two main ingredients:

  1. LIMIT (or Page Size): This tells the system how many items you want on each page. In our example, the LIMIT is 10.
  2. OFFSET (or Skip): This tells the system how many items to skip before starting to collect the items for the current page. If you want page 1, you skip 0 items (OFFSET 0). If you want page 2, you need to skip the first 10 items (OFFSET 10). For page 3, you skip the first 20 items (OFFSET 20), and so on.

The formula is usually: OFFSET = (Page Number - 1) * LIMIT

Simple Query Example (SQL Style)

Let's say we want to fetch page 3 of our products list, showing 10 products per page. The database query might look something like this:

-- Fetching Page 3, with 10 items per page
SELECT product_name, price
FROM products
ORDER BY product_id -- Important to have a consistent order!
LIMIT 10 -- We want 10 items
OFFSET 20; -- Skip the first (3 - 1) * 10 = 20 items

See? The database first sorts the products (usually by ID or creation date to keep things consistent), then it skips the first 20 products, and finally, it grabs the next 10. Voila! Page 3 is served.

This method feels very natural because it directly maps to the concept of page numbers we're all used to. But, as we'll see later (and as our backend developer friends know all too well), this simplicity can hide significant performance problems, especially when dealing with really big lists or data that changes frequently.

Method 2: Cursor-Based Pagination

Now, let's talk about a different approach that's become super popular, especially with social media feeds and apps where new content is always popping up: cursor-based pagination (sometimes called keyset pagination or seek method).

Instead of thinking in rigid page numbers like "give me page 5", cursor-based pagination thinks more like "give me the next batch of items after the last one I saw".

How it Works: Pointing the Way

Imagine you're reading a news feed. With cursor pagination, when you load the first 10 items, the system also gives you a special marker, a "cursor". This cursor is usually a unique and sortable piece of information tied to the last item you just received.

What Can Be a Cursor? Flexibility is Key!

The beauty here is that the cursor can be almost any value that is unique (or unique enough when combined with another field) and can be reliably sorted by the database. Common choices include:

  • Database IDs: Simple auto-incrementing integers are often used.
  • Timestamps: Like created_at or updated_at. These can be in various formats – standard SQL timestamps (YYYY-MM-DD HH:MM:SS), Unix epoch timestamps (seconds since 1970), ISO 8601 strings, etc., as long as the database can sort them correctly.
  • UUIDs (Specific Versions): While traditional UUIDs (like v4) are not sortable, newer versions like UUIDv7 are designed to be time-ordered and make excellent cursors because they are both unique and sortable!
  • Other Sortable Fields: Depending on the data, it could even be something like a unique score or a sequential event ID.

The crucial part is that the chosen field (or combination of fields) allows the database to efficiently find the "next" set of records after the cursor's value.

When you want the next 10 items (maybe you hit a "Load More" button), you send that cursor back to the system. The system then uses the cursor to find exactly where you left off and fetches the next 10 items that come after (or before, depending on sort direction) that specific point.

  1. The Cursor: A pointer (like an ID, timestamp, UUIDv7) to a specific item in the sorted list.
  2. LIMIT: Still used to define how many items to fetch in the next batch.
  3. The Query: Uses the cursor in a WHERE clause to find items greater than (or less than) the cursor value.

Simple Query Example (SQL Style - Using Timestamp)

Let's say we're fetching blog posts ordered by creation time (newest first). We just loaded a batch, and the oldest post in that batch (the last one) had the timestamp 2024-05-31 10:00:00.123456. That timestamp is our cursor for the next request.

-- Fetching the next 10 posts older than the cursor
SELECT post_title, author, created_at
FROM posts
WHERE created_at < '2024-05-31 10:00:00.123456' -- Our cursor value
ORDER BY created_at DESC -- Keep the same order (newest first)
LIMIT 10; -- Get the next 10

Sometimes, especially if cursors aren't perfectly unique (like timestamps that might collide), you might use a combination (e.g., WHERE (created_at < 'timestamp' OR (created_at = 'timestamp' AND post_id < last_post_id))) to handle tie-breaking, using a truly unique field like post_id as the secondary sort key.

This method avoids the need for the database to count and skip potentially millions of rows (like offset does). It just jumps straight to the relevant point using the cursor, making it much faster for large datasets and more reliable when new items are being added constantly – a huge win from a backend performance perspective!

Bonus Method: Token-Based Pagination

While offset and cursor/keyset are the big players, you might sometimes encounter token-based pagination. It shares similarities with the cursor method but often adds a layer of abstraction or security.

How it Works: The Opaque Pointer

Imagine instead of getting a clear cursor like a timestamp or an ID, the system gives you an encrypted or encoded string – a "token" – after you fetch a page of data. This token essentially contains the information needed to get the next page (it might secretly hold the last ID, timestamp, or even offset information), but it's not human-readable.

When you want the next page, you just send this opaque token back to the server. The server decodes or decrypts the token, figures out where you left off, fetches the next batch of items, and gives you a new token for the following page.

  1. The Token: An often opaque string representing the state needed to fetch the next page.
  2. LIMIT: Still used to specify the number of items per page.
  3. The Request: The client sends the token received from the previous response to get the next chunk.

Simple Example (API Style)

Token-based pagination is very common in APIs. Here’s a conceptual example of how the requests and responses might look:

Initial Request (Get first page):
GET /api/items?limit=10

Response (First page data + next page token):

{
  "data": [ ... 10 items ... ],
  "pagination": {
    "next_page_token": "aBcDeFgHiJkLmNoPqRsT12345"
  }
}

Request for Next Page (Using the token):
GET /api/items?limit=10&page_token=aBcDeFgHiJkLmNoPqRsT12345

Response (Second page data + another token):

{
  "data": [ ... next 10 items ... ],
  "pagination": {
    "next_page_token": "zYxWvUtSrQpOnMlKjIhGfEdCb67890"
  }
}

And so on. The client doesn't need to know what the token means, only that it needs to send it back to get the next set of results. This gives the backend system flexibility in how it implements pagination internally (it could even be using offset or cursor behind the scenes!).

Behind the Scenes: A Conceptual Backend Query

So, what might the backend do with that token aBcDeFgHiJkLmNoPqRsT12345? Let's imagine this token, when decoded or decrypted by the server, simply contains the ID of the last item from the previous page, say item_id = 50.

The backend logic receiving the request GET /api/items?limit=10&page_token=aBcDeFgHiJkLmNoPqRsT12345 might then perform these steps:

  1. Decode aBcDeFgHiJkLmNoPqRsT12345 to get last_seen_id = 50.
  2. Execute a query similar to cursor-based pagination:
-- Conceptual Backend Query for Token-Based Pagination
-- (Assuming token decoded to last_seen_id = 50)
SELECT id, name, description
FROM items
WHERE id > 50 -- Using the decoded value from the token
ORDER BY id ASC
LIMIT 10; -- The limit from the request
  1. Fetch the results (items 51-60).
  2. Generate a new token based on the last item fetched in this batch (item 60), perhaps encoding item_id = 60 into the new token zYxWvUtSrQpOnMlKjIhGfEdCb67890.
  3. Return the data and the new token to the client.

This is just one possible way a backend could implement it. The token could encode an offset, a timestamp, or more complex state. The key takeaway is that the token hides this internal implementation detail from the client, providing flexibility for the backend.


Choosing Your Path: Pros and Cons of Each Method

Okay, we've seen how these different pagination methods work. But the big question is: which one should you use? As a backend developer, choosing the right strategy is crucial for performance and scalability. Let's break down the good and the not-so-good parts of each approach, keeping that backend perspective in mind.

1. Offset-Based Pagination (The Familiar Friend)

  • Pros:
    • Super Intuitive (for Users): Everyone understands page numbers (1, 2, 3...). It feels natural for users making requests.
    • Easy Start: Getting basic offset pagination working is often quite straightforward, especially with simple datasets.
    • Jump Around: Users can easily jump directly to any page number they want (e.g., go straight to page 10 or the last page).
  • Cons:
    • Performance Nightmare at Scale: This is the big one for backend devs. Imagine asking for page 10,000 of a million items. The database might have to scan and effectively discard 99,990 rows just to find where page 10,000 starts! This OFFSET operation becomes incredibly slow and resource-intensive (CPU, memory, I/O) on large tables, potentially crippling application performance.
    • Data Drift Danger: What happens if someone adds or deletes an item while a user is clicking from page 2 to page 3? Because offset relies on skipping a fixed number from the start of the (potentially changing) dataset, users might see duplicate items or miss items entirely. This inconsistency is a major headache for frequently updated lists.

2. Cursor-Based Pagination (The Backend Performance Ally)

  • Pros:
    • Performance Champ: By using a cursor (like an ID, timestamp, or UUIDv7) and a WHERE clause (e.g., WHERE created_at < cursor_value), the database can often use indexes to jump directly to the starting point of the next batch. This avoids the costly full-table scan or large offset count, making it significantly faster and more scalable, especially on huge datasets. This is a massive win for backend performance and database health.
    • Stable and Reliable: Since it fetches items relative to the last item seen, cursor pagination handles newly added or deleted items much more gracefully. You're far less likely to serve duplicates or miss items, leading to a more consistent user experience and fewer data integrity headaches.
    • Infinite Scroll Ready: It's the perfect fit for "load more" buttons and infinite scrolling interfaces where sequential access is natural.
    • Flexible Cursors: You can use various sortable data types as cursors (database IDs, different timestamp formats, sortable UUIDs like v7), offering flexibility based on your data model.
  • Cons:
    • Slightly More Complex Logic: Setting up cursor logic (especially handling tie-breakers if the cursor column isn't strictly unique, e.g., using (timestamp, id)) can require a bit more thought than basic offset.
    • No Arbitrary Page Jumping: You generally can't easily jump to an arbitrary "page 20". Users have to navigate sequentially (get page 1, then 2, then 3...). This might be a limitation for UIs that absolutely require direct page access.
    • Requires Good Cursors: You need a reliable, unique (or unique in combination), and indexed sortable column (or columns) for the cursor to be efficient.

3. Token-Based Pagination (The Flexible Cloak)

  • Pros:
    • Backend Implementation Freedom: The server decides what the token means. It could be hiding cursor logic, offset logic, or something else entirely. This gives the backend team flexibility to optimize or change the internal strategy without breaking client applications.
    • Potentially Stateless: A well-designed token might contain all the necessary state (like the last ID and the sort order), reducing the need for the server to maintain session state for pagination.
    • Can Add Security: Tokens can be encrypted or signed, adding a layer of security and preventing users from easily guessing or manipulating pagination parameters.
  • Cons:
    • Opaque Box (for Clients): For the client (the app or website making the request), the token is often a mystery. This can make debugging trickier if pagination isn't working as expected.
    • Still No Page Jumping (Usually): Like cursor pagination, most token implementations don't allow easily jumping to arbitrary pages.
    • Implementation Complexity: Implementing a robust and secure token system adds its own layer of complexity on the backend.

So, the choice often comes down to your data size, how often it changes, the performance requirements of your backend systems, and the kind of user experience you want to provide (page numbers vs. load more). For applications expecting significant scale or frequent data changes, cursor-based pagination is often the strongly preferred method from a backend perspective.


Conclusion

So there you have it – a whirlwind tour of the world of pagination! From the simple page numbers of offset pagination to the slick, efficient scrolling powered by cursors and tokens, each method has its place.

Choosing the right approach depends heavily on your specific needs:

  • Got a small dataset that doesn't change much, and users love page numbers? Offset might be perfectly fine.
  • Dealing with massive amounts of data, a constantly updating feed, or aiming for top performance? Cursor/Keyset is likely your best bet.
  • Building an API and want flexibility or added security? Token-based pagination offers a robust alternative.

Understanding these techniques helps whether you're building the next big app or just curious about how websites manage all that data without breaking a sweat. The key is to weigh the pros and cons against your project's goals and constraints.

Hopefully, this friendly guide has made the concept of pagination a little less daunting. Now go forth and navigate those datasets with confidence!