Chapter 16: MongoDB Aggregation $sort
1. What does $sort really do? (Teacher explanation — crystal clear)
$sort reorders the documents that reach this stage according to one or more fields — ascending or descending.
- It works exactly like .sort() in find(), but inside the aggregation pipeline.
- It can sort on any field (numbers, dates, strings, even nested fields).
- It can sort on multiple fields at the same time (primary, secondary, tertiary sort keys).
- Very important: $sort happens in memory (or uses disk if too big), so order matters a lot for performance.
Basic syntax (very simple):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
{ $sort: { field1: 1 | -1, // 1 = ascending (A→Z, small→big, old→new) field2: 1 | -1, // -1 = descending (Z→A, big→small, new→old) "nested.field": 1 | -1 } } |
2. Ascending vs Descending — Quick Reminder Table
| Value | Meaning | Typical use case examples |
|---|---|---|
| 1 | Ascending (small → big) | Oldest first, A–Z names, lowest price first |
| -1 | Descending (big → small) | Newest first, highest rating first, Z–A names, most expensive first |
3. Hands-on Examples — Using Our Familiar movieAnalytics2026 Database
Assume we have movies like before (RRR, Kalki, Pushpa 2, Oppenheimer…).
|
0 1 2 3 4 5 6 |
use movieAnalytics2026 |
Example 1: Simplest — Sort all movies by rating descending
|
0 1 2 3 4 5 6 7 8 9 10 11 |
db.movies.aggregate([ { $sort: { rating: -1 } }, { $project: { title: 1, rating: 1, year: 1, _id: 0 } }, { $limit: 5 } // optional — just show top 5 ]) |
→ Pushpa 2 (8.5) comes first, then Oppenheimer (8.4), etc.
Example 2: Multi-field sort (very common in real apps)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
db.movies.aggregate([ // Primary: highest rating first // If ratings equal → newest year first { $sort: { rating: -1, year: -1 } }, { $project: { title: 1, rating: 1, year: 1, _id: 0 } } ]) |
→ If two movies have same rating → the one with bigger (newer) year comes first.
Example 3: After $group — sort groups by total value
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
db.movies.aggregate([ { $unwind: "$genres" }, { $group: { _id: "$genres", count: { $sum: 1 }, avgRating: { $avg: "$rating" }, totalRevenue: { $sum: "$revenue" } } }, // Sort genres by most movies first { $sort: { count: -1 } }, { $project: { genre: "$_id", moviesInGenre: "$count", averageRating: { $round: ["$avgRating", 1] }, totalBoxOfficeCr: "$totalRevenue", _id: 0 }}, { $limit: 4 } // top 4 most common genres ]) |
Example 4: Sort by computed / nested field
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
db.movies.aggregate([ { $addFields: { profitEstimate: { $subtract: ["$revenue", 300] } // dummy profit } }, { $sort: { profitEstimate: -1 } }, // highest profit first { $project: { title: 1, profitEstimate: 1, _id: 0 } } ]) |
Example 5: Alphabetical sort (strings)
|
0 1 2 3 4 5 6 7 8 9 |
db.movies.aggregate([ { $sort: { title: 1 } }, // A → Z { $project: { title: 1, _id: 0 } } ]) |
→ Kalki 2898 AD → Oppenheimer → Pushpa 2 → RRR
4. Very Important Performance & Behavior Rules (Listen carefully!)
| Rule / Gotcha | Explanation | Best Practice / Fix |
|---|---|---|
| $sort before $limit | MongoDB can optimize: only keeps top N in memory during sort | Always put $sort → $limit together when possible |
| Large $sort without index | Can spill to disk → very slow (and may hit 100 MB limit in older versions) | Add index on sort field(s) before pipeline |
| $sort early in pipeline | Usually bad — sorts huge amount of data unnecessarily | Filter with $match first, then sort |
| Sorting on text fields (case sensitivity) | Default is case-sensitive (A < a) | Use collation { locale: “en”, strength: 2 } for case-insensitive |
| Sorting null / missing fields | Nulls come first in ascending, last in descending | Use $ifNull or $exists to handle missing values |
| Max memory per sort stage | 100 MB by default (can be increased with allowDiskUse: true) | Add { allowDiskUse: true } to aggregate() options |
How to allow disk use (very useful for big data):
|
0 1 2 3 4 5 6 |
db.movies.aggregate([...pipeline...], { allowDiskUse: true }) |
5. Quick Cheat Sheet Table — Your $sort Reference
| Goal | $sort syntax | Typical position in pipeline | Pro tip |
|---|---|---|---|
| Highest score / rating first | { rating: -1 } | After $group or $project | Pair with $limit |
| Newest first | { createdAt: -1 } or { year: -1 } | After $match | Common for feeds, logs |
| Alphabetical A–Z | { name: 1 } | End of pipeline | Add collation for case-insensitive |
| Multi-level (primary + secondary) | { score: -1, age: 1 } | After $group | Breaks ties nicely |
| Sort by computed field | First $addFields, then sort on it | — | Use $addFields or $project before sort |
| Top 10 per group | $sort inside $group with $first/etc. | Advanced pattern | Look up “top N per group” pattern next |
6. Mini Exercise — Try Right Now in mongosh!
- Show top 3 highest rated movies (sort + limit + project)
- Sort movies by year ascending, then by rating descending inside same year
- After grouping by country — sort countries by total revenue descending
- Sort titles alphabetically (A–Z) — then reverse it (Z–A)
Understood beta? $sort looks trivial… but when combined with $group, $limit, $match — it turns raw data into rankings, leaderboards, timelines, dashboards — the stuff users actually love to see.
Next class — what do you want?
- The famous “top N per group” pattern (very common interview + real-world question)?
- $skip + $sort + $limit for perfect pagination?
- Collation for case-insensitive / language-aware sorting?
- Or start combining all stages into a full “Valentine Movie Ranking Report”?
Tell me — class is still full of energy! 🚀❤️
Any confusion with $sort? Ask freely — we’ll sort it out together 😄
