Table of Contents

    pagination

    API DESIGN & SERVICE CONTRACTS

    Pagination

    Learn how to divide large API collections into bounded pages using offset, page-number, cursor, and keyset pagination while preserving stable ordering, performance, security, and predictable navigation.

    Introduction

    Collection endpoints can contain thousands or millions of resources. Returning an entire collection in one response can consume excessive database, memory, serialization, network, and client-processing resources.

    Pagination divides a collection into smaller result sets that clients can retrieve incrementally.

    A complete pagination contract defines:

    • The default page size
    • The maximum page size
    • The result ordering
    • The navigation parameters
    • The response metadata
    • The behaviour under concurrent inserts and deletes
    • The treatment of filters and sorting
    • The cursor format and lifetime
    • The availability of total counts
    • The behaviour at collection boundaries

    Core idea: Pagination is not only a user-interface feature. It is a service-contract and data-access strategy that bounds resource consumption and defines how clients navigate a changing ordered collection.

    In the System Design curriculum, Pagination is Topic 4.5 under API Design & Service Contracts. It follows resource modeling and precedes versioning, idempotency, authentication versus authorization, and rate-limit semantics.

    Prerequisites

    # Prerequisite Why It Is Needed
    1 REST APIs Pagination is commonly part of a collection-resource contract.
    2 Resource modeling The collection, item representation, filters, and ordering must already be defined.
    3 SQL ordering Pagination depends on deterministic database ordering.
    4 Indexes Efficient keyset pagination requires indexes aligned with filters and sort keys.
    5 JSON and HTTP Pagination parameters and navigation metadata are exchanged through API requests and responses.
    6 Authentication and authorization Every page must include only resources the caller is authorized to access.

    Why Pagination Is Required

    Without pagination, a single collection request can attempt to retrieve, serialize, and transfer every matching resource.

    Unbounded collection request
            |
            v
    Large database result
            |
            v
    High application memory usage
            |
            v
    Large response payload
            |
            v
    Long transfer and client-processing time

    Pagination provides:

    • Bounded response sizes
    • Predictable memory usage
    • Lower network transfer per request
    • Incremental client rendering
    • Protection against accidental full-table retrieval
    • Better control over database work
    • A clear navigation contract
    Pagination Lifecycle
    apply authorization → apply filters → apply stable ordering → apply page boundary → return bounded results

    Pagination Strategies

    Strategy Navigation Input Typical Use
    Page-number pagination Page number and page size Small or stable datasets requiring page navigation
    Offset pagination Offset and limit Simple internal tools and shallow result sets
    Cursor pagination Opaque continuation token Large or frequently changing collections
    Keyset pagination Values from the last returned sort key Efficient indexed traversal through ordered data

    Page-number Pagination

    Page-number pagination allows clients to request a numbered page with a selected page size.

    GET /orders?page=3&pageSize=20 HTTP/1.1
    Host: api.example.com
    Accept: application/json

    The server commonly calculates the offset as:

    \[ Offset = (Page - 1) \times PageSize \]

    For page 3 with a page size of 20:

    \[ Offset = (3 - 1) \times 20 = 40 \]

    SQL Example

    SELECT
        id,
        customer_id,
        status,
        total_amount,
        created_at
    FROM orders
    ORDER BY
        created_at DESC,
        id DESC
    LIMIT 20
    OFFSET 40;

    Page-number pagination is easy for users to understand and supports direct navigation to a particular page.

    Offset Pagination

    Offset pagination allows the client to specify how many matching rows should be skipped before the server returns the next result set.

    GET /orders?limit=20&offset=40 HTTP/1.1
    Host: api.example.com
    Accept: application/json

    SQL Example

    SELECT
        id,
        customer_id,
        status,
        total_amount,
        created_at
    FROM orders
    ORDER BY
        created_at DESC,
        id DESC
    LIMIT 20
    OFFSET 40;

    Offset Pagination Advantages

    • Easy to understand
    • Easy to implement
    • Supports direct page navigation
    • Works naturally with many database frameworks
    • Can accompany a total count

    Offset Pagination Limitations

    • Large offsets can require increasing database work
    • Concurrent inserts can shift page boundaries
    • Concurrent deletes can cause items to be skipped
    • Clients can receive duplicate items between pages
    • Deep page requests can create expensive queries

    Page Drift

    Page drift occurs when the collection changes between offset-based page requests.

    Initial ordered collection:
    
    A B C D E F
    
    
    Page 1 with limit 3:
    
    A B C
    
    
    A new item X is inserted at the beginning:
    
    X A B C D E F
    
    
    Page 2 with offset 3:
    
    C D E
    
    
    Result:
    
    C appears on both pages.

    Deletions can produce the opposite problem, where an item shifts into a previously skipped range and is never returned to the client.

    Consistency warning: Offset pagination describes a position in the current result set, not a stable continuation from the last item previously observed.

    Cursor Pagination

    Cursor pagination uses an opaque continuation token that represents a position within an ordered result set.

    First Request

    GET /orders?limit=20 HTTP/1.1
    Host: api.example.com
    Accept: application/json

    First Response

    {
      "items": [
        {
          "id": "ord_9001",
          "status": "confirmed",
          "createdAt": "2026-09-22T08:30:00Z"
        }
      ],
      "page": {
        "limit": 20,
        "nextCursor": "eyJjcmVhdGVkQXQiOiIyMDI2LTA5LTIyVDA4OjMwOjAwWiIsImlkIjoib3JkXzkwMDEifQ",
        "hasMore": true
      }
    }

    Next Request

    GET /orders?limit=20&after=eyJjcmVhdGVkQXQiOiIyMDI2LTA5LTIyVDA4OjMwOjAwWiIsImlkIjoib3JkXzkwMDEifQ HTTP/1.1
    Host: api.example.com
    Accept: application/json

    The client treats the cursor as an opaque value. The client should not parse, modify, or create cursor values.

    Keyset Pagination

    Keyset pagination uses the values of the last returned item's sort keys to retrieve the next page.

    Ordering:
    
    created_at DESC,
    id DESC
    
    
    Last item from previous page:
    
    created_at = 2026-09-22T08:30:00Z
    id = ord_9001
    
    
    Next page:
    
    Return rows positioned after that
    created_at and id combination.

    SQL Example

    SELECT
        id,
        customer_id,
        status,
        total_amount,
        created_at
    FROM orders
    WHERE
        created_at < :cursor_created_at
        OR (
            created_at = :cursor_created_at
            AND id < :cursor_id
        )
    ORDER BY
        created_at DESC,
        id DESC
    LIMIT 21;

    The query requests one more row than the public page size. If the page size is 20 and the query returns 21 rows, the server can return the first 20 and report that more results are available.

    Stable and Deterministic Ordering

    Every pagination strategy requires explicit ordering. Cursor and keyset pagination require ordering that uniquely determines each item's position.

    Non-unique ordering
    ORDER BY created_at DESC

    Several rows can have the same created_at value, so the order between those rows is not uniquely defined.

    Deterministic composite ordering
    ORDER BY
        created_at DESC,
        id DESC

    A unique tiebreaker ensures that every item has one deterministic position in the ordered result.

    Ordering rule: Append a unique, immutable tiebreaker when the primary sort field is not unique.

    Offset vs Cursor Pagination

    Area Offset Pagination Cursor Pagination
    Navigation Offset or page number Continuation cursor
    Direct page jump Supported Normally unsupported
    Deep-page database work Can increase with the offset Can seek from indexed sort values
    Concurrent inserts Can shift page boundaries Continues from a known ordered position
    Concurrent deletes Can cause skipped items Normally continues from the cursor position
    Implementation complexity Lower Higher
    Typical use Small datasets and administrative tables Feeds, activity logs, transactions, and large collections

    Opaque Cursor Design

    A cursor can internally contain the values required to continue the ordered query.

    {
      "version": 1,
      "createdAt": "2026-09-22T08:30:00Z",
      "id": "ord_9001",
      "direction": "forward"
    }

    The server can serialize and encode this structure before returning it to the client.

    A cursor can include:

    • A cursor-format version
    • Sort-key values
    • A unique tiebreaker
    • The navigation direction
    • A filter or query fingerprint
    • An expiration value where required

    Encoding is not encryption or integrity protection. Sensitive cursor data should not be exposed merely by converting it to Base64.

    Cursor Validation

    A server should treat every cursor as untrusted input.

    Cursor validation can include:

    • Maximum encoded length
    • Valid encoding
    • Valid structure
    • Supported cursor version
    • Expected field types
    • Supported direction
    • Filter and sort compatibility
    • Expiration policy
    • Integrity verification where used

    Invalid Cursor Response

    HTTP/1.1 400 Bad Request
    Content-Type: application/problem+json
    
    {
      "type": "invalid-pagination-cursor",
      "title": "Invalid pagination cursor",
      "status": 400,
      "detail": "The supplied cursor cannot be used for this collection request."
    }

    Cursors, Filters, and Sorting

    A cursor generated for one filtered and sorted result set should not be reused with incompatible parameters.

    Original request:
    
    status = confirmed
    sort = createdAt descending
    
    
    Next request using cursor:
    
    status = cancelled
    sort = total ascending
    
    
    Problem:
    
    The cursor position no longer describes
    the requested ordered result set.

    The API can choose one of these contracts:

    • Reject changed filters or sorting when a cursor is supplied
    • Include a query fingerprint inside the cursor
    • Bind the cursor to stored server-side pagination state

    Cursor rule: A continuation cursor belongs to the query that created it, including its filters, authorization scope, and ordering.

    Forward and Backward Pagination

    Some APIs support only forward traversal. Others support both directions.

    Forward Request

    GET /orders?limit=20&after=NEXT_CURSOR HTTP/1.1

    Backward Request

    GET /orders?limit=20&before=PREVIOUS_CURSOR HTTP/1.1

    Backward pagination requires careful reversal of comparison and ordering logic. Results should still be presented in the collection's documented public order.

    Pagination Response Contract

    {
      "items": [
        {
          "id": "ord_9001",
          "status": "confirmed"
        },
        {
          "id": "ord_9000",
          "status": "processing"
        }
      ],
      "page": {
        "limit": 20,
        "nextCursor": "opaque-next-cursor",
        "previousCursor": null,
        "hasMore": true
      },
      "links": {
        "self": "/orders?limit=20",
        "next": "/orders?limit=20&after=opaque-next-cursor"
      }
    }

    A cursor response can include:

    • The returned items
    • The effective page size
    • The next cursor
    • The previous cursor where supported
    • A flag indicating more results
    • Navigation links

    Total Counts

    Some clients require a total matching count. Calculating an exact total can require a separate database aggregation over the complete filtered result.

    SELECT
        COUNT(*)
    FROM orders
    WHERE
        tenant_id = :tenant_id
        AND status = :status;

    The API contract should define whether the count is:

    • Exact
    • Estimated
    • Cached
    • Computed only when explicitly requested
    • Unavailable for selected collection types
    Hidden mandatory count
    Every page request performs:
    
    1. The paginated data query
    2. A complete matching COUNT query
    
    Even when the client does not display the total.
    Explicit count contract
    Default response:
    
    Returns page items and hasMore.
    
    
    Optional response:
    
    Returns total count only when the
    client requests and is authorized for it.

    PHP Cursor Pagination Example

    <?php
    
    declare(strict_types=1);
    
    const DEFAULT_PAGE_SIZE = 20;
    const MAXIMUM_PAGE_SIZE = 100;
    
    function readPageSize(
        array $query
    ): int {
        $requested =
            filter_var(
                $query['limit'] ??
                    DEFAULT_PAGE_SIZE,
                FILTER_VALIDATE_INT
            );
    
        if ($requested === false ||
            $requested < 1) {
    
            throw new InvalidArgumentException(
                'The page size must be a positive integer.'
            );
        }
    
        return min(
            $requested,
            MAXIMUM_PAGE_SIZE
        );
    }
    
    function encodeCursor(
        string $createdAt,
        string $id
    ): string {
        $payload =
            json_encode(
                [
                    'version' => 1,
                    'createdAt' => $createdAt,
                    'id' => $id
                ],
                JSON_THROW_ON_ERROR
            );
    
        return rtrim(
            strtr(
                base64_encode($payload),
                '+/',
                '-_'
            ),
            '='
        );
    }
    
    function decodeCursor(
        string $cursor
    ): array {
        if (strlen($cursor) > 1000) {
            throw new InvalidArgumentException(
                'The cursor is too long.'
            );
        }
    
        $padding =
            strlen($cursor) % 4;
    
        if ($padding !== 0) {
            $cursor .=
                str_repeat(
                    '=',
                    4 - $padding
                );
        }
    
        $decoded =
            base64_decode(
                strtr(
                    $cursor,
                    '-_',
                    '+/'
                ),
                true
            );
    
        if ($decoded === false) {
            throw new InvalidArgumentException(
                'The cursor encoding is invalid.'
            );
        }
    
        $payload =
            json_decode(
                $decoded,
                true,
                16,
                JSON_THROW_ON_ERROR
            );
    
        if (
            !is_array($payload) ||
            ($payload['version'] ?? null) !== 1 ||
            !is_string(
                $payload['createdAt'] ?? null
            ) ||
            !is_string(
                $payload['id'] ?? null
            )
        ) {
            throw new InvalidArgumentException(
                'The cursor structure is invalid.'
            );
        }
    
        return $payload;
    }

    The example demonstrates length checks, strict decoding, cursor versioning, and structural validation. A production application can additionally sign or encrypt cursors when required by the threat model.

    PHP Database Query

    <?php
    
    declare(strict_types=1);
    
    $pageSize =
        readPageSize(
            $_GET
        );
    
    $cursor =
        isset($_GET['after'])
            ? decodeCursor(
                (string)$_GET['after']
            )
            : null;
    
    $sql = '
        SELECT
            public_id,
            status,
            total_amount,
            currency,
            created_at
        FROM orders
        WHERE
            tenant_id = :tenant_id
    ';
    
    $parameters = [
        'tenant_id' => $tenantId
    ];
    
    if ($cursor !== null) {
        $sql .= '
            AND (
                created_at < :cursor_created_at
                OR (
                    created_at = :cursor_created_at
                    AND public_id < :cursor_id
                )
            )
        ';
    
        $parameters['cursor_created_at'] =
            $cursor['createdAt'];
    
        $parameters['cursor_id'] =
            $cursor['id'];
    }
    
    $sql .= '
        ORDER BY
            created_at DESC,
            public_id DESC
        LIMIT :query_limit
    ';
    
    $statement =
        $pdo->prepare(
            $sql
        );
    
    foreach (
        $parameters as
        $name => $value
    ) {
        $statement->bindValue(
            ':' . $name,
            $value,
            PDO::PARAM_STR
        );
    }
    
    $statement->bindValue(
        ':query_limit',
        $pageSize + 1,
        PDO::PARAM_INT
    );
    
    $statement->execute();
    
    $rows =
        $statement->fetchAll(
            PDO::FETCH_ASSOC
        );
    
    $hasMore =
        count($rows) > $pageSize;
    
    if ($hasMore) {
        array_pop($rows);
    }
    
    $nextCursor = null;
    
    if ($hasMore && $rows !== []) {
        $lastRow =
            $rows[
                array_key_last($rows)
            ];
    
        $nextCursor =
            encodeCursor(
                $lastRow['created_at'],
                $lastRow['public_id']
            );
    }
    
    $response = [
        'items' => $rows,
        'page' => [
            'limit' => $pageSize,
            'nextCursor' => $nextCursor,
            'hasMore' => $hasMore
        ]
    ];
    
    header(
        'Content-Type: application/json; charset=utf-8'
    );
    
    echo json_encode(
        $response,
        JSON_THROW_ON_ERROR
    );

    Authorization and tenant restrictions must be applied inside the database query before pagination. Filtering unauthorized rows after retrieving a page can produce incomplete pages and can disclose collection information.

    Supporting Database Index

    CREATE INDEX ix_orders_tenant_created_id
    ON orders
    (
        tenant_id,
        created_at DESC,
        public_id DESC
    );

    The most appropriate index depends on the database platform, data distribution, filters, selected fields, and query plan. Validate the actual query plan rather than assuming that an index will always be selected.

    GraphQL Connection Pagination

    type OrderConnection {
      edges: [OrderEdge!]!
      nodes: [Order!]!
      pageInfo: PageInfo!
    }
    
    type OrderEdge {
      cursor: String!
      node: Order!
    }
    
    type PageInfo {
      startCursor: String
      endCursor: String
      hasPreviousPage: Boolean!
      hasNextPage: Boolean!
    }
    
    type Query {
      orders(
        first: Int = 20
        after: String
      ): OrderConnection!
    }

    GraphQL Query

    query GetOrders(
      $first: Int!
      $after: String
    ) {
      orders(
        first: $first
        after: $after
      ) {
        nodes {
          id
          status
          createdAt
        }
        pageInfo {
          endCursor
          hasNextPage
        }
      }
    }

    gRPC Pagination

    message ListOrdersRequest {
      int32 page_size = 1;
      string page_token = 2;
      OrderStatus status = 3;
    }
    
    message ListOrdersResponse {
      repeated Order orders = 1;
      string next_page_token = 2;
    }

    The page token should be treated as an opaque continuation value. The service should enforce its own default and maximum page sizes even when the client requests a larger value.

    Authorization and Pagination

    Authorization filtering must occur before page boundaries are applied.

    Incorrect sequence
    1. Retrieve 20 orders.
    2. Remove unauthorized orders.
    3. Return the remaining 7 orders.
    Correct sequence
    1. Establish caller identity.
    2. Apply tenant and authorization conditions.
    3. Apply collection filters.
    4. Apply stable ordering.
    5. Retrieve the first 20 authorized resources.

    The cursor should not permit a caller to escape the caller's authorized tenant or data scope.

    Pagination Security Controls

    • Enforce a maximum page size.
    • Validate all cursor structures.
    • Reject unsupported sort fields.
    • Allowlist filtering operators.
    • Apply authorization before pagination.
    • Prevent arbitrary SQL field injection.
    • Limit cursor and query-string length.
    • Apply request-rate limits.
    • Use finite database and request deadlines.
    • Avoid placing sensitive values in exposed cursors.

    Pagination Observability

    Useful pagination metrics include:

    • Requested and effective page size
    • Returned item count
    • Pagination strategy
    • Database query duration
    • Rows examined where available
    • Deep offset frequency
    • Invalid cursor rate
    • Page traversal depth
    • Total-count query duration
    • Response payload size
    • Query timeout rate
    • Maximum-page-size violations

    Cursor values can contain encoded internal information. Logs should redact or hash them when the data-handling policy requires it.

    Test Pagination with curl

    Offset Request

    curl -i \
      'https://api.example.com/orders?limit=20&offset=40'

    Cursor Request

    curl -i \
      'https://api.example.com/orders?limit=20&after=OPAQUE_CURSOR'

    Filtered Cursor Request

    curl -i \
      'https://api.example.com/orders?status=confirmed&limit=20&after=OPAQUE_CURSOR'

    Replace the example endpoint and cursor with approved test values. Avoid including production credentials or sensitive cursors in shell history.

    Pagination Troubleshooting Workflow

    1. Confirm the endpoint, filters, sorting, and page parameters.
    2. Confirm the effective default and maximum page size.
    3. Verify that ordering is explicit and deterministic.
    4. Verify that a unique tiebreaker is included.
    5. Decode cursors only in an authorized diagnostic environment.
    6. Confirm cursor compatibility with the current filters and sorting.
    7. Inspect the generated database query.
    8. Inspect the query plan and supporting indexes.
    9. Test concurrent inserts and deletes between page requests.
    10. Verify authorization filtering before pagination.
    11. Check whether exact total counts are adding significant cost.
    12. Compare runtime responses with the published contract.

    Common Pagination Mistakes

    1

    Returning an Unlimited Collection

    Every potentially large collection should enforce a bounded page size.

    2

    Relying on Implicit Database Order

    Without an explicit order, the database does not provide a stable pagination sequence.

    3

    Sorting by a Non-unique Field Only

    Equal sort values can cause inconsistent page boundaries. Add a unique tiebreaker.

    4

    Allowing Unlimited Page Sizes

    A client can bypass pagination by requesting an excessively large page.

    5

    Using Large Offsets for High-volume Data

    Deep offsets can create increasing database work and unstable results under concurrent changes.

    6

    Exposing Editable Cursor Internals

    Clients should treat cursors as opaque values and should not construct their own continuation positions.

    7

    Reusing a Cursor with Different Filters

    A cursor belongs to the filtered and ordered query that created it.

    8

    Filtering Unauthorized Rows after Pagination

    This produces incomplete pages and can disclose information about hidden resources.

    9

    Computing Exact Totals for Every Request

    Exact counts can add unnecessary database work when the client needs only the next page.

    10

    Using Mutable Sort Fields without a Policy

    An item can move across the pagination boundary when its sort value changes during traversal.

    11

    Not Versioning Cursor Formats

    A cursor-format version supports controlled decoding after implementation changes.

    12

    Assuming Cursor Pagination Creates a Snapshot

    A cursor normally provides positional continuation. It does not automatically freeze the complete collection at one point in time.

    Recommended Test Cases

    Test Expected Evidence
    Default page size The documented default is applied when the client omits the size
    Maximum page size An excessive value is capped or rejected according to contract
    Empty collection An empty items array and correct navigation metadata are returned
    Final page The next cursor is absent and hasMore is false
    Invalid cursor The API returns a safe and consistent validation error
    Changed filter A cursor from another filtered query is rejected
    Duplicate sort values The unique tiebreaker prevents duplicate or missing items
    Concurrent insert Cursor traversal continues from the prior ordered position
    Concurrent delete The next page remains valid and does not fail on the removed row
    Unauthorized records Only authorized records contribute to the returned page
    Deep offset Performance and query-plan evidence are captured
    Cursor-format migration Supported cursor versions decode according to policy

    Pagination Best Practices

    Recommended Practices

    • Paginate every potentially large collection.
    • Define a sensible default page size.
    • Enforce a strict maximum page size.
    • Always use explicit and deterministic ordering.
    • Add a unique and immutable tiebreaker.
    • Use offset pagination for small, stable, and shallow result sets.
    • Use cursor or keyset pagination for large or frequently changing collections.
    • Treat cursors as opaque, untrusted input.
    • Version cursor formats.
    • Bind cursors to their filters, sorting, and authorization scope.
    • Do not place sensitive values in exposed cursors.
    • Apply authorization before pagination.
    • Align database indexes with filters and sort keys.
    • Request one additional row to determine whether more data exists.
    • Compute exact totals only when the client genuinely requires them.
    • Document null, empty, first-page, and final-page behaviour.
    • Test concurrent insertion, deletion, and sort-field changes.
    • Monitor deep offsets, invalid cursors, page sizes, and query duration.
    • Keep pagination parameters consistent across collection endpoints.
    • Validate runtime responses against the published service contract.

    Practice Exercise

    Implement offset and cursor pagination for an order collection, then compare correctness and database performance under concurrent changes.

    Requirements

    1. Create an order collection containing test records.
    2. Define ordering by creation time and public ID.
    3. Implement offset pagination.
    4. Implement cursor pagination.
    5. Use a default page size of 20.
    6. Enforce a maximum page size of 100.
    7. Use an opaque, versioned cursor.
    8. Validate cursor structure and length.
    9. Bind the cursor to the filter and sort contract.
    10. Apply tenant authorization before pagination.
    11. Request one extra row to calculate hasMore.
    12. Create a supporting composite index.
    13. Insert a new order between page requests.
    14. Delete an order between page requests.
    15. Compare duplicates, omissions, query plans, and response times.

    Comparison Template

    Measurement Offset Pagination Cursor Pagination
    Request parameters Record evidence Record evidence
    Database query Record evidence Record evidence
    Supporting index Record evidence Record evidence
    First-page duration Record measurement Record measurement
    Deep-page duration Record measurement Record measurement
    Concurrent insert behaviour Record evidence Record evidence
    Concurrent delete behaviour Record evidence Record evidence
    Direct page jump Record support Record limitation
    Client implementation complexity Record observation Record observation

    Frequently Asked Questions

    1

    What is pagination?

    Pagination divides an ordered collection into bounded result sets that clients retrieve incrementally.

    2

    What is offset pagination?

    Offset pagination skips a specified number of matching records and returns the next limited result set.

    3

    What is cursor pagination?

    Cursor pagination uses an opaque continuation token representing a position within an ordered result set.

    4

    What is keyset pagination?

    Keyset pagination retrieves the next page by comparing rows with the sort values of the last previously returned item.

    5

    Why does pagination need deterministic ordering?

    Deterministic ordering gives every item one defined position and prevents rows with equal sort values from moving unpredictably between pages.

    6

    Why should a unique tiebreaker be added?

    A unique tiebreaker distinguishes items that have the same primary sort value and creates an unambiguous page boundary.

    7

    Should clients decode pagination cursors?

    No. Cursors should be treated as opaque values controlled by the server.

    8

    Can a cursor be reused with different filters?

    Not unless the contract explicitly supports it. A cursor normally belongs to the filtering, sorting, and authorization context that created it.

    9

    Does cursor pagination create a database snapshot?

    No. A cursor normally identifies an ordered continuation position. Snapshot consistency requires a separate database and application design.

    10

    Should every page return an exact total count?

    Not necessarily. An exact count can add significant work. Return it only when the client contract requires it and the cost is acceptable.

    11

    Which pagination method is best for infinite scrolling?

    Cursor or keyset pagination is commonly appropriate because it continues from a known ordered position and avoids deep offsets.

    12

    What comes after pagination?

    The next topic is API versioning, followed by idempotency, authentication versus authorization, and rate-limit semantics.

    Key Takeaway

    Pagination bounds collection responses and defines how clients navigate ordered data. Offset pagination is simple and supports direct page jumps, but deep offsets and concurrent changes can reduce performance and consistency. Cursor and keyset pagination continue from stable sort values and are generally better suited to large or changing collections. Every pagination contract should enforce page-size limits, deterministic ordering, a unique tiebreaker, authorization before pagination, validated opaque cursors, supporting indexes, and documented first-page, final-page, filtering, counting, and error behaviour.