Laravel Offset Pagination vs Cursor Pagination: When to Use What (With Real-World Examples)

Laravel gives us two main strategies for paginating large datasets:
- Offset Pagination (
paginate()) - Cursor Pagination (
cursorPaginate())
Both work beautifully — but they solve different problems. Picking the wrong one can lead to slow queries, inconsistent results, and a page 2 that sometimes returns items from page 1 (yikes).
Let’s break this down the practical way.
1. Offset Pagination (paginate)
This is the classic pagination style:
$users = User::paginate(20);
It runs a query like:
SELECT * FROM users ORDER BY id LIMIT 20 OFFSET 40;
When Offset Pagination Is a Good Fit
- Small or medium datasets
- Admins dashboards where precision > performance
- Pagination where users jump to a specific page (page 10, page 50…)
- Filtering + sorting + searching
When Offset Falls Apart
Offset pagination is slow when:
- The table has millions of rows
- There is no good index for the sort column
- The user scrolls deep into the list (OFFSET 400k = painful)
Also: if new data is inserted while the user paginates, pages may shift.
Page 2 might show items from page 1 again because rows moved.
Real-World Example
Admin panel showing customers
You want to allow:
- Sorting by name
- Jumping to page numbers
- Filtering by status
- Going back and forth predictably
Offset pagination handles all of this.
Example:
$customers = Customer::orderBy('created_at', 'desc')->paginate(50);
Users want precision and navigation, not infinite-scroll speed, so offset works perfectly.
2. Cursor Pagination (cursorPaginate)
Cursor pagination doesn’t use OFFSET. Instead, it remembers the last item’s position and fetches from there:
$posts = Post::orderBy('id')->cursorPaginate(20);
This generates queries like:
SELECT * FROM posts WHERE id > last_seen_id ORDER BY id LIMIT 20;
When Cursor Pagination Shines
- Huge datasets (millions+ rows)
- Infinite scrolling (social feeds)
- Real-time data (chat messages, logs)
- When new data is constantly inserted
- When avoiding duplicates is important
Limitations
Cursor pagination does not support:
- Jumping to page numbers
- Sorting by arbitrary columns (must be unique and indexed)
- Some complex filtering scenarios
Real-World Example
Activity Feed / Timeline (e.g., Facebook-style feed)
You want:
- Infinite scroll
- New posts arriving constantly
- No repeated or missing posts
Cursor pagination guarantees consistency.
Example:
$activities = Activity::orderBy('id', 'desc')->cursorPaginate(20);
Even if 100 new activities are added while the user scrolls, the feed does not reshuffle or repeat.
3. Which One to Use? (Simple Rule)
Use Offset Pagination when:
- You need traditional pagination with page numbers
- You need flexible sorting or filtering
- Dataset is manageable (< few hundred thousand rows)
- Admin dashboards, reports, tables
Use Cursor Pagination when:
- You’re dealing with huge datasets
- You want infinite scrolling
- Data changes frequently
- You can sort by a unique, indexed column (usually
idor timestamps)
Quick Code Comparison
Offset Pagination
$logs = Log::orderBy('created_at', 'desc')->paginate(50);
Cursor Pagination
$logs = Log::orderBy('created_at', 'desc')->cursorPaginate(50);
Notice the key difference:
Cursor pagination requires the sorted column to be:
- Indexed
- Unique
- Stable
Offset pagination doesn’t.
Conclusion
Both methods are great — but they're built for different jobs.
- If you're building something UI-heavy like a dashboard table → Offset Pagination
- If you're building something real-time or massive → Cursor Pagination
Use the tool that fits the problem, not the other way around.
