MongoDB $addToSet Accumulator

Beginner
5 min read
Updated: Oct 2026
4 examples
Live preview

What Is $addToSet?

$addToSet is a MongoDB accumulator for the $group stage. For each group it builds an array of unique values—a value is added only if it is not already in that group’s array.

Remember
$addToSet = unique values per group

$input field → evaluate once per document
already in array? → skip
new value? → append

vs $push → $push keeps duplicates; $addToSet does not
UsePrefer
Distinct cities, tags, IDs$addToSet
Every value, including repeats$push
Unique items inside nested arrays$unwind then $addToSet

How to Use $addToSet in MongoDB

One expression per accumulator field is enough for most reports.

  1. Put $addToSet inside $group on an output field.
  2. Pass a field path (e.g. "$city") or any aggregation expression.
  3. Choose _id for the group key (store, category, or null for one group).
  4. If the source is an array of scalars, $unwind first so each item can be added separately.
PieceRequired?Role
$group._idYesGroup key ("$store", null, …)
$addToSetYesExpression whose unique results fill the array
ExpressionYesUsually "$field"; may be computed

Pseudocode

Pseudocode
for each group:
    set = []
    for each document in group:
        value = evaluate(expression)
        if value not in set:
            set.append(value)
    emit { _id: groupKey, field: set }

Cheat Sheet

Keep these facts handy while writing a unique-value rollup.

TopicDetail
Stage$group only (as an accumulator)
Syntax{ $addToSet: "$field" }
DuplicatesIgnored (BSON equality)
vs $push$push keeps repeats; $addToSet does not
Nested arrays$unwind first, then $addToSet the scalar
Sample checkstore A cities: New York, Chicago (New York once)

Live Preview

Watch unique cities collect document by document. Sample orders: A/New York, A/New York, A/Chicago, B/New York.

Simulates unique city collection (same idea as the mongosh examples below).

Result
store A + New York → ["New York"]
store A + New York → ["New York"]  (skip duplicate)
store A + Chicago → ["New York", "Chicago"]
store B + New York → ["New York"]

[
  { _id: "A", cities: ["New York", "Chicago"] },
  { _id: "B", cities: ["New York"] }
]

Worked Walkthrough — Unique Cities

Four orders, two stores. Watch store A keep New York once, then add Chicago.

Calculation
Documents:
  { store: "A", city: "New York" }
  { store: "A", city: "New York" }
  { store: "A", city: "Chicago" }
  { store: "B", city: "New York" }

Group store "A":
  [] + "New York" → ["New York"]
  ["New York"] + "New York" → ["New York"]   // already present
  ["New York"] + "Chicago" → ["New York", "Chicago"]

Group store "B":
  [] + "New York" → ["New York"]

Output:
  { _id: "A", cities: ["New York", "Chicago"] }
  { _id: "B", cities: ["New York"] }

MongoDB Examples

We use a tiny orders list. First we collect unique cities per store. Then we contrast $push. Finally we unwind tags and collect unique tag strings. Run these in mongosh after inserting the sample data once.

Example 1 — Sample Data

Imagine four orders. Each has a store, a city, and a tags array. Notice store A has New York twice—perfect for showing how $addToSet drops duplicates.

mongosh
db.orders.insertMany([
  { store: "A", city: "New York", tags: ["sale", "new"] },
  { store: "A", city: "New York", tags: ["sale"] },
  { store: "A", city: "Chicago", tags: ["clearance"] },
  { store: "B", city: "New York", tags: ["new"] }
]);

How the Data Is Stored in MongoDB

After the insert, MongoDB keeps 4 documents in orders. Run find() to see them:

mongosh
db.orders.find()

Read each document like this:

Field in MongoDBMeaningExample
storeWhich store made the order"A"
cityCity for that order"New York"
tagsLabels on the order (array)["sale","new"]
Picture
Orders by store:

  Store A: New York, New York, Chicago
  Store B: New York

Notice Store A has "New York" twice —
$addToSet will keep it only once.

How It Works

  1. Insert once. The sample command adds 4 documents to orders.
  2. MongoDB stores fields as written. Each document keeps the same field names and values you inserted.
  3. find() shows the same shape. What you inserted is what you get back—ready for $addToSet in the next examples.

Example 2 — Unique Cities per Store

Goal: for each store, list every different city that appeared. _id: "$store" makes one group per store value.

mongosh
db.orders.aggregate([
  {
    $group: {
      _id: "$store",
      cities: { $addToSet: "$city" }
    }
  },
  { $sort: { _id: 1 } }
]);

How It Works

  1. Split by store. _id: "$store" creates groups "A" and "B".
  2. Read each city. For every document, $addToSet looks at $city.
  3. Skip repeats. Store A sees New York twice; the second time it is already in the set, so nothing changes.
  4. Add new values. Chicago is new for store A, so it is appended. Store B only ever sees New York.

Example 3 — $addToSet vs $push

Same grouping, two accumulators side by side. $push keeps every city; $addToSet keeps each city once.

mongosh
db.orders.aggregate([
  {
    $group: {
      _id: "$store",
      citiesSet: { $addToSet: "$city" },
      citiesAll: { $push: "$city" }
    }
  },
  { $sort: { _id: 1 } }
]);

How It Works

  1. Same group key. Both fields use the same _id: "$store" groups.
  2. Set drops the duplicate. Store A’s citiesSet has New York once.
  3. Push keeps every visit. citiesAll lists New York twice because two orders used that city.
  4. Pick by intent. Distinct report → $addToSet. Full history → $push.

Example 4 — Unique Tags after $unwind

Goal: unique tag strings per store. Without $unwind, $addToSet: "$tags" would treat each whole array as one element.

mongosh
db.orders.aggregate([
  { $unwind: "$tags" },
  {
    $group: {
      _id: "$store",
      tags: { $addToSet: "$tags" }
    }
  },
  { $sort: { _id: 1 } }
]);

How It Works

  1. Flatten first. $unwind: "$tags" turns each tag into its own document.
  2. Group by store. Store A now has documents for sale, new, sale, and clearance.
  3. Deduplicate. $addToSet keeps sale once, then adds new and clearance.
  4. Without unwind. You would get arrays-of-arrays (whole tags values), not individual strings.

Edge Cases & Pitfalls

Check these before shipping a unique-value report.

arrays

Unwind nested arrays first

Without $unwind, the entire array is one set element. Flatten when you want unique scalars.

null

Missing fields become null

If a document lacks the field, null can appear once in the set. Filter with $match when you do not want it.

order

Do not depend on array order

First-seen order is common, but treat the result as an unordered set for APIs and tests.

objects

Objects compare by full shape

Two similar documents with one different field are both kept. Normalize fields before grouping if needed.

Performance Notes

ApproachCostBest for
$addToSetPer-doc set membership checkDistinct lists with moderate cardinality
$pushAppend only (grows faster)Full history; no uniqueness needed

Both scan grouped documents once. Large unique sets use more memory—filter early with $match and avoid collecting high-cardinality fields (like raw timestamps) into a set.

Key Takeaways

  • Role: unique-value array inside $group.
  • Rule: add only if not already present (BSON equality).
  • vs $push: set for distinct; push for every occurrence.
  • Nested arrays: $unwind before $addToSet on scalars.

One line: $addToSet builds a distinct list per group—use it when duplicates would only noise your report.

Frequently Asked Questions

$addToSet is a $group accumulator that builds an array of unique values. Each new value is added only if it is not already in the array for that group.
Inside $group as an accumulator field, for example { cities: { $addToSet: "$city" } }. It is not a query filter—it runs while grouping.
$push keeps every value, including duplicates. $addToSet skips values already present. Use $addToSet for distinct lists; use $push when you need every occurrence.
MongoDB usually keeps first-seen order for unique values, but treat the result as a set. Do not rely on array order unless your product docs require it.
$addToSet treats the whole array as one element. To collect unique items from nested arrays, $unwind first, then $addToSet the scalar field.
Yes. Equality follows BSON comparison. Two identical sub-documents count as one entry; objects that differ in any field stay separate.

Next: $avg

Compute the mean of numeric fields per group.

$avg tutorial →

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.

9 people found this page helpful