The Seek Method paginates by using a stable, indexed column value (like id or created_at) from the last row of the previous page, instead of counting and skipping rows like OFFSET does.
🧠 Core Idea
Instead of saying:
-- Offset method: skips rows
LIMIT 10 OFFSET 20;
You say:
-- Seek method: starts "after" a known value
WHERE created_at < '2024-01-01 10:00:00'
ORDER BY created_at DESC
LIMIT 10;
🧭 You’re saying: “Give me the next 10 posts older than this timestamp.”
✅ Example
Table: posts
| id | content | created_at |
|---|---|---|
| 105 | Hello world | 2024-06-10 10:00:00 |
| 104 | Post 2 | 2024-06-10 09:55:00 |
| 103 | Post 3 | 2024-06-10 09:45:00 |
| … | … | … |
First Page:
SELECT * FROM posts
ORDER BY created_at DESC
LIMIT 10;
You get the latest 10 posts. The last post’s created_at is 2024-06-10 09:00:00.
Next Page (Seek):
SELECT * FROM posts
WHERE created_at < '2024-06-10 09:00:00'
ORDER BY created_at DESC
LIMIT 10;
→ Returns the next 10 posts older than that date.
Repeat by tracking the last created_at on each page.
✅ Advantages
| Benefit | Why it Matters |
|---|---|
| 🔄 Consistent results | No shifting from inserts/deletes on other rows |
| ⚡ Very fast | Especially for deep pages (no OFFSET scan) |
| 💪 Works with indexes | Optimized for indexed columns |
⚠️ Limitations
| Drawback | Description |
|---|---|
| ❌ No page numbers | You can’t jump directly to page 10 |
| ❌ Need state | The client must remember the last seen value |
🧰 Pro Tips
- Use unique values in
ORDER BY, likecreated_at, id, to avoid duplicates:
ORDER BY created_at DESC, id DESC
WHERE (created_at, id) < (?, ?)
ORDER BY created_at DESC, id DESC
WHERE (created_at, id) < (?, ?)
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
🔁 Summary
| Feature | OFFSET | Seek Method (Keyset) |
|---|---|---|
| Speed | Slower on deep pages | Fast even for page 1000 |
| Result Stability | Can shift on inserts | Stable and consistent |
| Page jumping | ✅ Yes | ❌ No (next/prev only) |
| Requires bookmark? | ❌ No | ✅ Yes (last seen value) |