Pagination Techniques0%

Pagination Techniques

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

DRF Pagination: PageNumber, LimitOffset, and Cursor

Returning a flat JSON array of 500,000 database rows in a single HTTP response crashes client browsers, exhausts server memory, and degrades network latency. Pagination segments large QuerySets into manageable chunks. Django REST Framework provides three distinct pagination schemes: PageNumberPagination, LimitOffsetPagination, and CursorPagination.


1. Comparing DRF's Three Pagination Styles

Pagination StyleURL SignatureDatabase PerformanceStable against Insertions?Client Experience
PageNumberPagination?page=3&page_size=20Moderate (OFFSET)No (duplicate/skipped items)Classic page numbers (1, 2, 3)
LimitOffsetPagination?limit=25&offset=50Moderate (OFFSET)No (page drift on insert)Flexible custom slice loading
CursorPagination?cursor=cD0yMDI2LTA...Optimal (O(1) B-tree)Yes (100% resilient)Infinite scroll, live feeds

2. PageNumberPagination (Classic Page Numbers)

Python
# api/pagination.py
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
 
class StandardResultsSetPagination(PageNumberPagination):
page_size = 20
page_size_query_param = "page_size" # Allows client to override page size
max_page_size = 100 # Protects against DoS via ?page_size=100000
page_query_param = "page"

Response Envelope:

JSON
{
"count": 1420,
"next": "https://api.enterprise.com/v1/articles/?page=3",
"previous": "https://api.enterprise.com/v1/articles/?page=1",
"results": [
{ "id": 41, "title": "Understanding WebSockets" }
]
}

3. LimitOffsetPagination (Flexible Range Slicing)

Maps directly to SQL LIMIT and OFFSET:

Python
from rest_framework.pagination import LimitOffsetPagination
 
class CustomLimitOffsetPagination(LimitOffsetPagination):
default_limit = 25
limit_query_param = "limit"
offset_query_param = "offset"
max_limit = 100

Client Query:

HTTP
GET /api/v1/articles/?limit=10&offset=40 # Fetches items 41 through 50

4. CursorPagination (Infinite Scroll & High Scale)

Both PageNumber and LimitOffset rely on SQL OFFSET. As OFFSET increases into millions (OFFSET 1000000), the database must scan and discard 1,000,000 rows, crippling performance. Furthermore, if a new item is inserted while a user browses page 1, items shift, causing duplicate entries on page 2.

CursorPagination uses an opaque encoded pointer referencing a specific row (e.g. timestamp or ID):

  • Uses SQL WHERE created_at < cursor LIMIT 20 (Instant B-Tree lookup!).
  • 100% immune to missing or duplicate items during real-time writes.
Python
from rest_framework.pagination import CursorPagination
 
class FeedCursorPagination(CursorPagination):
page_size = 25
cursor_query_param = "cursor"
ordering = "-created_at" # Ordering MUST be deterministic!

Response:

JSON
{
"next": "https://api.enterprise.com/v1/feed/?cursor=cD0yMDI2LTA5LTE1VDA0...",
"previous": null,
"results": [...]
}

Notice: count is omitted because calculating SELECT COUNT(*) on massive tables is expensive!


5. Attaching Pagination to ViewSets

Python
class ArticleViewSet(ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
pagination_class = StandardResultsSetPagination

Interactive Quiz

Practice Quiz

Question 1
Why does SQL OFFSET become slow on deep pagination (e.g. ?page=10000)?
Question 2
What is the primary advantage of CursorPagination over PageNumberPagination?
Question 3
Why is max_page_size critical on custom PageNumberPagination classes?
Question 4
Why does CursorPagination omit the total count property from its JSON response?
Question 5
How can a client disable pagination for a specific request if allowed by the view?
Next Lesson

One-to-One, One-to-Many, Many-to-Many

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext