The $group Stage and the _id Field
The $group stage collects documents that share a common value into buckets and produces one output document per bucket. The _id field in a $group specification defines the grouping key: it can be a single field reference like "$customerId", a compound object combining multiple fields for multi-level grouping, or the literal value null to collapse the entire input into a single group. Every other field in the $group specification must be an accumulator expression, since $group's job is specifically to summarize many documents into fewer, richer ones.
Cricket analogy: Grouping every delivery bowled in a tournament by bowler name to compute each bowler's total wickets mirrors $group bucketing documents by a shared key like "$bowlerId".
Common Accumulator Operators
Accumulators compute a value across all documents in a group: $sum adds numeric values (or counts documents when given the literal 1), $avg computes a mean, $min and $max find extremes, $push collects values into an array preserving duplicates, and $addToSet collects unique values into an array. $first and $last, when used after a $sort stage, capture the first or last document's value within each group, which is a common way to get, for example, the most recent order per customer. Because $group operates on documents already grouped in memory, the accumulators run once per group rather than once per document, so a group's cost scales with the group's size, not the whole collection.
Cricket analogy: Computing a batter's career average by summing all runs and dividing by innings played mirrors $avg, while collecting every unique venue they've played at mirrors $addToSet.
db.sales.aggregate([
{ $sort: { date: -1 } },
{ $group: {
_id: "$region",
totalRevenue: { $sum: "$amount" },
avgOrderValue: { $avg: "$amount" },
orderCount: { $sum: 1 },
largestOrder: { $max: "$amount" },
latestOrderDate: { $first: "$date" },
distinctProducts: { $addToSet: "$productId" }
}}
]);$first and $last only produce meaningful results when a $sort stage immediately precedes $group in the pipeline; without a defined order, "first" and "last" reflect whatever order documents happened to arrive in, which is not guaranteed to be stable.
Multi-Field Grouping and Reshaping After $group
Setting _id to a compound object such as { year: { $year: "$date" }, region: "$region" } produces one bucket per unique combination of year and region, enabling multi-dimensional summaries like monthly sales per region. Because $group replaces the document shape entirely — the output documents contain only _id and the accumulator fields you defined — a $project stage typically follows $group to rename _id's subfields into a flatter, more client-friendly structure, since consumers of an API rarely want to dig through a nested _id object.
Cricket analogy: Grouping deliveries by both bowler and match format (Test, ODI, T20) to see format-specific averages mirrors compound _id grouping combining two keys into one bucket.
After a compound $group, chain a $project stage like { $project: { _id: 0, year: "$_id.year", region: "$_id.region", totalRevenue: 1 } } to flatten the nested _id into top-level fields before returning results to a client.
- $group buckets documents by an _id expression, which can be a single field, a compound object, or null for a single overall group.
- Every non-_id field in a $group specification must be an accumulator expression.
- $sum, $avg, $min, and $max compute numeric summaries; $push and $addToSet collect values into arrays, with $addToSet deduplicating.
- $first and $last require a preceding $sort stage to produce meaningful, stable results.
- A compound _id object enables multi-dimensional grouping, such as grouping by both region and year.
- $group output documents contain only _id and the defined accumulator fields, replacing the original document shape.
- A $project stage commonly follows $group to flatten a compound _id into simpler top-level fields for API consumers.
Practice what you learned
1. What must every field besides _id contain in a $group stage specification?
2. What is the difference between $push and $addToSet?
3. Why is a preceding $sort stage important when using $first or $last in $group?
4. What does setting _id to a compound object like { year: "$year", region: "$region" } achieve?
5. Why does a $project stage commonly follow $group in a pipeline?
Was this page helpful?
You May Also Like
The Aggregation Pipeline
Learn how MongoDB's aggregation pipeline transforms documents through an ordered sequence of stages to filter, reshape, and summarize data.
Common Aggregation Stages
A practical tour of the most frequently used aggregation stages — $match, $project, $sort, $limit, $skip, and $unwind — and how they reshape documents.
Aggregation Performance
Learn practical techniques for diagnosing and improving the performance of MongoDB aggregation pipelines, from index usage to explain plans.