MongoDB $addToSet Accumulator

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

What You’ll Learn

The $addToSet accumulator builds an array of unique values while MongoDB groups documents. It is the aggregation-friendly way to answer “what distinct values appeared in this group?”

01

Unique arrays

Skip duplicates automatically in $group.

02

$group syntax

Accumulator form with field or expression.

03

vs $push

When to keep every value vs distinct only.

04

$unwind pattern

Unique scalars from nested arrays.

05

Real reports

Cities, tags, skills, product IDs per group.

06

Gotchas

Array fields, null, object equality.

Definition and Usage

$addToSet is a MongoDB accumulator used inside the $group stage. For each group, it evaluates an expression on every input document and adds the result to an output array—but only if that exact value is not already in the array.

💡
Beginner tip

Think of $addToSet as “give me every different value I saw,” while $push is “give me every value I saw, even repeats.” Both return arrays; only $addToSet deduplicates.

Typical reports include unique cities per department, distinct product categories sold per store, or a set of user IDs who performed an action. Pair with $match and $sort before grouping for cleaner results.

📝 Syntax

$addToSet appears as a field accumulator in $group:

mongosh
{
  $group: {
    _id: <expression>,
    <outputField>: { $addToSet: <expression> }
  }
}

Syntax Rules

  • Stage — used only as an accumulator in $group (not in $match filters).
  • Expression — usually "$fieldName" or any valid aggregation expression.
  • Output — an array of unique values per group (empty array if no documents match).
  • Equality — deduplication uses BSON value comparison (same rules as $eq).
  • Array fields — without $unwind, the whole array is one value added to the set.
mongosh
db.orders.aggregate([
  {
    $group: {
      _id: "$storeId",
      productIds: { $addToSet: "$productId" }
    }
  }
]);

⚡ Quick Reference

QuestionAnswer
Stage$group accumulator
Syntax{ $addToSet: <expression> }
DuplicatesIgnored (only unique values kept)
vs $push$push keeps all values including repeats
Nested arrays$unwind first, then $addToSet scalar
Common pairing$match$group$project
Scalar field
{ $addToSet: "$city" }

Unique cities per group

Expression
{ $addToSet: {
  $toUpper: "$tag"
} }

Normalize before dedupe

After unwind
{ $unwind: "$tags" }
{ $group: {
  _id: "$userId",
  tags: { $addToSet: "$tags" }
} }

Unique tag strings

vs push
{ $push: "$city" }
// keeps duplicates

All occurrences

🧰 Parameters

$addToSet accepts one expression argument inside $group:

expression Required

Value to add to the set for each document in the group. Typically a field path like "$city" or a computed expression.

{ $addToSet: "$skill" }
_id Group key

The _id expression in the same $group defines how documents are bucketed. Each bucket gets its own unique-value array.

_id: "$department"
output field Array

Name your result field (e.g. uniqueCities). The value is always an array, even when only one unique value exists.

uniqueCities: { $addToSet: … }
missing / null Important

If the expression resolves to null or a missing field, MongoDB may add null once to the set. Filter with $match if nulls should be excluded.

$match: { city: { $ne: null } }

Examples Gallery

Sample employee orders data, then group by department to collect unique cities and compare $addToSet with $push.

📚 Getting Started

Load sample documents and run a basic $addToSet grouping.

Example 1 — Sample collection

Three sales records with repeating cities inside the same department:

mongosh
db.sales.insertMany([
  { department: "Engineering", city: "Austin",  productId: "P1" },
  { department: "Engineering", city: "Seattle", productId: "P2" },
  { department: "Engineering", city: "Austin",  productId: "P3" },
  { department: "Marketing",   city: "Boston",  productId: "P1" },
  { department: "Marketing",   city: "Boston",  productId: "P4" }
]);

How It Works

Engineering appears in Austin twice—perfect for showing how $addToSet collapses duplicates while still listing Seattle once.

Example 2 — Unique cities per department

Group by department and collect distinct city values:

mongosh
db.sales.aggregate([
  {
    $group: {
      _id: "$department",
      uniqueCities: { $addToSet: "$city" }
    }
  },
  { $sort: { _id: 1 } }
]);

/* Result:
[
  { _id: "Engineering", uniqueCities: ["Austin", "Seattle"] },
  { _id: "Marketing",   uniqueCities: ["Boston"] }
]
*/

How It Works

Austin is listed once even though two Engineering documents reference it. Order follows first encounter in the pipeline (do not rely on sorted output unless you sort afterward).

📈 Practical Patterns

Handle array fields, compare accumulators, and shape the final report.

Example 3 — Unique tags with $unwind

When each document stores tags as an array, unwind before grouping:

mongosh
db.articles.aggregate([
  { $unwind: "$tags" },
  {
    $group: {
      _id: "$author",
      uniqueTags: { $addToSet: "$tags" }
    }
  }
]);

// Without $unwind, { $addToSet: "$tags" } would store
// whole arrays as set members—not individual tag strings.

How It Works

$unwind emits one document per array element. Then $addToSet deduplicates scalar tag values across all articles by the same author.

Example 4 — $addToSet vs $push side by side

Same grouping with both accumulators shows the difference clearly:

mongosh
db.sales.aggregate([
  {
    $group: {
      _id: "$department",
      allCities:   { $push: "$city" },
      uniqueCities: { $addToSet: "$city" }
    }
  },
  { $match: { _id: "Engineering" } }
]);

/* Engineering result:
{
  _id: "Engineering",
  allCities: ["Austin", "Seattle", "Austin"],
  uniqueCities: ["Austin", "Seattle"]
}
*/

How It Works

Use $push when count or sequence matters (event logs). Use $addToSet when you only need the distinct set of values (catalog of cities visited).

Example 5 — Rename and count unique products

Combine $addToSet with $project to friendly field names and a size metric:

mongosh
db.sales.aggregate([
  {
    $group: {
      _id: "$department",
      productIds: { $addToSet: "$productId" }
    }
  },
  {
    $project: {
      _id: 0,
      department: "$_id",
      productIds: 1,
      productCount: { $size: "$productIds" }
    }
  }
]);

How It Works

$size counts array elements after deduplication—handy for “how many different products did this department sell?” dashboards.

🧠 How $addToSet Works

1

Documents enter $group

MongoDB buckets documents by the _id expression (e.g. department name).

Group
2

Evaluate expression

For each document in the bucket, compute the $addToSet expression (field path or formula).

Eval
3

Check uniqueness

If the value is not already in the group array (BSON-equal), append it; otherwise skip.

Dedupe
=

Unique array per group

Each group document contains one array field with all distinct collected values.

📝 Notes

  • $addToSet is an accumulator—it belongs inside $group, not in $match.
  • For array fields, $unwind first unless you intentionally want unique whole-array values.
  • Result arrays are best treated as sets, not sorted lists—use $sortArray in a later stage if order matters.
  • null may appear once in the set if the expression evaluates to null—filter early with $match when needed.
  • Do not confuse with update operator $addToSet on updateOne—same name, different context (document updates vs aggregation).
  • Next in the series: $avg for numeric averages per group.

Conclusion

$addToSet is the standard MongoDB accumulator when you need distinct values per group: unique cities, product IDs, tags, or any scalar you can extract from documents. Pair it with $unwind for nested arrays and with $size when you only need a count.

When duplicates must be preserved or order is significant, switch to $push instead. For custom fold logic, see $accumulator.

💡 Best Practices

✅ Do

  • Use $addToSet for distinct-value reports in $group
  • $unwind array fields when you need unique elements, not unique arrays
  • Filter nulls and empty strings in $match before grouping
  • Combine with $size for cardinality metrics
  • Document whether your UI treats the result as ordered or unordered

❌ Don’t

  • Use $addToSet when you need every duplicate event ($push instead)
  • Assume output arrays are sorted alphabetically
  • Forget that "10" and 10 are different set members
  • Mix up aggregation $addToSet with update $addToSet syntax
  • Skip indexes on _id group keys for large collections without testing performance

Key Takeaways

Knowledge Unlocked

Five things to remember about $addToSet

Use these points whenever you build distinct-value aggregation reports.

5
Core concepts
📦 02

$group only

Accumulator context.

Syntax
🔄 03

vs $push

All values vs distinct.

Compare
04

$unwind

For nested arrays.

Pattern
🔢 05

$size

Count distinct items.

Metric

❓ Frequently Asked Questions

$addToSet collects values into an array during $group and only adds a value if it is not already present. The result is a set of unique values for each group.
Inside the $group stage as an accumulator: { uniqueField: { $addToSet: "$field" } }. It is not a query filter operator—it runs while grouping documents.
$push appends every value (including duplicates). $addToSet skips values already in the array. Use $addToSet for distinct lists; use $push when you need every occurrence or order preserved.
MongoDB generally keeps first-seen order for unique values in the output array, but treat the result as an unordered set unless your use case explicitly requires order.
$addToSet adds the entire array as one element unless you $unwind the field first. To collect unique tags from nested arrays, $unwind before $group, then $addToSet the scalar.
Yes. It deduplicates by value equality (BSON comparison). Two sub-documents with identical fields count as one entry; slightly different objects remain separate entries.
Did you know?

MongoDB also has an update operator named $addToSet for adding unique values to an array field on a single document. The aggregation accumulator you learned here runs inside $group across many documents. Same name, different pipeline stage. See the official $addToSet aggregation docs.

Continue the Accumulators Series

Master distinct arrays with $addToSet, then move on to numeric summaries with $avg.

Next: $avg →

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