MongoDB $count Stage

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

What You’ll Learn

The $count stage returns a single summary document with how many records reached that point in the pipeline—perfect for “how many shipped orders?” after a $match filter.

01

One number out

{ total: N } shape.

02

After $match

Count filtered docs.

03

Field name arg

String, not an object.

04

vs $group+$sum

Shorthand pattern.

05

Empty input

No doc if count is 0.

06

vs countDocuments

Pipeline vs query API.

Definition and Usage

$count is an aggregation stage that replaces a verbose $group + $project pair. It counts every document flowing in from previous stages and emits one result row with your chosen field name.

💡
Beginner tip

Do not confuse this stage with the $count accumulator inside $group (which counts array items). This page covers the pipeline stage only—syntax is { $count: "fieldName" }.

Use $count at the end of dashboards queries, pagination metadata (“total matching rows”), and validation checks. Pair with $match to count subsets without pulling every document into memory.

📝 Syntax

$count takes a single string—the name of the output count field:

mongosh
{ $count: <string> }

Syntax Rules

  • String argument — not an object; the string becomes the output field name.
  • Valid names — non-empty, must not start with $, must not contain ..
  • Input — counts documents from all prior stages in the pipeline.
  • Output — exactly one document, e.g. { orderTotal: 15 }, unless input is empty.
  • Empty input — zero documents in → zero documents out (no { total: 0 }).
  • Equivalent$group: { _id: null, n: { $sum: 1 } } then $project: { _id: 0 }.
mongosh
db.orders.aggregate([
  { $match: { status: "shipped" } },
  { $count: "shippedTotal" }
]);
// → [ { shippedTotal: 3 } ]

⚡ Quick Reference

QuestionAnswer
Syntax{ $count: "fieldName" }
OutputOne doc: { fieldName: N }
Zero matchesEmpty result set (no document)
Typical placementLast stage after $match
vs $collStats count$count = exact pipeline tally; collStats = fast metadata
vs countDocuments()$count inside aggregate; respects prior stages
All docs
db.orders.aggregate([
  { $count: "total" }
])

Collection size

Filtered
{ $match: { amount: {
  $gte: 100
}}}

Then $count

Count groups
{ $group: {
  _id: "$status"
}}
{ $count: "groups" }

Distinct buckets

Equivalent
$group: { _id: null,
  n: { $sum: 1 } }

Verbose form

🧰 Parameters

$count has one parameter—the output field name:

field name Required

String key for the count value in the result document. Choose a descriptive name like matchingOrders or activeUsers.

{ $count: "matchingOrders" }
→ { matchingOrders: 42 }
input documents Implicit

All documents output by previous pipeline stages are counted—includes effects of $match, $unwind, $lookup, etc.

$match → $lookup → $count
return type Numeric

MongoDB uses the smallest numeric type that fits: integer → long → double.

5 docs → integer
2B docs → long
invalid names Avoid

Names starting with $ or containing . cause errors.

✗ "$total"
✗ "meta.count"

Examples Gallery

Sample orders collection—count all rows, filtered subsets, grouped buckets, empty-match behavior, and comparison with query APIs.

📚 Getting Started

Sample orders and a full-collection count.

Example 1 — Sample orders collection

mongosh
db.orders.insertMany([
  { product: "Keyboard", amount: 45,  status: "shipped",  region: "East" },
  { product: "Mouse",    amount: 25,  status: "pending",  region: "West" },
  { product: "Monitor",  amount: 220, status: "shipped",  region: "East" },
  { product: "Webcam",   amount: 60,  status: "pending",  region: "East" },
  { product: "Headset",  amount: 80,  status: "shipped",  region: "West" },
  { product: "Dock",     amount: 95,  status: "cancelled", region: "West" }
])

How It Works

Six orders across three statuses and two regions—enough variety for filtered counts and group counting in later examples.

Example 2 — Count every document in the collection

mongosh
db.orders.aggregate([
  { $count: "totalOrders" }
])

/* Result:
[ { totalOrders: 6 } ]
*/

// Same logic, verbose form:
db.orders.aggregate([
  { $group: { _id: null, totalOrders: { $sum: 1 } } },
  { $project: { _id: 0 } }
])

How It Works

With no prior stages, every collection document flows into $count. The stage collapses them into one summary row.

📈 Practical Patterns

Filtered counts, group totals, and edge cases.

Example 3 — Count shipped orders after $match

mongosh
db.orders.aggregate([
  {
    $match: {
      status: "shipped",
      amount: { $gte: 50 }
    }
  },
  { $count: "highValueShipped" }
])

/* Keyboard $45 → excluded (amount < 50)
   Monitor $220, Headset $80 → included
   Result: [ { highValueShipped: 2 } ] */

How It Works

$match shrinks the stream; $count only sees documents that passed the filter. This is the most common production pattern for “total matching filters” in APIs.

Example 4 — Count distinct groups after $group

mongosh
// How many distinct status values exist?
db.orders.aggregate([
  { $group: { _id: "$status" } },
  { $count: "statusCount" }
])
// → [ { statusCount: 3 } ]  (shipped, pending, cancelled)

// Count high-value orders per region, then count regions with any
db.orders.aggregate([
  { $match: { amount: { $gte: 80 } } },
  { $group: { _id: "$region", orders: { $sum: 1 } } },
  { $count: "regionsWithHighValue" }
])
// East + West both have orders ≥ $80 → statusCount-style: 2 regions

How It Works

After $group, each output document is one bucket—$count then counts those buckets, not the original rows. Useful for “how many categories?” questions.

Example 5 — Empty input and vs countDocuments()

mongosh
// Zero matches → $count returns NOTHING (empty array)
db.orders.aggregate([
  { $match: { status: "refunded" } },
  { $count: "refundedTotal" }
])
// → []   (not { refundedTotal: 0 })

// countDocuments() always returns a number, including 0
db.orders.countDocuments({ status: "refunded" })
// → 0

// $collStats count — fast metadata (may be approximate on sharded)
db.orders.aggregate([
  { $collStats: { count: {} } }
])
// → { count: 6, ns: "...", host: "..." }

How It Works

Know the empty-result quirk before building UIs. Use countDocuments() when you need a guaranteed number; use $count when the count must reflect complex pipeline logic.

🧠 How $count Works

1

Prior stages run

$match, $project, $group, etc. produce the document stream entering $count.

Input
2

Documents tallied

MongoDB counts every document in the stream—internally like $sum: 1 over all rows.

Tally
3

Summary emitted

One output document with your field name and the numeric total.

Output
=

Single count result

Downstream stages (rare) see one document—or none if input was empty.

📝 Notes

  • This page covers the $count stage, not the $count accumulator used inside $group on arrays.
  • Empty pipeline input → no output document; plan for [] in your code.
  • $unwind before $count multiplies rows—counts inflated array elements, not parent docs.
  • For counts per category, use $group with $sum: 1 instead of a final $count.
  • $count scans matching documents—indexes on $match fields keep it fast.
  • Previous topic: $collStats. Next: $currentOp.

Conclusion

$count is the concise way to answer “how many?” at the end of a pipeline. Filter with $match, then { $count: "yourField" } for a single summary document.

Remember the empty-result behavior and pick countDocuments() when you need a guaranteed zero. Next: $currentOp for in-flight operations.

💡 Best Practices

✅ Do

  • Place $match before $count to limit scanned documents
  • Use descriptive output field names: activeUsers, pendingOrders
  • Handle empty aggregation results in API responses (default to 0 if needed)
  • Index fields used in preceding $match filters
  • Use $facet to return both rows and count in one query when needed

❌ Don’t

  • Confuse the $count stage with the $count array accumulator
  • Expect { total: 0 } when no documents match—result is empty
  • Use $count when you need counts broken out by category—use $group
  • Pass an object to $count—only a string field name is valid
  • Count huge collections without filters when only a subset matters

Key Takeaways

Knowledge Unlocked

Five things to remember about $count

Use these points whenever you tally pipeline results.

5
Core concepts
📝 02

String arg

Output field name.

Syntax
🔍 03

After $match

Filtered totals.

Pattern
⚠️ 04

Zero matches

Empty array [].

Edge case
🛠 05

vs countDocuments

Pipeline vs query.

Choose

❓ Frequently Asked Questions

$count tallies how many documents reach that point in the aggregation pipeline and returns a single document like { myTotal: 42 }. The string argument is the output field name. It is a shortcut for $group with $sum: 1 plus $project to drop _id.
Usually at the end after filters: db.orders.aggregate([{ $match: { status: "shipped" } }, { $count: "shippedTotal" }]). You can also count intermediate results—e.g. after $group to count how many groups were formed.
The $count stage counts pipeline documents and outputs one summary row. The $count accumulator lives inside $group and counts array elements or similar per group bucket—they solve different problems.
If zero documents reach $count, MongoDB returns no result document at all—not { total: 0 }. Handle empty cursors in application code or use $group/$facet if you need an explicit zero.
countDocuments() runs a query count outside a pipeline. $count runs inside aggregate() after prior stages ($match, $lookup, etc.) so the count reflects filtered or transformed pipeline output.
The argument must be a non-empty string, cannot start with $, and cannot contain a dot (.). Example: { $count: "orderTotal" } outputs { orderTotal: N }.
Did you know?

You can return both paginated rows and a total count in one round trip with $facet: one sub-pipeline uses $skip/$limit for the page, another ends with $count for the total. See the official $count docs.

Continue the Stages Series

Tally matching documents with $count, then inspect live operations with $currentOp.

Next: $currentOp →

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