Results, Sorting & Pagination
Offset Pagination
page × pageSize with skip and limit, and the point at which it starts to hurt.
IntermediateAbout 5 minutes
Offset pagination is what every admin table expects: ?page=3&pageSize=10. You skip((page - 1) * pageSize).limit(pageSize) on a sorted cursor. It is simple. It is correct enough for small data. It is the wrong default for a public feed of millions of orders.
const page = 3;
const pageSize = 10;
db.products
.find({ inStock: true })
.sort({ price: 1, _id: 1 })
.skip((page - 1) * pageSize)
.limit(pageSize)const filter = { inStock: true };
const [items, total] = await Promise.all([
products.find(filter).sort({ price: 1, _id: 1 }).skip(20).limit(10).toArray(),
products.countDocuments(filter),
]);- Good for — internal tools, a few thousand rows, jumping to page 12.
- Hurts when —
skipis large (walks every prior document), or rows insert/delete while someone pages (page 2 duplicates or skips a product). - Count —
countDocumentson the same filter is another full pass. Many UIs drop 'of N' and only show Next.
Interview question
When is skip/limit pagination acceptable, and when is it not?
Think about it first.
Acceptable for small, slow-changing lists — an admin of a few thousand products. Not for deep pages or hot feeds: skip cost grows with offset, and inserts make rows jump between pages. Then I use a cursor.