MongoDB $unwind Stage

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

What Is $unwind?

$unwind is a MongoDB aggregation stage that expands an array so each element becomes its own document. Other fields are copied onto every new row—perfect when you need to filter, group, or join on individual array items.

Remember
$unwind = 1 array → N documents (one per element)

{ $unwind: "$tags" }

{ tags: ["mongo", "api"] }
  → { tags: "mongo" }
  → { tags: "api" }

empty / missing → dropped (default)
preserveNullAndEmptyArrays: true → keep them
includeArrayIndex: "idx" → add position
UsePrefer
One row per array element$unwind
Count tags / items$unwind then $group
Pick one array element$arrayElemAt

How to Use $unwind in MongoDB

Point at the array path, then decide whether empty arrays should survive.

  1. Name the array with a $ prefix (for example "$tags").
  2. Use the short form for a simple expand, or the object form for options.
  3. Set preserveNullAndEmptyArrays: true if empty/missing arrays must stay.
  4. Add includeArrayIndex when you need the original position.
PieceRequired?Role
path / short stringYesArray field to expand
preserveNullAndEmptyArraysNoKeep empty / missing arrays
includeArrayIndexNoStore zero-based index

Pseudocode

Pseudocode
for each document:
    arr = document.tags
    if arr is missing, null, or empty:
        if preserveNullAndEmptyArrays:
            emit document with tags = null
        else:
            skip
    else:
        for each element (and optional index) in arr:
            emit copy of document with tags = element

Cheat Sheet

Keep these facts handy while expanding arrays.

TopicDetail
TypeAggregation stage
Syntax{ $unwind: "$field" }
OutputOne document per element
Other fieldsCopied to every row
Empty defaultDocument dropped
PreservepreserveNullAndEmptyArrays
Sample["mongo","api"] → 2 docs

Live Preview

Pick an array shape and see how many documents $unwind emits.

Simulates $unwind on Asha’s tags field.

Result
$unwind: "$tags"

before → { name: "Asha", tags: ["mongo", "api"] }
after  → 2 documents
  { name: "Asha", tags: "mongo" }
  { name: "Asha", tags: "api" }

Worked Walkthrough — Expand Tags

Asha has two tags. $unwind turns that one document into two—one per tag—so we can count tags later.

Calculation
Document:
  { name: "Asha", tags: ["mongo", "api"] }

$unwind: "$tags"

Result:
  { name: "Asha", tags: "mongo" }
  { name: "Asha", tags: "api" }

1 in → 2 out · name copied to both

MongoDB Examples

We use one student with a tags array. First insert it, then unwind the tags, then keep empty arrays with preserveNullAndEmptyArrays. Run these in mongosh after inserting the sample data once.

Example 1 — Sample Data

Imagine student Asha with two skill tags, and Ravi with an empty tag list. We save both in students.

mongosh
db.students.insertMany([
  { _id: 1, name: "Asha", tags: ["mongo", "api"] },
  { _id: 2, name: "Ravi", tags: [] }
]);

How the Data Is Stored in MongoDB

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

mongosh
db.students.find()

Read those arrays like this:

StudenttagsAfter default unwind
Asha["mongo", "api"]2 documents
Ravi[]Dropped
Picture
{ name: "Asha", tags: ["mongo", "api"] }
                 ↓ $unwind "$tags"
{ name: "Asha", tags: "mongo" }
{ name: "Asha", tags: "api" }

How It Works

  1. Insert once. insertMany adds Asha and Ravi to students.
  2. Array shape matters. Asha has two elements; Ravi has none.
  3. find() shows the full shape. Expanding happens in the pipeline, not in storage.

Example 2 — Basic Unwind

Goal: expand tags so each tag is its own document.

mongosh
db.students.aggregate([
  { $unwind: "$tags" }
]);

How It Works

  1. Point at the array. "$tags" is the path to expand.
  2. One element → one row. Asha produces two documents; name is copied to both.
  3. Empty dropped. Ravi’s empty tags disappears under the default rules.

Example 3 — Preserve Empty Arrays

Same data, but keep Ravi even though his tags array is empty. Also store the original index for Asha’s tags.

mongosh
db.students.aggregate([
  {
    $unwind: {
      path: "$tags",
      preserveNullAndEmptyArrays: true,
      includeArrayIndex: "idx"
    }
  }
]);

How It Works

  1. Object form. Options need path plus flags, not the short string.
  2. Preserve. Ravi stays; tags becomes null.
  3. Index field. idx is 0 / 1 for Asha, and null for Ravi.

Edge Cases & Pitfalls

Check these before expanding arrays in production pipelines.

volume

Output can explode

A document with 1,000 array elements becomes 1,000 rows. Filter with $match first when you can.

empty

Empty arrays disappear by default

If you still need those parents, set preserveNullAndEmptyArrays: true.

path

Remember the $ prefix

Use "$tags", not "tags". Wrong path → errors or unexpected empty results.

lookup

Often follows $lookup

$lookup returns matches in an array. Unwind that array when you want one joined row per match.

Performance Notes

ApproachCostBest for
$unwind small arraysCheap multiplyTags / short lists
$match then unwindFewer rows earlyLarge collections
Unwind huge arraysHigh document countAvoid unless needed

Unwind multiplies documents. Narrow the set first, and unwind only the arrays you truly need to process element-by-element.

Key Takeaways

  • Role: one array element → one document.
  • Syntax: { $unwind: "$field" } or object form.
  • Empty default: missing / empty arrays are dropped.
  • Sample: ["mongo","api"] → two tagged rows.

One line: $unwind answers “give me one pipeline document per array element.”

Frequently Asked Questions

$unwind expands an array field so each element becomes its own output document. One document with tags: ["mongo", "api"] becomes two documents—each with a single tag value.
Short form: { $unwind: "$arrayField" }. Extended form: { $unwind: { path: "$arrayField", preserveNullAndEmptyArrays: true, includeArrayIndex: "idx" } }. The path must start with $.
By default, $unwind drops documents where the array is missing, null, or empty. Set preserveNullAndEmptyArrays: true to keep those documents with the field set to null.
To count or sum per array element—tag frequency, line items, skills—you unwind first so each element is its own row, then $group on that field.
Yes. Set includeArrayIndex to a field name (for example "idx") and MongoDB adds the zero-based position of each element.
No. It only changes documents in the pipeline stream unless you write results back with $out or $merge.

Next: $group

Group unwound rows to count tags, totals, and more.

$group 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.

11 people found this page helpful