Interview Revision
Revision: Aggregation and $lookup
Pipeline order, $group, $unwind, $lookup — the questions that come up most.
IntermediateAbout 7 minutes
- 01Documents
- 02$match
- 03$group
- 04$sort
- 05$limit
- 06Result
- find = stored docs. aggregate = computed answer. Do not
reducein Node. - Order is meaning.
$matchonstatusbefore$group.$matchontotalSpentafter. - `$group`.
_id= key (null= one bucket). Accumulators only — no leftoverstatus.$sum: 1counts. - `$unwind`. One array element → one doc. Empty array drops the parent unless
preserveNullAndEmptyArrays. Do not$sumamountafter unwind (repeats the order total). - `$lookup`. Left join, always an array. No match =
[]. Index the foreign field.$match/$limitfirst.$unwindwithout preserve = inner join.$size: 0= anti-join. - `$project` = new shape. `$set` = patch. Expressions: operands in arrays.
$condvs$match.$map/$filterin place; unwind to group across orders. - Dates.
$dateToString%Y-%min_id, timezone if IST. Filter status before grouping.
db.orders.aggregate([
{ $match: { status: { $ne: "cancelled" } } },
{ $group: { _id: "$userId", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } },
{ $limit: 3 },
{ $lookup: { from: "users", localField: "_id", foreignField: "_id", as: "user" } },
{ $unwind: "$user" }
])Interview question
Why put $match as early as possible?
Think about it first.
Later stages see fewer documents. An early $match can use an index. After $group you only have _id and accumulators.
Interview question
What does $lookup return, and what should you watch for?
Think about it first.
A left join: matching docs in an array, [] if none. It runs per remaining input document — filter first. Foreign field should be indexed. Empty array + $unwind drops the parent. Frequent joins on the hot path are often a modelling smell.
Practice