← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Pagination Strategies

Performance PHP 5.0+ Beginner
debt(d7/e3/b5/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints list laravel-debugbar, mysql-slow-query-log, and clockwork — these are profiling/observability tools, not default linters. OFFSET misuse is only visible under load; page 1 looks identical to page 1000 in normal development testing. You need slow-query logs or a debugbar showing query time to catch it, and only when exercising deep pages.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix is a targeted SQL pattern swap — replace LIMIT x OFFSET y with WHERE id > :last_id LIMIT x. This is a localised change per query/repository method, but may require updating API contracts (removing page-number parameters, adding cursor tokens) and adjusting frontend pagination controls, making it slightly more than a one-liner but still within one component.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). applies_to covers web and api contexts broadly. Once OFFSET pagination is baked into an API contract and frontend components, every list endpoint carries the performance debt and any migration to keyset pagination must be coordinated between backend query logic, API response shape, and client-side navigation UI — a persistent tax across multiple work streams without being fully architectural.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7). The misconception is explicit: developers assume LIMIT/OFFSET is efficient because page 1 is fast. The O(N) scan behaviour at depth contradicts the expectation that later pages cost the same as earlier ones. This is a documented gotcha that contradicts how developers reason about indexed queries, scoring t7 — it doesn't reach t9 because keyset pagination is well-documented and the failure mode is performance degradation rather than silent correctness bugs.

About DEBT scoring →

Also Known As

SQL pagination LIMIT OFFSET keyset pagination cursor pagination

TL;DR

Techniques for splitting large result sets across pages to avoid loading unbounded data in a single query.

Explanation

Offset pagination (LIMIT x OFFSET y) is simple but degrades at large offsets — the database must scan and discard all preceding rows. Keyset (cursor) pagination (WHERE id > :last_id ORDER BY id LIMIT x) is O(log n) via the index regardless of page depth, making it suitable for deep pagination and infinite scroll. For PHP applications with large datasets, keyset pagination is the default recommendation. Total count queries for offset pagination are expensive — consider approximate counts or omitting page counts for large datasets.

Common Misconception

LIMIT/OFFSET pagination is efficient for all pages. OFFSET N scans and discards the first N rows on every query — page 1000 of 20 items per page scans 20,000 rows. Keyset (cursor) pagination using WHERE id > last_seen_id is O(1) regardless of page depth.

Why It Matters

Loading all records at once is a common performance anti-pattern — pagination limits result sets to manageable chunks, keeping memory usage flat and response times consistent regardless of total data size.

Common Mistakes

  • OFFSET-based pagination on large datasets — OFFSET 100000 still scans 100K rows before returning results.
  • Not using cursor-based (keyset) pagination for high-volume feeds — use WHERE id > :last_id LIMIT 20.
  • No total count query — either skip it (infinite scroll) or cache it; running COUNT(*) on every page is expensive.
  • Allowing unlimited page sizes via API — ?per_page=999999 loads the entire table.

Avoid When

  • Offset pagination on large tables — OFFSET 10000 scans and discards 10,000 rows; use keyset pagination instead.
  • Paginating tiny result sets — if the total is always under 20 rows, returning all at once is simpler.
  • Using page numbers in APIs that other services consume — page numbers are unstable when data is inserted or deleted between requests.

When To Use

  • Any list endpoint that can return more than ~50 items — return all and you risk memory exhaustion and slow responses.
  • Cursor or keyset pagination for feeds and timelines where consistent ordering and stability matter.
  • User-facing tables and search results where navigation by page number is expected.
  • Infinite scroll UIs where the next page is loaded as the user approaches the bottom.

Code Examples

✗ Vulnerable
// OFFSET pagination — slow on large tables (reads all preceding rows)
$page = (int) $_GET['page'];
$rows = $db->query('SELECT * FROM posts ORDER BY id DESC LIMIT 20 OFFSET ' . ($page * 20));
✓ Fixed
// Cursor/keyset pagination — constant time regardless of page depth
$cursor = (int) ($_GET['cursor'] ?? PHP_INT_MAX);
$rows   = $db->query(
    'SELECT * FROM posts WHERE id < ? ORDER BY id DESC LIMIT 21',
    [$cursor]
);
$hasMore = count($rows) === 21;
$items   = array_slice($rows, 0, 20);
$nextCursor = $hasMore ? end($items)->id : null;

Added 15 Mar 2026
Edited 25 Mar 2026
Views 68
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 1 ping S 1 ping M 0 pings T 0 pings W 1 ping T 0 pings F 1 ping S 2 pings S 0 pings M 0 pings T 3 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 1 ping S 0 pings M 1 ping T 0 pings W
No pings yet today
PetalBot 1
Amazonbot 15 Ahrefs 8 Perplexity 7 Google 5 Scrapy 5 Unknown AI 3 SEMrush 3 Claude 2 ChatGPT 2 Majestic 1 Meta AI 1 PetalBot 1
crawler 47 crawler_json 4 pre-tracking 2
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Medium
⚡ Quick Fix
Use keyset/cursor pagination (WHERE id > :last_id LIMIT 20) instead of OFFSET for large datasets — OFFSET scans and discards rows, keyset uses the index directly
📦 Applies To
PHP 5.0+ web api laravel symfony
🔗 Prerequisites
🔍 Detection Hints
OFFSET pagination on large tables: LIMIT 20 OFFSET 10000 — full scan to row 10020 then discard 10000
Auto-detectable: ✓ Yes laravel-debugbar mysql-slow-query-log clockwork
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Medium Context: File Tests: Update


✓ schema.org compliant