MongoDBSorting Deep Dive

Sorting

sort() orders the documents a query returns. It looks simple, but two things separate a fast sort from a slow, memory-hungry one: whether an index already provides the order you asked for, and whether you need locale-aware ordering via collation.

Single-Field Sort

Ascending and descending

JS
db.products.find().sort({ price: 1 })    // ascending — 1
db.products.find().sort({ price: -1 })   // descending — -1
Multi-Field Sort

List multiple fields in the sort document and MongoDB breaks ties left to right — the first field is the primary sort key, the second breaks ties within it, and so on.

Sort by category, then price descending within each category

JS
db.products.find().sort({ category: 1, price: -1 })
Why Sort Can Be "Free" With an Index

A B-tree index already stores keys in sorted order. If the sort fields match an index's key order (or its exact reverse), MongoDB walks the index directly and never has to sort in memory at all — the results simply come back in order.

Index-supported sort

JS
db.products.createIndex({ category: 1, price: -1 })

// This sort matches the index exactly — no in-memory sort stage needed
db.products.find({ category: "widgets" }).sort({ category: 1, price: -1 })

// MongoDB can also walk the SAME index backwards for the fully-reversed order
db.products.find({ category: "widgets" }).sort({ category: -1, price: 1 })
Tip
Run .explain("executionStats") and check the winning plan. If there's a SORT stage above the IXSCAN, the index didn't cover the sort and MongoDB sorted in memory. If the plan goes straight from IXSCAN to results with no separate SORT stage, the sort was free.
The 32 MB In-Memory Sort Limit

When MongoDB can't use an index to satisfy a sort, it sorts in memory — and that in-memory sort buffer is capped at 32 MB. Exceed it and the query fails outright, unless you explicitly allow spilling to disk.

Error without allowDiskUse

JS
db.hugeCollection.find().sort({ randomField: 1 })
// Error: Sort exceeded memory limit of 33554432 bytes, but did not opt in
// to external sorting.

allowDiskUse — spill the sort to disk

JS
db.hugeCollection.aggregate(
  [{ $sort: { randomField: 1 } }],
  { allowDiskUse: true }
)
Warning
allowDiskUse is only available on the aggregation pipeline's $sort stage, not on a plain find().sort() cursor. If a query-level sort hits the 32 MB limit, either add a supporting index or rewrite the query as an aggregation pipeline with allowDiskUse: true.
Natural Order — a Caveat

.sort({ $natural: 1 }) returns documents in the order they exist on disk, which usually — but is not guaranteed to — resemble insertion order.

Natural order

JS
db.logs.find().sort({ $natural: -1 }).limit(10)   // "roughly most recent first"
Warning
Natural order is an implementation detail of the storage engine, not a guaranteed contract. Documents that are updated in place, moved during compaction, or affected by replication can end up out of strict insertion order. Never rely on $natural where correctness depends on ordering — use an explicit _id or timestamp field sort instead.
Collation for Case-Insensitive / Locale-Aware Sort

By default, string comparison (and therefore sorting) is binary — based on UTF-8 byte order. That means uppercase letters sort before all lowercase letters ("Banana" sorts before "apple"), which usually isn't what a user expects. A collation tells MongoDB to use locale-aware comparison rules instead.

Case-insensitive sort with collation

JS
db.products.find().sort({ name: 1 }).collation({ locale: "en", strength: 2 })
// strength: 2 = case-insensitive comparison (also ignores some accent distinctions)
// Without collation: "Banana", "Zebra", "apple", "banana"
// With collation:     "apple", "Banana", "banana", "Zebra"
Note
Collation affects sorting, equality, and range comparisons alike. If you regularly sort or query a field case-insensitively, create the supporting index with the same collation (db.collection.createIndex({ name: 1 }, { collation: { locale: "en", strength: 2 } })) — otherwise the query still can't use the index for that comparison.
  • A sort that matches an index's key order (forwards or exactly reversed) runs with no extra memory cost.

  • A sort without a supporting index buffers in memory, capped at 32 MB — spill to disk with allowDiskUse: true in an aggregation pipeline.

  • $natural reflects disk order, which is an implementation detail — never rely on it for correctness.

  • Use .collation() for locale-aware or case-insensitive sorting, and index with the matching collation to keep it fast.