MongoDBAggregation Framework Introduction

Aggregation Framework — Introduction

The aggregation framework is MongoDB's tool for transforming, reshaping, and summarizing data — the equivalent of SQL's GROUP BY, joins, and computed columns combined into one composable pipeline. If find() answers "which documents match," aggregation answers "what does this data look like once I filter, join, group, and reshape it."

The Pipeline Mental Model

Think of an aggregation pipeline the way you'd think of Unix pipes: each stage takes the documents produced by the previous stage as input, and passes its own output documents to the next stage. Data flows through the pipeline one stage at a time.

The Unix-pipe analogy

JS
// Unix:      cat orders.json | grep shipped | sort -k total | head -5

// MongoDB:
db.orders.aggregate([
  { $match: { status: "shipped" } },   // like grep
  { $sort: { total: -1 } },            // like sort
  { $limit: 5 }                        // like head
])
find() vs Aggregation — When to Reach for Which

Need

Use

Filter + sort + simple field selection

find() — simpler, and equally fast for this

Group and summarize (totals, averages, counts per category)

Aggregation — $group

Join data from another collection

Aggregation — $lookup

Reshape documents (rename/compute/restructure fields)

Aggregation — $project / $addFields

Flatten arrays into separate documents

Aggregation — $unwind

Multiple independent result sets from one query (e.g. results + counts)

Aggregation — $facet

Stage Overview

Stage

Purpose

$match

Filters documents — the aggregation equivalent of a find() filter

$project

Reshapes documents — include/exclude/compute fields

$addFields

Adds or overwrites fields while keeping everything else untouched

$group

Groups documents by a key and computes aggregates per group

$sort

Orders documents

$limit / $skip

Bounds the result set

$unwind

Flattens an array field into one document per element

$lookup

Joins in documents from another collection

$facet

Runs multiple sub-pipelines in parallel over the same input

$bucket / $bucketAuto

Groups documents into ranges (histograms)

$count

Collapses input into a single document with a count

$out / $merge

Writes pipeline results into a collection

A Simple Pipeline Walkthrough

Say we have an orders collection and want the total revenue per customer, for shipped orders only, highest spenders first.

Sample documents

JS
{ _id: 1, customer: "Alice", status: "shipped", total: 42.50 }
{ _id: 2, customer: "Bob",   status: "pending", total: 19.99 }
{ _id: 3, customer: "Alice", status: "shipped", total: 15.00 }
{ _id: 4, customer: "Carol", status: "shipped", total: 88.00 }

Pipeline: filter → group → sort

JS
db.orders.aggregate([
  { $match: { status: "shipped" } },
  { $group: { _id: "$customer", totalSpent: { $sum: "$total" } } },
  { $sort: { totalSpent: -1 } }
])
[
  { _id: 'Carol', totalSpent: 88 },
  { _id: 'Alice', totalSpent: 57.5 }
]

Notice Bob is gone entirely — his only order was pending, filtered out by the $match stage before grouping even happened. Each stage's output becomes the next stage's input, exactly like a Unix pipeline.

aggregate() Syntax

Basic call shape

JS
db.collectionName.aggregate(
  [ /* array of stage objects, executed in order */ ],
  { /* options — allowDiskUse, collation, etc. */ }
)
Note
aggregate() returns a cursor, exactly like find() — call .toArray(), .forEach(), or iterate it, rather than expecting a plain array back directly.
Explaining an Aggregation

Just like a query, a pipeline can be explained to see whether early stages use an index and how many documents flow between stages.

explain() on an aggregation

JS
db.orders.explain("executionStats").aggregate([
  { $match: { status: "shipped" } },
  { $group: { _id: "$customer", totalSpent: { $sum: "$total" } } }
])
Tip
Put $match (and $sort, when possible) as early as possible in the pipeline. MongoDB can push a leading $match down to use an index the same way a plain find() would — filtering 10,000 documents down to 50 before an expensive $group or $lookup stage is far cheaper than filtering after.
Warning
Every pipeline stage still has to respect the 16 MB limit on any single document it outputs, and (by default) a 100 MB limit on memory used per stage — set { allowDiskUse: true } to let stages like $sort and $group spill to disk when working over very large datasets.
  • An aggregation pipeline is an ordered array of stages, each transforming the output of the one before it.

  • $match and $sort early in the pipeline can use indexes, exactly like find().

  • aggregate() returns a cursor, just like find().

  • Use .explain() on a pipeline the same way you would on a query, to check for missing indexes or unnecessary in-memory work.