Choosing the Right Pagination Strategy in Django REST Framework
Stop API memory exhaustion by choosing the right DRF pagination strategy. Learn when to use PageNumber, LimitOffset, and CursorPagination for optimal database performance.
22 Sept 2025, 14:36 UTC

The Memory Exhaustion Trap
When an API endpoint returns a simple list of objects, it works perfectly with 100 records. However, as your database grows to 100,000 records, that same endpoint becomes a liability. Attempting to serialize and transmit a massive dataset in a single HTTP response leads to high memory consumption on the server, slow Time to First Byte (TTFB), and potential timeouts for the client.
The solution is pagination: breaking the dataset into manageable chunks. In Django REST Framework (DRF), the challenge isn't enabling pagination, but choosing the specific strategy that balances user experience with database performance.
PageNumberPagination: The Standard Approach
PageNumberPagination is the most intuitive method. It uses a simple ?page=2 query parameter to retrieve a specific slice of data. This is ideal for administrative dashboards or search results where a user needs to jump to a specific page.
The Performance Trade-off: This method relies on SQL OFFSET. As the page number increases, the database must scan through all preceding rows before returning the requested slice. For very deep pages (e.g., page 5,000), query performance degrades significantly.
LimitOffsetPagination: Flexible Slicing
LimitOffsetPagination mirrors the logic of SQL LIMIT and OFFSET. Clients specify how many items they want (limit) and where to start (offset), such as ?limit=20&offset=40.
This is particularly useful for clients that implement their own custom scrolling logic or need to request variable page sizes. Like PageNumberPagination, however, it suffers from performance degradation as the offset grows.
CursorPagination: The High-Scale Solution
For real-time feeds or massive datasets, CursorPagination is the engineering choice. Instead of an offset, it uses a "cursor"—a pointer to a specific record (usually based on a timestamp or ID). The client requests the next set of results relative to that pointer.
Key Advantages:
- Constant Time Performance: It avoids the
OFFSETscan, keeping query speeds consistent regardless of how deep the user scrolls. - Consistency: In a fast-moving feed, if a new item is inserted at the top of the list, offset-based pagination would shift all items, causing the user to see a duplicate record on the next page. Cursors prevent this by anchoring the position.
Implementation Example: Per-View Configuration
While you can set pagination globally in settings.py, applying it per-view allows you to use the best strategy for each specific use case. Below is a configuration for a high-volume Order API using Cursor Pagination.
# views.py
from rest_framework.pagination import CursorPagination
from rest_framework import generics
from .models import Order
from .serializers import OrderSerializer
class OrderPagination(CursorPagination):
page_size = 100
ordering = '-created_at' # Required for CursorPagination
class OrderListView(generics.ListAPIView):
queryset = Order.objects.all()
serializer_class = OrderSerializer
pagination_class = OrderPagination
Execution Details:
- Where to run: This code resides in your Django application's
views.py. - Permissions: The user executing the request must have
GETpermissions for the endpoint. - Expected Result: The API response will change from a JSON list
[...]to a JSON object containing"next"and"previous"URLs, along with a"results"key containing the data.
Comparison Summary
| Strategy | Client Parameter | DB Performance | Use Case |
|---|---|---|---|
| PageNumber | ?page=X |
Degrades with depth | Admin Panels, Search |
| LimitOffset | ?limit=X&offset=Y |
Degrades with depth | Custom Client Scrolling |
| Cursor | ?cursor=X |
Consistent/Fast | Infinite Scroll, Large Feeds |
Limitations and Verification
The primary limitation of CursorPagination is that it does not support "jumping" to a specific page; users can only move forward or backward sequentially. Additionally, it requires a strictly ordered queryset (e.g., a unique timestamp or primary key).
Verification Step: To verify your implementation, use a tool like curl or Postman to request the first page. Check that the response contains a "next" key. Copy that URL and request it; verify that the items returned are distinct from the first page and that the response time remains stable even as you navigate deeper into the dataset.
Actionable Closing
Audit your current DRF endpoints. If you are using global PageNumberPagination for tables with millions of rows, you are creating a performance bottleneck. Switch your high-volume feeds to CursorPagination and reserve PageNumberPagination for low-volume management interfaces.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.