How to think about an aggregation pipeline
Most people write pipelines by trial and error. These six questions decide the stages before you type anything - answer them in order and the pipeline usually falls out. The patterns after them are the shapes that recur often enough to recognise on sight.
For almost every question, ask these in order:
Question 1: Which documents do I need?
$match
Example:
Only completed orders from 2026.
Question 2: Is the data I need inside an array?
$unwind
Example:
Analyze products inside
items.
Question 3: Do I need to combine multiple documents?
$group
Example:
Revenue per product.
Question 4: Do I need calculated fields?
$project
or
$set
or expressions inside $group
Example:
price × quantity
Question 5: How should the result look?
$project
Example:
Instead of:
{
_id: "Laptop",
revenue: 2000
}
You might want:
{
product: "Laptop",
revenue: 2000
}
Question 6: Do I need ranking/pagination?
$sort
$skip
$limit
Rapid pattern recognition
Worth being able to map on sight:
| Problem | Think |
|---|---|
| Filter documents | $match |
| Keep/rename/calculate fields | $project |
| Add a calculated field | $set |
| Analyze elements inside an array | $unwind |
| Count/sum/average by something | $group |
| Order results | $sort |
| Top N | $sort → $limit |
| Pagination | $sort → $skip → $limit |
| One overall total | $group: { _id: null } |
The five patterns worth memorising
Pattern 1 — Count by X
[
{
$group: {
_id: "$someField",
count: { $sum: 1 }
}
}
]
Pattern 2 — Sum by X
[
{
$group: {
_id: "$someField",
total: {
$sum: "$amount"
}
}
}
]
Pattern 3 — Array analytics
[
{
$unwind: "$items"
},
{
$group: {
_id: "$items.product",
count: { $sum: 1 }
}
}
]
Pattern 4 — Top N
[
{
$group: {
_id: "$category",
total: {
$sum: "$amount"
}
}
},
{
$sort: {
total: -1
}
},
{
$limit: 5
}
]
Pattern 5 — Filter → Transform → Group → Sort
This is probably the most common pattern:
$match
↓
$unwind / $project / $set
↓
$group
↓
$project
↓
$sort
↓
$limit
Try it
All six questions, in order, in one pipeline - revenue per product from completed orders:
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $unwind: "$items" },
{
$group: {
_id: "$items.product",
revenue: {
$sum: {
$multiply: ["$items.price", "$items.quantity"]
}
}
}
},
{
$project: {
_id: 0,
product: "$_id",
revenue: 1
}
},
{ $sort: { revenue: -1 } },
{ $limit: 5 }
])
Delete stages from the bottom up and run it after each one. You are watching the six questions get answered in reverse, and it is the fastest way to learn to read someone else’s pipeline.