Solving API Latency: Choosing the Right Pagination Strategy in Django REST Framework
Stop API timeouts and database lag. Learn when to use PageNumber, LimitOffset, or Cursor pagination in Django REST Framework to optimize large dataset performance.
09 Mar 2026, 16:14 UTC

The Cost of the 'Full List' Response
When an API endpoint first launches, returning a full list of objects is convenient. However, as your database grows from hundreds to hundreds of thousands of records, that same endpoint becomes a performance bottleneck. Loading massive datasets into memory causes high latency, spikes in RAM usage, and can eventually lead to timeout errors or database crashes.
The solution is pagination—splitting a large dataset into smaller, manageable chunks. In Django REST Framework (DRF), the challenge isn't just enabling pagination, but choosing the specific strategy that matches your data's growth pattern and the client's navigation needs.
Offset-Based Pagination: PageNumber and LimitOffset
Most developers start with PageNumberPagination or LimitOffsetPagination. These are known as offset-based strategies because they tell the database to skip a certain number of rows before returning the result.
- PageNumberPagination: Uses a
?page=2parameter. It is intuitive for users who expect a traditional "Page 1, 2, 3" interface. - LimitOffsetPagination: Uses
?limit=10&offset=20. This is common in many industry-standard APIs and gives the client precise control over the window of data.
The critical trade-off here is performance. As the offset increases (e.g., requesting page 1,000), the database must still scan through all previous rows to find the starting point, leading to a linear increase in query time.
Cursor Pagination: The High-Performance Alternative
For datasets that scale into the millions or for "infinite scroll" feeds, CursorPagination is the superior choice. Instead of an offset, it uses a cursor—a pointer to a specific record (usually based on a unique, sequential field like an ID or a timestamp).
Because the database can jump directly to the record following the cursor using an index, the query time remains constant regardless of how deep the client navigates into the dataset. This eliminates the performance degradation seen in offset-based methods.
Implementation Example: Per-View Pagination
While you can set a global pagination class in settings.py, applying pagination per view allows you to use PageNumberPagination for administrative lists and CursorPagination for public-facing feeds.
# views.py
from rest_framework import generics
from rest_framework.pagination import CursorPagination
from .models import Transaction
from .serializers import TransactionSerializer
# Custom class to define the ordering field for the cursor
class TransactionPagination(CursorPagination):
page_size = 20
ordering = '-created_at' # Requires a unique, indexed field
class TransactionListView(generics.ListAPIView):
queryset = Transaction.objects.all()
serializer_class = TransactionSerializer
pagination_class = TransactionPagination
Verification and Testing
To verify the implementation, run the server and perform a GET request to the endpoint. You should observe the following changes in the JSON response structure:
- The result list is wrapped in a pagination object.
- A
countfield (for offset-based) or anextURL containing acursorparameter (for cursor-based) is present. - The number of items returned matches the
page_sizedefined in the class.
Comparing the Strategies
| Feature | PageNumber / LimitOffset | CursorPagination |
|---|---|---|
| Jump to specific page | Supported | Not Supported |
| Large Dataset Performance | Degrades (Slows down) | Constant (Fast) |
| Handling New Inserts | Items may shift/duplicate | Stable navigation |
| Implementation Cost | Low (Default) | Medium (Requires indexed field) |
Constraints and Limitations
Cursor pagination is not a universal replacement. Its primary limitation is the loss of "random access." A client cannot request "Page 5」 directly; they must follow the next link provided by the previous response. Additionally, the field used for ordering must be unique and indexed; otherwise, the database cannot efficiently locate the cursor, defeating the purpose of the strategy.
Final Decision Matrix
When choosing your strategy, ask these two questions:
- Does the user need to jump to a specific page? If yes, use
PageNumberPagination. - Is the dataset expected to grow beyond 10,000 records? If yes, use
CursorPaginationto prevent API timeouts.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.