API Design | Jul 16, 2025 | 14 min read
Pagination is key to building fast, scalable REST APIs. It improves performance, reduces server load, and helps users navigate large datasets easily. This guide covers common pagination strategies, implementation tips, and best practices for clean, efficient API design.
Implementing pagination in REST APIs is essential to optimizing performance, reducing server load, and improving the user experience by breaking large datasets into manageable chunks.
This article covers fundamental pagination patterns, including offset-based and cursor-based approaches, outlines how to use page
and limit
query parameters with sensible defaults and abuse prevention, demonstrates a sample implementation in Node.js and Express, explains how to structure paginated responses with metadata, and highlights best practices for building scalable and reliable APIs.
Need real-time insight into how your APIs are used and performing?
Treblle helps you monitor, debug, and optimize every API request.
Explore TreblleNeed real-time insight into how your APIs are used and performing?
Treblle helps you monitor, debug, and optimize every API request.
Explore TrebllePagination in REST APIs is a technique used to divide a large dataset into smaller, more manageable chunks, known as pages. Instead of returning the entire dataset in a single response, which can be inefficient and overwhelming, pagination allows clients to request data incrementally, improving performance and user experience.
Looking for a broader overview of pagination strategies? Check out our companion API pagination guide for more examples and design considerations.
Implementing pagination in your API offers several advantages:
Improved Performance: By limiting the amount of data returned in each request, pagination reduces server load and response times, leading to faster and more efficient data retrieval.
Reduced Resource Usage: Smaller data sets mean less memory consumption on both the server and client sides, optimizing resource utilization.
Enhanced User Experience: Pagination enables users to easily navigate large datasets, preventing long loading times and potential timeouts.
Scalability: As your dataset grows, pagination ensures that your API can handle increased data volumes without degrading performance.
Error Handling: With pagination, if an error occurs, only the current page needs to be retried, not the entire dataset, simplifying error recovery.
Overall, poor API performance drives users away. Learn how performance issues impact user retention and what to do about it in this performance deep dive.
There are several pagination strategies commonly used in REST APIs:
Offset-based pagination involves specifying a starting point (offset) and the number of items to retrieve (limit). This method is straightforward and widely supported.
Example Request:
GET /api/items?offset=20&limit=10
SQL Equivalent:
SELECT * FROM items LIMIT 10 OFFSET 20;
Advantages:
Simplicity: Easy to implement and understand.
Direct Access: Clients can request any page directly by adjusting the offset.
Disadvantages:
Performance Issues: As the offset increases, the database must scan more rows, leading to slower queries.
Data Inconsistency: If the dataset changes between requests (e.g., items are added or deleted), it can result in missing or duplicate records.
Cursor-based pagination marks a position in the dataset using a unique identifier (cursor). Instead of specifying an offset, the client provides the cursor to fetch the next set of records.
Example Request:
GET /api/items?after=abc123&limit=10
Advantages:
Consistency: More reliable in dynamic datasets, as it avoids issues caused by changes in the data between requests.
Performance: Typically more efficient for large datasets, as it doesn't require scanning through rows like offset-based pagination.
Disadvantages:
Complexity: More challenging to implement and understand.
Limited Navigation: Clients can't easily jump to a specific page; they must traverse the dataset sequentially.
Keyset pagination is a variant of cursor-based pagination that uses a specific field (e.g., ID or timestamp) to paginate through the dataset.
Example Request:
GET /api/items?after_id=100&limit=10
Advantages:
Efficiency: Offers better performance than offset-based pagination for large datasets.
Predictability: Provides a stable and consistent order of records.
Disadvantages:
Limited Flexibility: Clients need to know the key values to navigate.
Sorting Constraints: Requires a consistent and unique sorting field.
Time-based pagination uses timestamps to divide the dataset into time-based segments.
Example Request:
GET /api/items?created_before=2025-01-01T00:00:00Z&limit=10
Advantages:
Natural Segmentation: Useful for datasets where records are naturally ordered by time.
Simplifies Queries: Allows for straightforward queries based on time ranges.
Disadvantages:
Time Gaps: Some time segments may have no data if records are sparse.
Time Zone Considerations: Handling time zones can add complexity.
The page
parameter in REST APIs specifies which segment of the dataset to retrieve, allowing clients to request data sequentially without overwhelming the server.
The limit
parameter defines the number of records the API should return in a single request, controlling payload size and preventing large responses.
When clients omit these parameters, APIs often default to page=1
(the first page) and a standard limit
value such as 10 or 20 to ensure consistent behavior.
To prevent abuse, it’s best practice to enforce a maximum allowable limit
—for example, capping requests at 50 or 100 records per page—and returning an error or automatically applying the cap when clients exceed this threshold.
Pagination limits help mitigate abuse, but for a complete protection strategy, explore how to secure your first REST API with authentication, rate limiting, and input validation.
Pairing page
and limit
with rate limiting and throttling mechanisms further protects your API from excessive requests and denial-of-service attacks.
Pagination often involves GET requests, but knowing when to use POST, PUT, PATCH, and DELETE is just as important. Revisit the fundamentals in our guide to HTTP methods for REST APIs.
Convert page
and limit
from strings to integers—e.g., using parseInt(req.query.page, 10)
—and default to safe values like page = 1
and limit = 10
when parameters are missing or invalid.
Verify that both page
and limit
are positive integers; if not, respond with HTTP 400 Bad Request and a clear error message.
Enforce a maximum limit
(for example, 100 records per page) to prevent excessive payloads; if the client exceeds the threshold, you may cap the value or return HTTP 400.
Compute the offset with the formula offset = (page - 1) * limit
, determining how many records to skip before selecting the current page’s items.
Always apply a consistent ORDER BY
clause (e.g., on id
or created_at
) to ensure stable pagination, avoiding duplicates or gaps when the underlying data changes.
In SQL-based backends, integrate these values into your query:
SELECT *
FROM posts
ORDER BY created_at DESC
LIMIT {limit}
OFFSET {offset};
.skip(offset).limit(limit)
on your cursor to achieve the same effect.Node.js with Express and Knex
app.get('/api/posts', async (req, res, next) => {
const page = Math.max(parseInt(req.query.page, 10) || 1, 1);
const limit = Math.min(parseInt(req.query.limit, 10) || 10, 100);
const offset = (page - 1) * limit;
try {
const [ { count: total } ] = await db('posts').count('* as count');
const posts = await db('posts')
.orderBy('created_at', 'desc')
.offset(offset)
.limit(limit);
res.json({
data: posts,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
});
} catch (err) {
next(err);
}
});
This example handles parsing, offset calculation, querying with Knex, and response construction.
Python with Flask and SQLAlchemy
from flask import Flask, request, jsonify
from models import Post
@app.route('/api/posts')
def get_posts():
page = max(int(request.args.get('page', 1)), 1)
limit = min(int(request.args.get('limit', 10)), 100)
offset = (page - 1) * limit
query = Post.query.order_by(Post.created_at.desc())
total = query.count()
posts = query.offset(offset).limit(limit).all()
return jsonify({
'data': [p.to_dict() for p in posts],
'pagination': {
'page': page,
'limit': limit,
'total': total,
'pages': (total + limit - 1) // limit
}
})
Flask-SQLAlchemy’s offset()
and limit()
methods map directly to SQL semantics, making pagination straightforward in Python.
New to REST APIs? Start with our beginner-friendly tutorial on creating a simple REST API with JSON responses.
Always include total
(total record count) and pages
(total pages) so clients know dataset bounds.
Optionally provide next
and prev
URLs in the JSON body or Link
headers to simplify client navigation without manual URL building.
For full HATEOAS compliance, return hypermedia links like self,
first
, prev
, next
, and last
in response headers or body.
Handle out-of-range pages by returning HTTP 404 or HTTP 400 with an explanatory message, depending on your API’s design philosophy.
Designing a thoughtful, consistent paginated response is key to helping clients navigate large datasets effectively.
Below is a structured approach covering core payload design, essential metadata, optional navigation links (including HATEOAS), header usage, and edge-case handling to ensure your API is both predictable and easy to consume:
Your paginated response should bundle the actual data and pagination details together in a single JSON object for clarity and discoverability. A typical pattern is:
{
"data": [ … ],
"pagination": { … }
}
This keeps clients from hunting through headers or disparate endpoints to understand the paging state.
Include at a minimum the following fields inside your pagination
object:
page
: current page number.
limit
: number of items per page.
total
: total number of records across all pages.
pages
: total pages available (ceil(total/limit)).
Returning these four values ensures clients can render accurate pagination controls and know when they’ve reached the final page.
For smoother client integration, you can embed URLs for adjacent pages directly in the response body:
"links": {
"first": "/api/items?page=1&limit=10",
"prev": "/api/items?page=1&limit=10",
"next": "/api/items?page=3&limit=10",
"last": "/api/items?page=10&limit=10"
}
These HATEOAS-style links reduce client complexity by eliminating manual URL construction.
Providing self
alongside first
/prev
/next
/last
aligns with RESTful hypermedia principles.
Many APIs leverage the standard Link
header to indicate navigation URLs as an alternative or supplement to body links. For example:
Link: <https://api.example.com/items?page=3&limit=10>; rel="next",
<https://api.example.com/items?page=10&limit=10>; rel="last"
GitHub’s API uses this pattern to keep payloads lean while offering discoverability.
Choose headers when you want to separate meta-information from primary data, but ensure your documentation clearly describes how clients should parse them.
If your paginated API will be accessed from browsers or third-party clients, configuring CORS correctly is essential to avoid cross-origin request issues.
Out-of-range pages: Return HTTP 400 (Bad Request) or HTTP 404 (Not Found) with an error message explaining that the requested page exceeds available pages.
Empty results: Requesting a valid but empty page (e.g., page beyond pages
) can return an empty data array
with correct pagination
metadata to indicate “end of data” rather than an error.
Consistent ordering: Always apply a deterministic ORDER BY
(e.g., by primary key or timestamp) to prevent inconsistent results when data changes between requests.
Out-of-range pages and malformed parameters should be met with consistent and meaningful error messages. Learn how to implement this in our API error handling guide.
Below is a concrete example showing how a client requests the second page of posts with ten items per page, and how the server responds with both the data and pagination metadata.
GET /api/posts?page=2&limit=10 HTTP/1.1
Host: api.example.com
Accept: application/json
This request uses the page
and limit
query parameters to fetch page 2 with up to 10 items per page..
The pattern mirrors common pagination endpoints found in community examples—e.g., /products/index?page_number=5&page_size=20
—but using page
/limit
for consistency.
{
"data": [
{ "id": 11, "title": "Building Scalable APIs", "author": "Jane Doe" },
{ "id": 12, "title": "Understanding Cursors", "author": "John Smith" },
/* …items 13–19 omitted for brevity… */
{ "id": 20, "title": "Optimizing Database Queries","author": "Alice Lee" }
],
"pagination": {
"page": 2,
"limit": 10,
"total": 95,
"pages": 10
}
}
The top-level data
array contains the actual records for the requested page.
The pagination
object provides:
page
: current page number
limit
: items per page
total
: total number of records across all pages
pages
: total number of pages (ceil(total/limit)
).
Clients can iterate over data
to render the items and use pagination
to display controls (e.g., “Page 2 of 10”) or disable Next
/Prev
buttons appropriately..
Some APIs embed hypermedia links (e.g., first
, prev
, next
, last
) within a sibling links
object for HATEOAS compliance..
Alternatively, services like GitHub return pagination URLs in the HTTP Link
header rather than the response body, keeping payloads lean while still providing navigational hints..
Clear and consistent response formats across endpoints enable easy consumption by web, mobile, and server-side clients, ensuring performance and usability..
Note: Use this template as a basis for your API: adjust field names, include additional metadata or links as needed, and ensure your documentation clearly describes the request parameters and response schema. The example above uses a GET request to retrieve data, one of the core CRUD operations in REST. To understand how pagination fits into the full spectrum of REST interactions, check out our guide to RESTful CRUD operations.
Implementing effective pagination is crucial for building scalable and user-friendly APIs. Below are key best practices to guide your implementation:
Offset-based Pagination: Simple to implement and allows direct access to any page using parameters like offset
and limit
. However, it can lead to performance issues with large datasets and may result in inconsistent data if records are added or removed between requests .
Cursor-based Pagination: Uses a pointer (cursor) to navigate through results, providing better performance and consistency, especially with large or frequently changing datasets. It avoids the pitfalls of offset-based pagination but is slightly more complex to implement.
Keyset Pagination: This method relies on specific attributes (like an ID or timestamp) to paginate through results. It's highly efficient for large datasets but doesn't allow random access to specific pages.
Adopt standard naming conventions for pagination parameters, such as page
and limit
, or offset
and limit
. This consistency improves API usability and aligns with developer expectations.
Consistency in parameter and route naming improves developer experience. For detailed naming conventions and versioning strategies, check out our REST API endpoint design guide.
Include metadata in your API responses to convey additional information about the pagination state. This can include:
total
: Total number of records.
page
: Current page number.
limit
: Number of items per page.
pages
: Total number of pages.
Providing this metadata helps API consumers navigate through paginated data more effectively.
Allow clients to specify sorting and filtering parameters to retrieve data in a desired order or subset. This enhances flexibility and enables users to retrieve targeted results efficiently.
Account for scenarios such as:
Out-of-range page requests: Return informative error messages indicating that the requested page is out of range and provide relevant metadata to indicate the maximum available page number.
Invalid pagination parameters: Validate the pagination parameters provided by the API consumer. If the parameters are invalid, return an appropriate error message with details on the issue.
Use Proper Indexing: To improve query performance, ensure that the fields used for sorting and filtering are properly indexed.
Limit Query Execution Time: Avoid unnecessary processing by limiting the execution time of queries.
Leverage Caching: Store frequently accessed data to reduce database queries and improve response times.
Effective API monitoring and analysis are vital for ensuring optimal performance, reliability, and user satisfaction. Continuously observing your API's behavior can help you proactively identify issues, optimize performance, and make informed decisions.
Treblle offers a comprehensive API observability platform designed to streamline monitoring and analysis:
Real-Time Insights: Treblle provides immediate visibility into API activity, capturing every request and response in real time. This facilitates prompt issue detection and ensures rapid response times.
API Governance: Automate the process of assessing your APIs' design, performance, and security with a single intelligent governance layer.
Error Detection and Reporting: Treblle excels in identifying and reporting errors, offering detailed information that aids in quick debugging and resolution.
Implementing pagination in your REST API is essential for scalability, performance, and a seamless user experience. By adopting best practices, such as choosing the appropriate pagination strategy, using consistent parameter names, providing comprehensive metadata, and gracefully handling edge cases, you ensure that your API can efficiently manage large datasets and meet client needs.
Consider further integrating tools like Treblle to enhance your API's reliability and observability. Treblle offers real-time API monitoring, automated API documentation, and analytics, helping you build and optimize enterprise-grade APIs.
By combining robust pagination techniques with comprehensive monitoring solutions, you can build APIs that are not only efficient and scalable but also reliable and user-friendly.
Need real-time insight into how your APIs are used and performing?
Treblle helps you monitor, debug, and optimize every API request.
Explore TreblleNeed real-time insight into how your APIs are used and performing?
Treblle helps you monitor, debug, and optimize every API request.
Explore TreblleAPIs are the backbone of modern software, but speed, reliability, and efficiency do not happen by accident. This guide explains what API performance really means, which metrics matter, and how to optimize at every layer to meet the standards top platforms set.
Effective error handling is key to building reliable APIs. In this guide, you’ll learn how to categorize errors, choose the right status codes, return consistent JSON responses, and implement secure, centralized error handling in Express.
AI apps need both context and scalability—but how do you choose the right architecture? This article compares the Model Context Protocol (MCP) and Serverless APIs, helping you understand where each fits and how they can work together to power smarter, faster AI experiences.