MongoDBCounting & Distinct Values

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

JS
db.orders.countDocuments({ status: "shipped" })
db.orders.countDocuments({})   // total count with a filter — still does real work
estimatedDocumentCount()

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

JS
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

Tip
Use 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

JS
db.orders.aggregate([
  { $match: { status: "shipped" } },
  { $unwind: "$items" },
  { $match: { "items.category": "widgets" } },
  { $count: "widgetLineItems" }
])
// [ { widgetLineItems: 42 } ]
Note
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

JS
db.products.distinct("category")
// [ "widgets", "gadgets", "gizmos" ]

db.products.distinct("category", { inStock: true })   // with a filter
// [ "widgets", "gadgets" ]
Warning
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

JS
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 equivalent find().

  • distinct() on an indexed field can be satisfied from the index alone without touching the underlying documents — a big win on large collections.

  • Prefer $group over distinct() 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 }
]