Sorting, limiting and paging

MongoDB · Essentials · 8 min

Lists, feeds and tables all need ordered, paged results that stay fast.

Cursor methods shape a result: sort({ createdAt: -1 }) orders it (1 ascending, -1 descending; several keys break ties in order), limit(n) caps the count and skip(n) jumps ahead. The server always applies them as sort, then skip, then limit, whatever order you chain them in.

Without sort, result order is not guaranteed. It often looks like insertion order but can change after updates or with a different plan, so always sort when order matters, and include a unique field such as _id as a final tie-breaker so pages don't overlap.

skip paging is easy but gets slower as pages go deeper: to return page 500 the server still walks past every skipped document. For feeds and infinite scroll use range (keyset) paging: remember the last value seen and ask for the next ones, e.g. find({ _id: { $lt: lastId } }).sort({ _id: -1 }).limit(20). With an index on the sort key that is fast at any depth.

Counting: countDocuments(filter) runs an accurate count of matching documents; estimatedDocumentCount() reads collection metadata and is instant but ignores filters. The old cursor.count() is deprecated.

Sorting a large result without a supporting index is done in memory; the next module shows how indexes make sorts cheap.

Example

for (let i = 1; i <= 7; i++) db.posts.insertOne({ _id: i, likes: i % 3 })
db.posts.find({}, { likes: 1 }).sort({ likes: -1, _id: 1 }).limit(3)
// [ { _id: 2, likes: 2 }, { _id: 5, likes: 2 }, { _id: 1, likes: 1 } ]

// page 2 with skip (fine for small offsets)
db.posts.find().sort({ _id: 1 }).skip(3).limit(3).map(d => d._id)   // [ 4, 5, 6 ]

// keyset paging: continue after the last _id seen
const lastId = 3
db.posts.find({ _id: { $gt: lastId } }).sort({ _id: 1 }).limit(3).map(d => d._id)
// [ 4, 5, 6 ]
db.posts.countDocuments({ likes: { $gt: 0 } })   // 5

Remember

  • Without sort the order is undefined; add _id as a tie-breaker
  • sort, skip, limit are applied in that order regardless of chaining
  • Deep skip is slow; range paging on an indexed key is not
  • countDocuments for accurate counts, estimatedDocumentCount for a fast total

Watch out for

  • Relying on natural (insertion) order for pagination without a sort.

Check yourself

  1. You write .limit(5).sort({ price: 1 }). What do you get?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.mongodb.com/docs/manual/reference/method/cursor.sort/