Skip to content

$unwind — flattening arrays

Arrays are everywhere in MongoDB documents — tags on a post, items in an order, lessons in a course. To summarise across the elements of those arrays, you usually need to look at them one at a time. $unwind does exactly that: it takes a document with an array field and emits one document per element, copying the rest of the document onto each. A document with a three-element array becomes three documents.

For this lesson each sales document records an order with several line items in an items array:

{
"_id": "o1",
"buyer": "Ada",
"items": [
{ "title": "Dune", "qty": 2 },
{ "title": "Foundation", "qty": 1 }
]
}

Give $unwind the path to an array field (with the $ prefix), and it fans the document out. Every emitted document is identical except that the array field is replaced by a single one of its elements.

flowchart LR
  In["one order with items array of two"] --> U["$unwind items"]
  U --> D1["order, items = Dune"]
  U --> D2["order, items = Foundation"]
A single document with a two-element array becomes two documents, one per element
db.sales.aggregate([
{ $unwind: "$items" }
])

The single order becomes one document per line item, with items now a plain object rather than an array:

[
{ "_id": "o1", "buyer": "Ada", "items": { "title": "Dune", "qty": 2 } },
{ "_id": "o1", "buyer": "Ada", "items": { "title": "Foundation", "qty": 1 } }
]

In Compass: add an $unwind stage in the Aggregations tab and point it at the array field. The preview shows the document count grow — a sign you have flattened an array — which is the opposite of what $match and $group do.

$unwind is rarely the last word. Its usual job is to flatten an array so a later $group can summarise across the elements. Here we flatten every order’s items, then total the quantity sold per title across all orders:

db.sales.aggregate([
{ $unwind: "$items" },
{ $group: {
_id: "$items.title",
sold: { $sum: "$items.qty" }
} },
{ $sort: { sold: -1 } }
])

Now each title has its total quantity, summed across every order’s line items:

[
{ "_id": "Dune", "sold": 5 },
{ "_id": "Foundation", "sold": 3 }
]

By default, $unwind drops a document whose array field is empty, missing, or null — there is no element to emit, so the document vanishes. When you want those documents to survive (with the field absent), pass the long form with preserveNullAndEmptyArrays:

db.sales.aggregate([
{ $unwind: {
path: "$items",
preserveNullAndEmptyArrays: true
} }
])

An order with no items still appears once, simply without an items field:

[
{ "_id": "o2", "buyer": "Grace" }
]
  • $unwind multiplies documents. An order with five items becomes five documents — flattening can grow the stream a lot before a later $group shrinks it again.
  • Without preserveNullAndEmptyArrays, documents with an empty or missing array are silently dropped. If your counts come up short after an unwind, this is the usual culprit.
  • After unwinding, reference the element’s fields through the original path: once items is a single object, "$items.qty" reads that element’s quantity.
  • If the field is not actually an array, $unwind treats a single scalar as a one-element array and emits the document once — so it is forgiving of non-array values.
A document has an items array with 3 elements. What does $unwind on items produce?
Which stage most commonly follows $unwind to summarise the flattened elements?
By default, what happens to a document whose unwound array is empty or missing?
How do you keep documents with empty or missing arrays in the output?