Think of OFFSET like telling a waiter, “skip the first few plates and bring me the next ones.” Interviewers love it because it looks simple, but it hides a big paging and performance trap.
Question: What does OFFSET do in SQL?
Answer: OFFSET skips a number of rows in the result set and then returns the rows after that. It is usually paired with ORDER BY and often with LIMIT or FETCH to build pages of results. The important idea is that it does not delete rows from the table; it only changes which rows appear in the query result.
Interview-Ready Answer: “OFFSET skips the first N rows of an ordered result set, so it’s commonly used for pagination. I always pair it with ORDER BY because without a stable sort, the skipped rows can change between runs. One practical detail is that large offsets can get slow because the database may still have to read and discard many rows.”
OFFSET is a clause that tells SQL how many rows to ignore before starting to return rows. You can think of it as slicing a list after it has been sorted. The most important technical word here is deterministic, which means “always producing the same result for the same inputs.” To make paging deterministic, you need a stable ORDER BY.
FROM, WHERE, joins, and any grouping.ORDER BY, it sorts those rows into a defined order.OFFSET N tells the engine to skip the first N rows in that ordered stream.LIMIT or FETCH NEXT, the engine returns only the next chunk after the skipped rows.OFFSET is a classic pagination tool, but it has two common traps: unstable ordering and poor performance on deep pages. For example, page 1 may use OFFSET 0, page 2 uses OFFSET 20, and page 500 uses OFFSET 9980. The deeper you go, the more rows the database may need to walk past just to reach your page.
When a page number is small, OFFSET is simple and readable. When users jump far into a large dataset, a different approach called keyset pagination (also called “seek pagination”) is usually faster. Keyset pagination uses the last seen sort key, such as WHERE id > 5000, instead of counting and skipping many rows.
| Approach | How it works | Strength | Weakness |
|---|---|---|---|
| OFFSET | Skip N rows | Simple page numbers | Slow on deep pages |
| Keyset | Start after last key | Fast at scale | Harder to jump to page 50 |
Cost depends on the plan. If the database can use an index that matches the ORDER BY, it may walk the index and discard rows until it reaches the offset. That is still roughly proportional to the offset, so OFFSET 100000 can be much slower than OFFSET 20. If the query must sort first, the sort itself can dominate, often behaving like O(n log n) for the sort plus extra work to skip rows.
In practical terms, a page size of 20 or 50 is common in apps. A deep offset like 100,000 can mean scanning or walking past a huge number of rows just to show a tiny page. That is why many teams switch to keyset pagination for feeds, logs, timelines, and large admin grids.
ORDER BY: the database may return rows in any physical order, so page 2 can overlap page 1 or miss rows.Memory hook: “Sort first, then skip.” If you remember that one line, you’ll avoid most OFFSET mistakes.
Imagine a checkout service that has an admin screen listing recent orders. Support agents page through orders 25 at a time using OFFSET and LIMIT, sorted by created_at DESC. This works well when the list is short and the order is stable.
What goes wrong: an engineer forgets the ORDER BY, or sorts only by a non-unique column like created_at when many orders share the same timestamp. At peak traffic, new orders arrive while agents are paging. Suddenly page 2 shows duplicates from page 1, some orders seem to vanish, and support thinks the database is “losing” records. In logs, nothing is obviously broken; the bug is logical, not a crash. The fix is to use a stable ordering such as created_at DESC, id DESC, and for large datasets, consider keyset pagination instead of deep offsets.
-- PostgreSQL / MySQL-style example demonstrating OFFSET pagination.
-- The key lesson: ORDER BY must come first for predictable paging.
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
id INT PRIMARY KEY,
customer VARCHAR(50) NOT NULL,
created_at DATE NOT NULL,
total DECIMAL(10,2) NOT NULL
);
INSERT INTO orders (id, customer, created_at, total) VALUES
(1, 'Ava', '2024-01-01', 19.99),
(2, 'Ben', '2024-01-02', 42.50),
(3, 'Cleo', '2024-01-03', 11.00),
(4, 'Dan', '2024-01-04', 77.25),
(5, 'Eli', '2024-01-05', 15.75),
(6, 'Finn', '2024-01-06', 88.10),
(7, 'Gita', '2024-01-07', 24.00),
(8, 'Hana', '2024-01-08', 31.40);
-- Page 1: skip 0 rows, return the first 3 rows in a stable order.
SELECT id, customer, created_at, total
FROM orders
ORDER BY created_at, id
LIMIT 3 OFFSET 0;
-- Page 2: skip the first 3 rows, then return the next 3.
SELECT id, customer, created_at, total
FROM orders
ORDER BY created_at, id
LIMIT 3 OFFSET 3;
-- Edge case: offset beyond the available rows returns an empty result set.
-- This is not an error; it just means there are no rows left to show.
SELECT id, customer, created_at, total
FROM orders
ORDER BY created_at, id
LIMIT 3 OFFSET 100;
-- Common failure pattern: this query is legal in many databases,
-- but the ordering is not stable enough for paging if timestamps tie.
-- In real systems, add a unique tiebreaker like id.
SELECT id, customer, created_at, total
FROM orders
ORDER BY created_at DESC, id DESC
LIMIT 3 OFFSET 3;Follow-up & Tricky Questions:
OFFSET always be paired with ORDER BY?OFFSET zero-based or one-based?OFFSET 0 means “skip nothing.” Page 1 usually maps to offset 0, not 1.OFFSET and LIMIT?OFFSET decides where the page starts; LIMIT decides how many rows to return. They usually work together to express “skip some, then take some.”Tricky / Gotcha Questions:
OFFSET delete or hide rows in the table?OFFSET without ORDER BY?Common Mistakes:
OFFSET without ORDER BY. Correction: always sort first, and include a unique tiebreaker like id.OFFSET filters rows. Correction: it does not filter by value; it skips a number of rows after sorting.Memory Hook: “Sort, then skip.” Imagine a librarian lining up books alphabetically, then telling you to ignore the first 100 before handing you the next 20.
Cheat Sheet:
OFFSET skips rows in the result set.ORDER BY and LIMIT/FETCH.OFFSET 0 means no rows skipped.Practice Tasks:
created_at and id.id instead of OFFSET.