intermediate

Aggregation pipeline

Use pipeline stages to filter, reshape, group, join, and compute data without moving all work into application code.

The aggregation pipeline processes documents through ordered stages — like a server-side dataflow. Use it to filter, reshape, group, join, and compute analytics close to data instead of shipping large result sets to the app.

					db.orders.aggregate([
  { $match: { status: "paid", createdAt: { $gte: ISODate("2026-06-01") } } },
  { $unwind: "$items" },
  { $group: {
      _id: "$items.sku",
      revenue: { $sum: { $multiply: ["$items.qty", "$items.price"] } },
      orders: { $addToSet: "$_id" }
  }},
  { $project: { sku: "$_id", revenue: 1, orderCount: { $size: "$orders" } } },
  { $sort: { revenue: -1 } },
  { $limit: 10 }
])
				

| Stage | Role | |-------|------| | `$match` | Filter early; should use indexes | | `$group` / `$bucket` | Aggregations and histograms | | `$lookup` | Left-outer join to another collection | | `$facet` | Multiple sub-pipelines in one round trip | | `$set` / `$project` | Shape output fields |

`$match` and `$sort` early reduce working set size. `$lookup` is powerful but expensive at scale — prefer embedding or targeted pre-aggregation when possible. Memory limits apply unless `allowDiskUse` is enabled.

On interviews: sketch stages for a reporting question and say where indexes must support `$match`.

Common pitfalls: `$lookup` on unindexed foreign fields; `$unwind` exploding cardinality before `$group`; running aggregation without `explain`; moving trivial filters to the application after fetching too many docs.

The trade-off is server-side compute and pipeline complexity versus network and application memory — the pipeline wins when it eliminates large transfers and duplicate business logic.

Checklist:

  • Put selective `$match` first.
  • Name stages for filter, join, group, and project.
  • Check index support on matched fields.
  • Estimate cardinality after `$unwind` and `$lookup`.