iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

$sort / $limit

$sort in the aggregation pipeline. Index-friendly placement, multi-key sorting, and the difference between sort-on-disk vs in-memory.

MongoDB — $sort stage

EXAMPLE
// ===== Basic =====
db.orders.aggregate([
  { $sort: { created_at: -1 } }
]);
// -1 = descending, 1 = ascending

// ===== Multi-key =====
db.orders.aggregate([
  { $sort: { status: 1, total: -1 } }
]);
// First by status ascending, then by total descending (within same status).

// ===== Placement matters =====
// Put $sort EARLY when possible so it can use an index:
db.orders.aggregate([
  { $match: { customer_id: 'c-1' } },
  { $sort: { created_at: -1 } },        // can use index on { customer_id, created_at }
  { $limit: 20 }
]);

// Put $sort AFTER $project / $group when you sort on a computed field:
db.orders.aggregate([
  { $group: { _id: '$customer_id', total: { $sum: '$total' } } },
  { $sort: { total: -1 } },
  { $limit: 10 }
]);

// ===== Memory limit =====
// $sort uses 100MB of memory by default; beyond that:
db.orders.aggregate([
  { $sort: { foo: 1 } }
], { allowDiskUse: true });   // spill to disk if needed (slower)

// In MongoDB 6.0+, allowDiskUse defaults to true at the server level. Verify your setting.

// ===== Sort + limit optimisation =====
// $sort + $limit is special-cased: the server only needs to track the top N.
// Place $limit right after $sort to enable it:
{ $sort: ... }, { $limit: 10 }    // optimised top-N
{ $sort: ... }, { $skip: 0 }, { $limit: 10 }   // also optimised
{ $sort: ... }, { $project: ... }, { $limit: 10 }   // STILL N sort all, then limit (worse)

// ===== Stable sort =====
// Mongo sort is NOT stable across equal keys; add a tiebreaker:
{ $sort: { created_at: -1, _id: -1 } }

// ===== Sort by metadata (text search) =====
db.posts.aggregate([
  { $match: { $text: { $search: 'mongo' } } },
  { $sort: { score: { $meta: 'textScore' } } }
]);

// ===== Index for sort =====
// Sort key MUST match a prefix of an index for the planner to use it without sorting.
db.orders.createIndex({ customer_id: 1, created_at: -1 });

// Then this query is index-supported:
db.orders.find({ customer_id: 'c-1' }).sort({ created_at: -1 }).limit(20);
// And the equivalent aggregate.

// ===== Patterns to internalise =====
// - $sort right after $match for index use
// - $limit immediately after $sort for top-N optimisation
// - Tiebreaker on _id for stability
// - Composite indexes whose suffix matches the sort direction(s)

// ===== Pitfalls =====
// - Sorting without an index on large collections -> in-memory + 100MB limit
// - allowDiskUse on hot queries -> very slow
// - Sort key with mixed asc/desc that breaks index order
// - Forgetting that text-search sort requires the meta { textScore }

Why it matters

$sort is index-friendly when it directly follows $match and matches an index prefix. Pair with $limit for top-N optimisation, add a tiebreaker on _id for stability, and watch memory limits on large pipelines. Place it early when possible, late when sorting on computed fields.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
{ $sort: { total: -1 } },
{ $limit: 10 }
Try it Yourself »

Discussion

Loading…