MongoDB $sortByCount Stage

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

What You’ll Learn

The $sortByCount stage groups, counts, and ranks field values in one step—perfect for “most popular category,” status breakdowns, and tag-frequency analytics without writing separate $group and $sort stages.

01

Group + count

One field.

02

Desc count

High first.

03

Output shape

_id + count.

04

After $match

Filtered pool.

05

vs $group

Shorthand.

06

+ $unwind

Tag counts.

Definition and Usage

$sortByCount is an aggregation stage that takes a single field path, groups all incoming documents by that field’s value, counts documents per group, and returns groups sorted by count descending (most frequent value first).

💡
Beginner tip

Think of it as a frequency table:
{ $sortByCount: "$category" } answers “how many orders per category, biggest category first?”
Each result row: { _id: "electronics", count: 12 }.

Use $sortByCount for dashboards, histogram summaries, and exploratory analytics. Put $match before it when counts should reflect a filtered subset—e.g. only shipped orders or active users.

📝 Syntax

$sortByCount accepts one argument—a string field path:

mongosh
{ $sortByCount: <string field path> }

// field path is usually prefixed with $
{ $sortByCount: "$category" }

Syntax Rules

  • String path — must be a string like "$status" or "$address.city" for nested fields.
  • One field only — groups by a single field; multi-field breakdowns need $group instead.
  • Output — each document has _id (the field value) and count (group size).
  • Sort direction — always descending by count; no option to change inside this stage.
  • Null / missing — documents missing the field group under _id: null.
  • Arrays — array values group as whole arrays; use $unwind first to count individual array elements.
mongosh
db.orders.aggregate([
  { $sortByCount: "$category" }
])

⚡ Quick Reference

QuestionAnswer
What it doesGroup by field → count → sort counts descending
Syntax{ $sortByCount: "$field" }
Output shape{ _id: value, count: N } per group
Sort orderCount descending only (fixed)
Equivalent$group + $sort: { count: -1 }
Array fields$unwind first, then $sortByCount
Basic
{ $sortByCount:
  "$status"
}

Status counts

Filtered
$match →
$sortByCount

Subset counts

Tags
$unwind →
$sortByCount

Per-tag freq

Rename
$project after
_id → label

API shape

🧰 $sortByCount Argument & Output

The stage has one input (field path) and a fixed output structure:

field path Required

String referencing the field to group by. Use $ prefix for document fields: "$category", "$region".

{ $sortByCount: "$paymentMethod" }
_id Output

Each distinct field value becomes _id in the result—e.g. "shipped", "pending", or numeric/boolean values.

{ _id: "electronics", count: 8 }
{ _id: "books", count: 3 }
count Output

Number of input documents in each group. Results sorted by this field descending automatically.

// Highest count appears first
{ _id: "electronics", count: 8 }
{ _id: "books", count: 3 }
Nested path Dot notation

Group by nested fields with dot notation in the path string.

{ $sortByCount: "$shipping.region" }
{ $sortByCount: "$meta.source" }

Examples Gallery

Orders and articles collections—category frequency, filtered status counts, manual $group equivalent, tag popularity after $unwind, and renaming output for APIs.

📚 Getting Started

Orders and articles for frequency analytics.

Example 1 — Orders and articles collections

mongosh
db.orders.drop()
db.articles.drop()

db.orders.insertMany([
  { orderId: "O-01", category: "electronics", status: "shipped",   amount: 199 },
  { orderId: "O-02", category: "electronics", status: "shipped",   amount: 89  },
  { orderId: "O-03", category: "books",       status: "shipped",   amount: 24  },
  { orderId: "O-04", category: "electronics", status: "pending",   amount: 149 },
  { orderId: "O-05", category: "home",        status: "shipped",   amount: 55  },
  { orderId: "O-06", category: "books",       status: "cancelled", amount: 18  },
  { orderId: "O-07", category: "electronics", status: "shipped",   amount: 299 },
  { orderId: "O-08", category: "home",        status: "pending",   amount: 42  },
  { orderId: "O-09", category: "books",       status: "shipped",   amount: 32  },
  { orderId: "O-10", category: "electronics", status: "shipped",   amount: 79  }
])

db.articles.insertMany([
  { title: "MongoDB Basics",     tags: ["mongodb", "database", "tutorial"] },
  { title: "Node API Guide",     tags: ["nodejs", "api", "tutorial"]       },
  { title: "Aggregation Tips",   tags: ["mongodb", "aggregation"]          },
  { title: "React Hooks",        tags: ["react", "frontend"]               },
  { title: "Pipeline Patterns",  tags: ["mongodb", "aggregation", "tutorial"] }
])

How It Works

Ten orders across three categories and five tagged articles—enough to show clear frequency rankings and tag counts after unwind.

Example 2 — Count orders by category (basic $sortByCount)

mongosh
db.orders.aggregate([
  { $sortByCount: "$category" }
])

// Output (sorted by count desc):
// { _id: "electronics", count: 5 }
// { _id: "books",       count: 3 }
// { _id: "home",        count: 2 }

How It Works

One stage replaces group-and-sort. Electronics appears first because it has the highest count—ideal for bar charts and “top categories” widgets.

Example 3 — Shipped orders only ($match first)

mongosh
db.orders.aggregate([
  { $match: { status: "shipped" } },
  { $sortByCount: "$category" },
  {
    $project: {
      _id: 0,
      category: "$_id",
      shippedCount: "$count"
    }
  }
])

// Pool: 7 shipped orders (O-01, O-02, O-03, O-05, O-07, O-09, O-10)
// electronics: 4, books: 2, home: 1
// pending/cancelled excluded by $match

How It Works

Filter before counting so frequencies reflect business rules—completed sales only, active users only, or a date range from an earlier $match.

📈 Practical Patterns

Manual equivalent, tag clouds, and API-friendly output.

Example 4 — Same result with $group + $sort (equivalent pipeline)

mongosh
// $sortByCount shorthand:
db.orders.aggregate([
  { $sortByCount: "$status" }
])

// Long-form equivalent — identical output:
db.orders.aggregate([
  {
    $group: {
      _id: "$status",
      count: { $sum: 1 }
    }
  },
  { $sort: { count: -1 } }
])

// Both return:
// { _id: "shipped",   count: 7 }
// { _id: "pending",   count: 2 }
// { _id: "cancelled", count: 1 }

How It Works

Understanding the equivalent helps when you need extra group logic—multiple accumulators, custom sort, or renaming—beyond what $sortByCount provides.

Example 5 — Tag popularity ($unwind + $sortByCount)

mongosh
// Count how often each tag appears across articles
db.articles.aggregate([
  { $unwind: "$tags" },
  { $sortByCount: "$tags" },
  {
    $project: {
      _id: 0,
      tag: "$_id",
      articles: "$count"
    }
  }
])

// Each article tag becomes one row after $unwind
// mongodb: 3, tutorial: 3, aggregation: 2, ...
// "mongodb" tops the list (3 articles)

How It Works

Array fields need $unwind first—otherwise MongoDB groups by the entire array as one value. This pattern powers tag clouds and keyword popularity reports.

🧠 How $sortByCount Works

1

Documents in

Pipeline stream arrives—optionally filtered by prior $match stages.

Input
2

Group by field

MongoDB buckets documents by the specified field value—each distinct value becomes one group.

Group
3

Count per group

Each bucket gets a count—number of documents sharing that field value.

Count
=

Sorted frequency list

Groups emit as { _id, count } rows—highest count first, ready for charts or APIs.

📝 Notes

  • Argument must be a string field path—typically "$fieldName".
  • Output always includes _id and count; sort is descending by count only.
  • For array fields, $unwind first unless you want to count identical array values as one group.
  • Use $project after to rename _id to a friendlier API field like category or tag.
  • Multi-field histograms need $group with compound _id—not $sortByCount.
  • Previous topic: $sort. Next: $unset.

Conclusion

$sortByCount is the quick frequency stage: pass a field path, get grouped counts sorted highest-first—filter with $match, unwind arrays for tags, and project for clean API output.

When you need custom accumulators or ascending sort, use $group + $sort instead. Next: $unset to remove fields from pipeline documents.

💡 Best Practices

✅ Do

  • Use $match before $sortByCount to count meaningful subsets
  • Pair $unwind + $sortByCount for tag and keyword analytics
  • Follow with $project to rename _id for JSON APIs
  • Index the grouped field when running on large collections
  • Reach for $group + $sort when you need sums or averages too

❌ Don’t

  • Use $sortByCount when you only need one total—use $count
  • Expect ascending count order—this stage always sorts descending
  • Group array elements without $unwind unless intentional
  • Confuse with $sort for ordering full documents
  • Forget that _id in output is the grouped field value—not document _id

Key Takeaways

Knowledge Unlocked

Five things to remember about $sortByCount

Use these points for frequency and breakdown analytics.

5
Core concepts
📈 02

Desc sort

High first.

Order
📋 03

_id + count

Output shape.

Format
🔍 04

$match first

Filter pool.

Pattern
🏷️ 05

$unwind tags

Array fields.

Tags

❓ Frequently Asked Questions

$sortByCount groups documents by a single field value, counts how many documents fall into each group, and returns the groups sorted by count from highest to lowest. Output shape is { _id: fieldValue, count: N } for each distinct value.
{ $sortByCount: "$fieldPath" } — the argument is a string field path, usually prefixed with $ like "$category" or "$status". Example: { $sortByCount: "$category" } counts orders per category, most common first.
$sort reorders full documents by field values. $sortByCount aggregates first—it collapses many documents into count summaries grouped by one field. Use $sortByCount for frequency charts; use $sort for ranked document lists.
Two stages: { $group: { _id: "$category", count: { $sum: 1 } } } followed by { $sort: { count: -1 } }. $sortByCount combines both into one readable stage.
Not directly on the array—$sortByCount groups by the whole field value. Use $unwind on the array first so each tag becomes its own document, then { $sortByCount: "$tags" } for per-tag frequencies.
No. $sortByCount always sorts by count descending (highest count first). For ascending count order or custom sort keys, use $group plus $sort manually.
Did you know?

$sortByCount was added in MongoDB 3.4 as a readability shortcut—the server executes the same grouping and counting logic as $group with $sum: 1, then sorts by the count field. For compound breakdowns (e.g. category and region), use $group: { _id: { category: "$category", region: "$region" } } instead. See the official $sortByCount docs.

Continue the Stages Series

Count frequencies with $sortByCount, then drop fields with $unset.

Next: $unset →

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