How to Iterate Paginated Results in SDKs: Examples

How to iterate paginated results in SDKs examples with automatic and manual loops in Python, Java, TypeScript and Go. Learn page and cursor patterns.

Jump to section

Jump to section

Jump to section

When you're building an SDK for a paginated API endpoint, you face a fundamental choice: should users iterate through results automatically with a simple for loop, or should they manually control each page request? The answer isn't just about developer preference—it's about understanding when convenience trumps control and how to implement both patterns effectively.

Most API companies get pagination wrong in their SDKs, either forcing users into clunky manual page handling or providing auto-iteration that breaks down under real-world conditions like network failures or memory constraints. This guide covers the core pagination patterns your API likely uses, how to implement both automatic and manual iteration across different programming languages, and the error handling strategies that separate production-ready SDKs from basic API wrappers.

What are the different pagination patterns in APIs?

SDKs iterate through paginated API results using two primary methods: an automatic iterator, like a for...of loop, which fetches new pages for you behind the scenes, and a manual page-by-page approach that gives you explicit control over each request. To implement these, an SDK must first understand the API's pagination pattern, which is how the API breaks large datasets into smaller, manageable chunks called "pages".

The most common patterns are offset, cursor, and token-based pagination. A well-designed SDK abstracts these details away, providing a consistent and ergonomic way to work with lists of data, no matter the underlying mechanism, because your API isn't finished until the SDK ships.

Offset-limit pagination

This is the simplest pattern, like asking for pages in a book. You specify a limit (how many items per page) and an offset (how many items to skip). To get the second page of 20 items, you'd set limit=20 and offset=20.

  • Pro: It's easy to implement and allows jumping to any page directly.

  • Con: It can be inefficient for large datasets and unstable if items are added or removed while you're paginating, causing you to see duplicate or skipped items.

Cursor pagination

Cursor-based pagination uses a pointer, or cursor, that marks your position in the dataset. Each API response includes a cursor pointing to the next set of items. You pass this cursor in your next request to get the following page.

  • Pro: It's highly performant and stable, even when new data is added, making it ideal for real-time feeds.

  • Con: The cursor is typically an opaque string, meaning you can't jump to a specific page; you can only move forward or backward from your current position.

Page token pagination

This pattern is similar to cursor pagination but often uses a nextPageToken provided in the response. You include this token in your subsequent request to retrieve the next page. This approach is common in many Google APIs.

  • Pro: It's stateless from the server's perspective and performs well.

  • Con: Like cursors, it generally doesn't allow you to jump to arbitrary pages.

Page number pagination

This is a variation of offset pagination where you request a specific page number directly, along with a page_size. The server calculates the offset for you (offset = (page - 1) * page_size). It shares the same pros and cons as the offset-limit pattern.

How do SDKs handle pagination iteration?

Modern SDKs provide two main ways to consume paginated endpoints, giving you a choice between convenience and control.

Automatic iteration pattern

The most ergonomic approach is the auto-paginating iterator. This lets you use a standard for loop in your language of choice, and the SDK handles fetching subsequent pages automatically in the background as you iterate. You can process an entire dataset without ever thinking about pages, cursors, or tokens.

This pattern is incredibly powerful for data processing tasks where you need to operate on every item in a collection.

Manual page control pattern

Sometimes you need more control. For example, you might be building a UI with a "Load More" button or need to inspect the headers of each page's response. For these cases, SDKs provide a manual way to fetch one page at a time.

You'll typically get a page object back from the initial request, which contains the list of items for that page and a method like hasNextPage() and getNextPage(). This gives you full control over when and how the next page is fetched.

How does pagination work across SDK languages?

A hallmark of a great SDK is that it feels natural in its target language. While the underlying API call is the same, the way you iterate through paginated results should be idiomatic.

Here’s how you might list all users using a Stainless-generated SDK with both automatic and manual patterns across different languages.

TypeScript example (Stainless-based)

import { MyStainlessClient } from 'myorg-sdk';
// Typically, a generated TypeScript package might be scoped, e.g. '@myorg/myapi'.

(async () => {
  const client = new MyStainlessClient({
    authToken: process.env.MY_API_KEY || 'my-api-key',
  });

  // Automatic iteration
  for await (const user of client.users.list()) {
    console.log(user.name);
  }

  // Manual iteration
  let page = await client.users.list();
  for (const user of page.data) {
    console.log(user.name);
  }
  while (page.hasNextPage()) {
    page = await page.getNextPage();
    for (const user of page.data) {
      console.log(user.name);
    }
  }
})();

Python example (Stainless-based)

from myorg_sdk import MyStainlessClient

client = MyStainlessClient(
    auth_token="my-api-key",
)

# Automatic iteration
for user in client.users.list():
    print(user.name)

# Manual iteration
page = client.users.list()
for user in page.items:
    print(user.name)

while page.has_next_page:
    page = page.get_next_page()
    for user in page.items:
        print(user.name)

Java example (Stainless-based)

import com.myorg.sdk.MyStainlessClient;
import com.myorg.sdk.models.User;
import com.myorg.sdk.pagination.UserListPaginator;

public class Example {
    public static void main(String[] args) {
        MyStainlessClient client = MyStainlessClient.builder()
            .authToken("my-api-key")
            .build();

        // Automatic iteration
        for (User user : client.users().list().autoPager()) {
            System.out.println(user.getName());
        }

        // Manual iteration
        UserListPaginator page = client.users().list();
        for (User user : page.getData()) {
            System.out.println(user.getName());
        }

        while (page.hasNextPage()) {
            page = page.getNextPage();
            for (User user : page.getData()) {
                System.out.println(user.getName());
            }
        }
    }
}

Go example (Stainless-based)

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/myorg/myorg-go"
)

func main() {
    client, err := myorg.NewMyStainlessClient("my-api-key")
    if err != nil {
        log.Fatal(err)
    }

    // Automatic iteration
    iter := client.Users.List(context.Background(), &myorg.UserListParams{
        Limit: myorg.Int64(10),
    })
    for iter.Next() {
        user := iter.Current()
        fmt.Println(user.Name)
    }
    if err := iter.Err(); err != nil {
        log.Fatal(err)
    }

    // Manual iteration
    page, err := client.Users.ListPage(context.Background(), &myorg.UserListParams{
        Limit: myorg.Int64(10),
    })
    if err != nil {
        log.Fatal(err)
    }

    for _, user := range page.Data {
        fmt.Println(user.Name)
    }
    if page.HasNextPage() {
        nextPage, err := page.GetNextPage()
        if err != nil {
            log.Fatal(err)
        }
        for _, user := range nextPage.Data {
            fmt.Println(user.Name

When should you use manual vs automatic pagination?

Choosing between automatic and manual pagination depends entirely on your use case.

Use Case

Recommended Pattern

Why?

Data exports or reports

Automatic

You need to process every item, and convenience is key.

Background processing jobs

Automatic

Simplifies the code and reduces boilerplate.

UI with "infinite scroll"

Manual

You need to fetch and append one page at a time based on user action.

UI with "Load More" buttons

Manual

You need explicit control over when the next page is requested.

Needing page-level metadata

Manual

You need access to headers or other data returned with each page response.

Memory-constrained environments

Manual

You can process and discard each page's data before fetching the next.

How do you handle errors in paginated requests?

A paginated request can fail midway through, leaving you with partial data. A production-grade SDK should help you handle this gracefully.

Transient error strategy

Transient errors are temporary issues like network glitches, server timeouts (408), or rate limit exceptions (429).

  • Automatic Retries: A robust SDK will automatically retry these requests with exponential backoff. It should also respect Retry-After headers sent by the API.

  • Idempotency: For POST requests that might be retried, the SDK should send an Idempotency-Key header to prevent creating duplicate resources.

Permanent error strategy

Permanent errors are issues that won't be resolved by a retry, such as an invalid cursor, a permissions error (403), or a resource not being found (404). In these cases, the SDK's iterator should stop and surface the error to your code immediately so you can handle it. The loop should terminate gracefully, allowing you to work with the data you've already received.

How Stainless SDKs simplify pagination

At Stainless, we believe API providers should be able to offer powerful pagination without writing any boilerplate. Our platform automates this entire process.

Automatic pattern detection

You define your API's pagination strategy once in a simple configuration file, similar to creating OpenAPI specs to describe your API structure.

# stainless.yml
pagination:
  - name: my_cursor_page
    type: cursor
    request:
      after_cursor:
        type: string
        nullable: true
    response:
      next_cursor:
        type: string
        nullable: true
      items:
        type: array
        items: {}
        x-stainless-pagination-property:
          purpose

Generated iterator ergonomics

From that simple config, the Stainless SDK generator creates fully-typed, idiomatic iterators and page objects for every language you support.

Performance and retry integration

Because pagination is built into the core of our generated SDKs, it works seamlessly with other essential features. All paginated requests automatically benefit from our built-in retry logic, timeout handling, and efficient memory usage, giving your users a reliable and robust experience out of the box.

Ready to offer your users a first-class pagination experience? Get started for free.

Frequently asked questions about SDK pagination

What is the key difference between cursor and offset pagination?

The key difference is stability. Cursor pagination is stable even when items are added or removed, whereas offset pagination can lead to skipped or repeated items in a dynamic dataset.

Which pagination method works best for real-time data?

Cursor pagination is almost always the best choice for real-time data, like social media feeds or activity logs, because it provides a stable reference point in a constantly changing list.

How can I test pagination logic in my SDK?

You can test pagination by mocking API responses for multiple pages and asserting that your code correctly iterates through all items, handles the end of the list, and manages errors gracefully, especially when using branches to validate changes before deployment.

Can one SDK support multiple pagination styles?

Yes, a well-architected SDK can support different pagination styles for different endpoints. Our platform allows you to define each endpoint's pagination scheme, and the generated SDK will handle it transparently, while still supporting custom code when needed for specialized pagination logic.

How do GraphQL connections compare to REST pagination?

GraphQL's connection model is a standardized form of cursor-based pagination, using concepts like edges, nodes, and pageInfo to navigate datasets, which is very similar to how modern REST APIs implement cursors.