MongoDB Aggregation

Beginner
⏱️ 16 min read
📚 Updated: Jul 2026
🎯 5 Examples
Aggregation

What You’ll Learn

Aggregation is how MongoDB runs multi-step analytics: filter documents, compute fields, group totals, join collections, and return reports—all in one aggregate() call.

01

Pipeline model

Stages run in order.

02

Core stages

$match, $group, $project.

03

Operators

Expressions per document.

04

Accumulators

Rollups inside $group.

05

Joins

$lookup + $unwind.

06

Performance

Filter early, index smart.

Definition and Usage

Aggregation processes data records and returns computed results without changing the source collection. You chain pipeline stages where each stage receives documents, transforms them, and passes the output forward— similar to Unix pipes, but for BSON documents.

💡
Beginner tip

Think of find() as a single filter + projection. Aggregation is find() plus grouping, computed columns, joins, and sorting—all composed as an explicit list of stages you can read top to bottom.

Use aggregation for dashboards (revenue by region), ETL-style reshaping, leaderboard queries, and any report that needs more than a plain find(). Dive deeper into pipeline stages, expression operators, and accumulators once you understand the pipeline model.

🔑 Key concepts

ConceptRoleLearn more
PipelineOrdered array of stages in aggregate([ ... ])This tutorial
StageOne transformation step ($match, $group, …)Stages hub
OperatorPer-document expression ($add, $cond)Operators hub
AccumulatorPer-group rollup ($sum, $avg)Accumulators hub
Document streamEach stage consumes and produces BSON documentsHow it works below

📝 Syntax

Every aggregation starts with a collection and a pipeline array:

mongosh
db.<collection>.aggregate(
  [
    { <stage1> },
    { <stage2> },
    ...
  ],
  { options }   // optional: allowDiskUse, maxTimeMS, etc.
);

Syntax Rules

  • Order matters — stages execute top to bottom; put $match early to reduce data volume.
  • One operator per stage key — each object in the array has a single stage name: { $match: { ... } }.
  • Result cursoraggregate() returns a cursor; iterate or use .toArray() in scripts.
  • Read-only by default — pipelines do not modify source data unless you add $out or $merge.
  • Indexes — early $match, $sort, and $limit can use indexes when the planner allows.
  • allowDiskUse — large sorts/groups may need { allowDiskUse: true } to spill to disk.
mongosh
db.orders.aggregate([
  { $match: { status: "completed" } },
  { $group: { _id: "$region", total: { $sum: "$amount" } } },
  { $sort: { total: -1 } }
]);

⚡ Quick Reference

TaskStage / pattern
Filter documents{ $match: { field: value } }
Pick/rename fields{ $project: { name: 1, total: 1 } }
Add computed field{ $addFields: { tax: { $multiply: [...] } } }
Group and sum{ $group: { _id: "$cat", total: { $sum: "$amt" } } }
Sort results{ $sort: { total: -1 } }
Join collection{ $lookup: { from, localField, foreignField, as } }
Top 10 only{ $limit: 10 }
Beginner trio
$match → $group → $sort

Filter, summarize, rank

Shape data
$project / $addFields

Reshape before output

Join pattern
$lookup → $unwind

Attach related docs

Debug tip
add $limit: 5 early

Small output while building

🧰 Pipeline building blocks

Start with these stage families—each has dedicated tutorials on CodeToFun.

$match Start here

Filter the document stream—same operators as find(). Place first when possible for index use.

{ $match: { status: "active" } }
$group Summarize

Bucket by _id and run accumulators. Required for totals, counts, and averages.

{ $group: {
  _id: "$category",
  count: { $sum: 1 }
} }
$project / $addFields Reshape

$project selects fields; $addFields adds computed columns while keeping existing fields.

{ $addFields: {
  lineTotal: { $multiply: ["$qty", "$price"] }
} }
$lookup Join

Left-outer join another collection. Often paired with $unwind to flatten arrays.

{ $lookup: {
  from: "users",
  localField: "userId",
  foreignField: "_id",
  as: "user"
} }

Examples Gallery

An orders collection—progress from a simple pipeline to joins and multi-view analytics.

📚 Getting Started

Sample data and your first three-stage pipeline.

Example 1 — Sample orders and a first pipeline

mongosh
db.orders.insertMany([
  { orderId: 1, region: "US",  amount: 120, status: "completed" },
  { orderId: 2, region: "EU",  amount: 80,  status: "completed" },
  { orderId: 3, region: "US",  amount: 200, status: "completed" },
  { orderId: 4, region: "APAC", amount: 50, status: "pending" }
]);

db.orders.aggregate([
  { $match: { status: "completed" } },
  { $project: { orderId: 1, region: 1, amount: 1 } }
]);

How It Works

$match keeps completed orders; $project returns only the fields you need. Two stages, one round trip to the server.

Example 2 — Revenue by region ($match + $group + $sort)

mongosh
db.orders.aggregate([
  { $match: { status: "completed" } },
  {
    $group: {
      _id: "$region",
      revenue: { $sum: "$amount" },
      orderCount: { $sum: 1 }
    }
  },
  { $sort: { revenue: -1 } }
]);

/* Result:
   { _id: "US",   revenue: 320, orderCount: 2 }
   { _id: "EU",   revenue: 80,  orderCount: 1 }
*/

How It Works

This is the classic analytics pattern: filter, group with $sum accumulators, sort descending. Most dashboards start here.

📈 Practical Patterns

Computed columns, joins, and multi-view queries.

Example 3 — Computed fields with operators

mongosh
db.orders.aggregate([
  { $match: { status: "completed" } },
  {
    $addFields: {
      tax: { $multiply: ["$amount", 0.08] },
      grandTotal: {
        $add: ["$amount", { $multiply: ["$amount", 0.08] }]
      },
      highValue: {
        $cond: {
          if: { $gte: ["$amount", 100] },
          then: true,
          else: false
        }
      }
    }
  },
  { $project: { orderId: 1, amount: 1, tax: 1, grandTotal: 1, highValue: 1 } }
]);

How It Works

$addFields uses expression operators ($multiply, $add, $cond) per document—no grouping required.

Example 4 — Join customers with $lookup

mongosh
db.orders.aggregate([
  { $match: { status: "completed" } },
  {
    $lookup: {
      from: "customers",
      localField: "customerId",
      foreignField: "_id",
      as: "customer"
    }
  },
  { $unwind: "$customer" },
  {
    $project: {
      orderId: 1,
      amount: 1,
      customerName: "$customer.name",
      customerEmail: "$customer.email"
    }
  }
]);

How It Works

$lookup attaches matching customer docs in an array; $unwind flattens to one row per order. See the $lookup tutorial for correlated pipeline joins.

Example 5 — Dashboard with $facet (multiple views)

mongosh
db.orders.aggregate([
  { $match: { status: "completed" } },
  {
    $facet: {
      totals: [
        { $group: {
          _id: null,
          revenue: { $sum: "$amount" },
          orders: { $sum: 1 }
        } }
      ],
      byRegion: [
        { $group: { _id: "$region", revenue: { $sum: "$amount" } } },
        { $sort: { revenue: -1 } },
        { $limit: 5 }
      ],
      topOrders: [
        { $sort: { amount: -1 } },
        { $limit: 3 },
        { $project: { orderId: 1, amount: 1, region: 1 } }
      ]
    }
  }
]);

/* One query → { totals: [...], byRegion: [...], topOrders: [...] } */

How It Works

$facet runs sub-pipelines on the same input—ideal for API endpoints that need a summary, a breakdown, and a top-N list together.

🧠 How Aggregation Works

1

Collection scan or index

MongoDB reads from the source collection (or view). Early $match can use indexes.

Input
2

Stage 1 runs

First stage transforms the stream—often $match to filter documents.

Stage
3

Chain continues

Each subsequent stage receives the previous output—group, reshape, join, sort.

Pipeline
4

Planner optimizes

MongoDB may coalesce stages or push $match / $sort toward the collection scan.

Optimize
=

Cursor returned

Client receives transformed documents—your report, API payload, or export.

📝 Notes

  • Original data is safe—aggregation reads documents; it does not update them unless you add a write stage.
  • Stage order matters—filter with $match before $group to process fewer documents.
  • 100 MB limit—each stage has a memory limit per pipeline; use allowDiskUse: true for large sorts/groups if needed.
  • explain()—run db.collection.explain("executionStats").aggregate([...]) to inspect index use.
  • Compass—the aggregation builder visualizes each stage output for easier learning.
  • Previous topic: Accumulators. Next: Replication.

Conclusion

Aggregation is MongoDB’s powerhouse for analytics: chain stages to filter, reshape, summarize, join, and sort in one request. Master the pipeline model here, then explore the stages, operators, and accumulators hubs for deep dives on each piece.

Start every pipeline with a clear question—“total revenue by region” maps directly to $match + $group + $sort. Build one stage at a time until the answer matches your report.

💡 Best Practices

✅ Do

  • Place $match and $project early to shrink document size
  • Index fields used in $match and join keys used in $lookup
  • Build pipelines incrementally—one stage at a time in mongosh
  • Use $limit while debugging large collections
  • Prefer server-side aggregation over fetching raw data to the app
  • Read stage-specific tutorials when a single stage gets complex

❌ Don’t

  • Pull entire collections into Node/Python when aggregation can filter server-side
  • Run $unwind on huge arrays before filtering—$match first
  • Forget _id in $group—it defines your buckets
  • Assume find() and aggregation indexes behave identically—verify with explain
  • Use $where or JavaScript when native operators suffice—slower and harder to secure
  • Skip learning accumulators vs operators—they serve different roles in pipelines

Key Takeaways

Knowledge Unlocked

Five things to remember about aggregation

Use these points when designing MongoDB analytics pipelines.

5
Core concepts
🔍 02

$match first

Filter early.

Perf
📊 03

$group

Summarize data.

Analytics
04

Operators

Per-doc math/logic.

Expressions
🔗 05

$lookup

Join collections.

Relational

❓ Frequently Asked Questions

Aggregation is MongoDB's framework for processing documents through a pipeline of stages. Each stage transforms the stream of documents and passes results to the next stage—like an assembly line for analytics, reports, and reshaped query output.
Use find() for simple filters and projections on one collection. Use aggregation when you need grouping, computed fields, joins ($lookup), sorting/limiting after transforms, or multi-step analytics in one database round trip.
A pipeline is an ordered array of stages passed to db.collection.aggregate([ ... ]). Example: $match to filter, $group to summarize, $sort to order results. Stages run sequentially; the output of one stage feeds the next.
Stages ($match, $group) are pipeline steps. Operators ($add, $cond) are expressions inside stages. Accumulators ($sum, $avg) run inside $group to compute one value per bucket.
No. Aggregation reads documents and returns a result cursor. Original collection data stays unchanged unless you use a write stage like $out or $merge.
Build one stage at a time in mongosh or MongoDB Compass. Run db.collection.aggregate([stage1]) then add stage2. Use $limit early while testing to keep output small.
Did you know?

MongoDB can merge adjacent pipeline stages during optimization—for example, combining multiple $match stages into one, or pushing a $sort + $limit to the collection scan when indexes support it. Always verify with explain() on production-sized data. See the official pipeline optimization guide.

Continue the MongoDB Series

You understand aggregation pipelines—next learn how replication keeps clusters highly available.

Next: Replication →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

5 people found this page helpful