7 LIMIT and OFFSET
LIMIT n returns at most n rows. OFFSET m skips the first m rows of the result. Combined, they implement classic page-based pagination: page k of size p is LIMIT p OFFSET (k - 1) * p. Without an ORDER BY, “first” is meaningless — always pair LIMIT/OFFSET with an explicit order.
What you’ll learn
- Restricting result size with
LIMIT - Skipping rows with
OFFSET - Pagination patterns and their costs
- Why offset-based pagination scales poorly on large tables
- Keyset (cursor) pagination as the better alternative
Example
LIMIT 2 OFFSET 1 reads “two rows, after skipping one.” Internally, PostgreSQL still has to compute all rows up to OFFSET + LIMIT and discard the offset — for page 10,000 of 50 rows per page, that means processing 500,000 rows for a one-based page number. Offset pagination is fine for the first few pages but terrible for deep pagination.
The keyset pattern (also called cursor-based or seek pagination) uses the last seen value as the filter: WHERE id > $last_id ORDER BY id LIMIT 50. The query touches only the next 50 rows regardless of how far you have paginated. It requires an indexed column (often the primary key or created_at, id) but is the standard approach in production systems.
Run the example
To run (requires fruits from earlier lessons):
Common pitfalls
LIMIT 10withoutORDER BYreturns some ten rows — and the choice can change between executions. Always sort.- Two rows with the same sort key may swap places on different pages. Add a tie-breaker (
ORDER BY created_at DESC, id DESC) for stable pagination. - Deep offsets are slow. For page 1,000 of an “infinite scroll” feed, switch to keyset.
OFFSETis also used inSELECT ... FOR UPDATEpatterns and can interact poorly with row locks; if you need to process batches, keyset over a primary key is again the safer pattern.
PostgreSQL also accepts FETCH FIRST n ROWS ONLY (SQL-standard), equivalent to LIMIT n. FETCH FIRST n ROWS WITH TIES returns more than n if there are ties at the boundary — handy for top-N queries.
Compute “the third-largest fruit id”: SELECT id FROM fruits ORDER BY id DESC LIMIT 1 OFFSET 2;. Then try OFFSET 0, OFFSET 1, etc. Then rewrite as keyset pagination: SELECT id FROM fruits WHERE id < (some_value) ORDER BY id DESC LIMIT 1.
Downloads
SQL file: limit-offset.sql · All lesson files · Setup guide