Offset Pagination vs Cursor Pagination in MongoDB
Two ways to paginate in MongoDB. Here is when to use each.
Offset Pagination vs Cursor Pagination in MongoDB
Two ways to paginate in MongoDB: offset (skip and limit) and cursor. Here is when to use each.
Offset Pagination (skip and limit)
const skip = parseInt(req.query.skip) || 0;
const limit = Math.min(parseInt(req.query.limit) || 20, 100);
const items = await User.find().skip(skip).limit(limit).lean();
const total = await User.countDocuments();
res.json({ data: items, total, skip, limit });
Pros:
- Simple.
- Client can jump to any page (skip=40 for page 3).
- Returns total, so the client can show page numbers.
Cons:
- Slow for large offsets. MongoDB scans the skipped docs.
- New items inserted between requests can cause duplicates or skips.
Cursor Pagination
const cursor = req.query.cursor ? new Date(req.query.cursor) : new Date();
const limit = Math.min(parseInt(req.query.limit) || 20, 100);
const items = await Message.find({ createdAt: { $lt: cursor } })
.sort({ createdAt: -1 })
.limit(limit)
.lean();
const nextCursor = items.length === limit ? items[items.length - 1].createdAt : null;
res.json({ data: items, nextCursor });
Pros:
- Fast for large offsets. MongoDB uses an index on createdAt.
- Stable. New items do not cause duplicates or skips.
Cons:
- Client cannot jump to a page. Only next and prev.
- No total (would require a separate count query).
When to Use Offset
- The collection is small (< 10000 docs).
- The client needs page numbers ("showing 1-20 of 432").
- The client needs to jump to a specific page.
When to Use Cursor
- The collection is large (> 10000 docs).
- The client only needs next and prev (infinite scroll, feed).
- Performance matters.
Indexes
Both need indexes:
- Offset: index the sort field.
- Cursor: index the cursor field (e.g., createdAt). Without an index, cursor pagination is not faster than offset.
The Takeaway
Use offset pagination (skip and limit) for small collections or when the client needs page numbers. Use cursor pagination for large collections or infinite scroll (faster, stable). Both need indexes on the sort or cursor field. Cursor cannot jump to pages; offset can.
Offset uses skip and limit; client can jump to any page; slow for large offsets. Cursor uses a cursor field (e.g., createdAt); client can only go next/prev; fast for large collections; stable when new items are inserted.
For large collections (> 10000 docs) or infinite scroll. Cursor is fast because MongoDB uses an index on the cursor field. It is stable: new items do not cause duplicates or skips between requests.
For small collections or when the client needs page numbers ('showing 1-20 of 432'). Offset is simple and lets the client jump to any page. Slow for large offsets because MongoDB scans the skipped docs.
MongoDB still scans the skipped documents before returning the ones you want. For offset 100000, it scans 100000 docs. Use cursor-based pagination instead: find documents where createdAt is less than the last cursor. MongoDB uses an index.
Yes. Both offset and cursor need an index on the sort or cursor field. Without an index, MongoDB scans the whole collection, and cursor pagination is not faster than offset. Index the field you sort by.
Ready to master Node.js completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master Node.js
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

