Pagination
16 min
Returning GET /users with 10 million users in one response would be
slow, memory-hungry, and mostly useless to a client anyway.
Pagination returns a bounded SLICE of the collection per request:
def paginate(items, page, page_size):
start = (page - 1) * page_size
return items[start : start + page_size]
users = list(range(1, 26)) # imagine 25 user IDs
print(paginate(users, 1, 10)) # first 10
print(paginate(users, 3, 10)) # the remaining 5
A client typically follows this exact loop pattern -- keep requesting the next page until an empty (or partial) page signals the end of the collection.
Clients (and pagination UI — "page 3 of 12") often need to know the
TOTAL page count upfront, which requires rounding UP — a collection of
10 items at page size 3 needs 4 pages (3+3+3+1), not 10/3 = 3.33
truncated down to 3 (that would silently drop the last item):
def total_pages(total_items, page_size):
return -(-total_items // page_size) # ceiling division trick
print(total_pages(10, 3)) # 4
-(-a // b) is a common Python idiom for ceiling division using only
integer floor division (//) — negating, floor-dividing, then negating
again flips a floor into a ceiling. (math.ceil(a / b) works too, but
involves a float conversion this integer-only trick avoids.)
Write `paginate(items, page, page_size)`: return the slice of `items` for the given 1-indexed `page`.
Write `total_pages(total_items, page_size)`: return how many pages are needed to cover `total_items`, rounding UP (a partial last page still counts as a full page).
Why do most REST APIs paginate large collections instead of returning everything in one response?
You can paginate a collection into fixed-size pages and compute the total page count, rounding up so no items get silently dropped.