The $unwind stage splits array fields into one document per element—essential for tag analytics, order line items, flattening $lookup results, and any pipeline that must treat array items as rows.
01
Array → rows
One per item.
02
path
Field to split.
03
preserve null
Keep empty.
04
Array index
Position.
05
+ $group
Count tags.
06
+ $lookup
Flatten join.
Fundamentals
Definition and Usage
$unwind is an aggregation stage that takes an array field and outputs one document per array element. All other fields on the parent document are copied into each output row—the array field is replaced by a single scalar (or sub-document) value per row.
💡
Beginner tip
Before: { title: "Post A", tags: ["mongo", "api"] } — one document. After { $unwind: "$tags" }: two documents — tags: "mongo" and tags: "api". Same title on both rows; tags split for filtering or grouping.
Use $unwind whenever array elements need individual treatment: count tags with $group, sum line-item prices, match on one skill in a list, or flatten $lookup join arrays into readable rows.
Foundation
📝 Syntax
$unwind supports a shorthand string path or an extended options document:
Articles include normal tags, an empty array, and null—perfect for demonstrating default vs preserve behavior. Orders have nested item arrays for index examples.
4 tagged rows (same as basic)
+ Draft Notes tag: null
+ Untagged Post tag: null
How It Works
Use preserveNullAndEmptyArrays: true when untagged or empty-list documents must still appear—optional categories, incomplete records, or left-outer-join semantics after lookup.
📈 Practical Patterns
Line indexes, tag analytics, and lookup flattening.
O-1001 line 0: Widget qty 2 lineTotal 50
O-1001 line 1: Cable qty 1 lineTotal 9
O-1002 line 0: Gadget qty 1 lineTotal 80
How It Works
includeArrayIndex preserves position—line 1, line 2 on invoices—or lets you sort items in original order after unwind.
Example 5 — Tag frequency ($unwind + $group)
mongosh
// Count how many articles use each tag
db.articles.aggregate([
{ $match: { tags: { $exists: true, $ne: null, $not: { $size: 0 } } } },
{ $unwind: "$tags" },
{
$group: {
_id: "$tags",
articleCount: { $sum: 1 }
}
},
{ $sort: { articleCount: -1 } },
{
$project: {
_id: 0,
tag: "$_id",
articleCount: 1
}
}
])
// mongodb & database: 1 each (MongoDB Basics)
// nodejs & api: 1 each (REST API Guide)
// Shortcut alternative: $unwind then { $sortByCount: "$tags" }
📤 Tag counts:
mongodb 1 article
database 1 article
nodejs 1 article
api 1 article
(All tied at 1 in this small dataset)
How It Works
The classic analytics chain: unwind array elements into rows, then group and count. Same pattern powers tag clouds, skill inventories, and category breakdowns. For count-only output, $sortByCount after unwind is a one-stage shortcut.
Compare
📋 $unwind vs related array stages
Stage / tool
Behavior
Best for
$unwind
One doc per array element
Flatten tags, items, lookup arrays
$group
Collapse many docs into buckets
After unwind—count or sum per element
$sortByCount
Group + count + sort (no unwind needed for scalar field)
After unwind on tags for frequency list
$lookup
Join foreign docs into array field
Often followed by $unwind to flatten
$project (array ops)
Map/filter arrays in place
Transform arrays without row multiplication
🧠 How $unwind Works
1
Read array field
MongoDB reads the field at path on each input document.
Input
2
Emit one row per element
Each array item becomes a separate document; sibling fields are copied into every row.
Split
3
Apply options
Empty/null arrays drop or preserve per preserveNullAndEmptyArrays; index added if requested.
Options
=
🔄
Flattened stream
Downstream stages see scalar array values—ready for $match, $group, or joins.
$unwind turns array fields into rows: use shorthand for simple splits, preserveNullAndEmptyArrays for optional arrays, and includeArrayIndex when position matters.
Pair with $group for analytics or follow $lookup to flatten joins. Next: $group to bucket and summarize unwound rows.
Unwind before $group when counting array element frequencies
Unwind $lookup result arrays for flat joined reports
Use preserveNullAndEmptyArrays: true for optional or incomplete arrays
Add includeArrayIndex for ordered line items or ranked lists
$match before unwind when only some parent documents matter
❌ Don’t
Unwind huge arrays on every request without filtering first
Forget that empty arrays disappear with default settings
Skip unwind and expect $group to count individual tags correctly
Unwind the same array twice without need—row count grows fast
Omit the $ prefix on the path string
Summary
Key Takeaways
Knowledge Unlocked
Five things to remember about $unwind
Use these points whenever array elements must become rows.
5
Core concepts
🔄01
Array → rows
One per item.
Basics
📝02
$path
Field string.
Syntax
📋03
Preserve empty
Optional flag.
Option
🔢04
Index field
Line number.
Order
👥05
+ $group
Analytics.
Pattern
❓ Frequently Asked Questions
$unwind deconstructs an array field so each array element becomes its own output document. One document with tags: ["mongo", "api"] becomes two documents—each with a single tag value—so you can filter, group, or join on individual array items.
Short form: { $unwind: "$arrayField" }. Extended form: { $unwind: { path: "$arrayField", preserveNullAndEmptyArrays: true, includeArrayIndex: "indexName" } }. The path must be prefixed with $.
By default, $unwind removes documents where the array is missing, null, or empty. Set preserveNullAndEmptyArrays: true to keep those documents with the array field null or missing in output.
When you need counts or sums per array element—tag frequency, line-item totals, skills per employee—you must unwind first so each element is its own row, then $group on that field.
$lookup returns matching documents in an array field. $unwind flattens that array so joined fields sit at the top level—one output row per match instead of nested arrays.
Yes. Set includeArrayIndex to a field name string—e.g. includeArrayIndex: "lineIndex"—and MongoDB adds the zero-based position of each element in the original array.
Did you know?
MongoDB also offers $unwind with a path pointing at nested arrays—you can unwind sub-documents inside arrays when each element is an object (like order line items). For arrays of arrays, you may need two unwind stages in sequence. See the official $unwind docs.