Count & Distinct
Counting documents and finding distinct values sound trivial, but MongoDB gives you more than one way to do each — and the "obvious" choice isn't always the fastest one. This page covers countDocuments(), estimatedDocumentCount(), $count in aggregation, and distinct().
countDocuments()
countDocuments() runs an actual aggregation-backed count that respects your filter. It's accurate — always reflects a real scan matching the current filter — but that accuracy costs a real query.
Accurate, filtered count
db.orders.countDocuments({ status: "shipped" })
db.orders.countDocuments({}) // total count with a filter — still does real workestimatedDocumentCount()
estimatedDocumentCount() reads the collection's cached metadata count instead of scanning anything. It's near-instant regardless of collection size — but it ignores any filter and can be slightly stale under heavy concurrent writes.
Fast, unfiltered, approximate count
db.orders.estimatedDocumentCount() // no filter argument accepted at all
Method | Accuracy | Speed | Supports a Filter? |
|---|---|---|---|
countDocuments(filter) | Exact | Scans matching documents/index entries — scales with match count | Yes |
estimatedDocumentCount() | Approximate (collection metadata) | Near-instant, independent of collection size | No |
estimatedDocumentCount() for dashboard-style "total rows in this collection" displays where a filter isn't needed and perfect precision doesn't matter. Use countDocuments() whenever the count must reflect a filter, or must be exactly correct.$count in Aggregation
Inside a pipeline, $count collapses everything reaching that stage into a single document with a count field — handy when the count needs to happen after other stages like $match, $group, or $unwind.
$count after other pipeline stages
db.orders.aggregate([
{ $match: { status: "shipped" } },
{ $unwind: "$items" },
{ $match: { "items.category": "widgets" } },
{ $count: "widgetLineItems" }
])
// [ { widgetLineItems: 42 } ]db.collection.countDocuments(filter) is implemented internally as an aggregation pipeline ([{ $match: filter }, { $group: { _id: null, n: { $sum: 1 } } }] roughly) — so reaching for $count directly is really just doing that same thing explicitly, useful when you need it mid-pipeline rather than as the final result.distinct()
distinct() returns the unique values of a single field across the (optionally filtered) collection, as a plain array — no counts.
distinct() basics
db.products.distinct("category")
// [ "widgets", "gadgets", "gizmos" ]
db.products.distinct("category", { inStock: true }) // with a filter
// [ "widgets", "gadgets" ]distinct() has a result-size limit: the combined size of all distinct values must fit within the 16 MB BSON document limit, because the result set is built as a single document internally. For fields with a huge number of unique values, use a $group aggregation instead — it streams rather than buffering everything into one document.Distinct Values With Counts via $group
distinct() only gives you the unique values — not how many documents have each one. For that, use $group, which doubles as a "distinct with counts" tool.
Distinct values AND their counts
db.products.aggregate([
{ $group: { _id: "$category", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
])
// [
// { _id: "widgets", count: 120 },
// { _id: "gadgets", count: 85 },
// { _id: "gizmos", count: 43 }
// ]Performance Notes
estimatedDocumentCount()is effectively free — it reads cached metadata and never scans data.countDocuments()with an index-covered filter is fast; without one, it scans as many documents as match, same cost as an equivalentfind().distinct()on an indexed field can be satisfied from the index alone without touching the underlying documents — a big win on large collections.Prefer
$groupoverdistinct()when the field has extremely high cardinality (approaching or exceeding what fits in 16 MB) or when you need per-value counts.
Example Session
test> db.orders.estimatedDocumentCount()
48213
test> db.orders.countDocuments({ status: "shipped" })
12904
test> db.orders.distinct("status")
[ 'pending', 'shipped', 'cancelled', 'refunded' ]
test> db.orders.aggregate([
... { $group: { _id: "$status", count: { $sum: 1 } } }
... ])
[
{ _id: 'pending', count: 3021 },
{ _id: 'shipped', count: 12904 },
{ _id: 'cancelled', count: 892 },
{ _id: 'refunded', count: 156 }
]