$unwind explodes arrays into documents
coreintermediate$unwind takes a document with an array field and outputs one copy of that document per array element, with the array field replaced by a single element each time. A document with a 5-element array becomes 5 pipeline documents.
Think of it as
Think of $unwind as flattening a one-to-many relationship that was embedded as an array back into rows — the same shape a SQL join produces, computed on the fly instead of stored that way. It exists so later stages ($match, $group) can operate per array element instead of per whole document.
What we're doing: Count how many orders mention each tag, which requires per-tag documents before grouping.
- 2
- An order with tags: ["sale", "gift"] becomes two pipeline documents, one per tag — the order's other fields are duplicated onto both.
- 3
- $group can now treat "$tags" as one scalar value per document, which is what makes counting orders per tag possible.
Why this works: $group needs one value per document to group by — a document still holding an array of tags cannot be grouped "by tag" directly, so $unwind is the step that turns "one order, many tags" into "one document per (order, tag) pair" first.
Running $unwind before a $match that only needs the parent document's other fields
Wrong
Better
What you see: The pipeline unwinds far more documents than necessary, multiplying work that a cheap early filter would have avoided.
Why: $unwind multiplies the document count by each array's length before any later stage runs — filtering on a field the unwind itself does not touch should happen first, exactly like pushing $match early in any pipeline.
- Input — { _id: 1, tags: ["a", "b", "c"] } — one document
- $unwind: "$tags" — the array field is exploded
- Output — { _id: 1, tags: "a" }, { _id: 1, tags: "b" }, { _id: 1, tags: "c" } — three documents
Remember: $unwind turns one document with an N-element array into N documents, each holding one element — and silently drops documents whose array is empty or missing, unless preserveNullAndEmptyArrays is set.
See also: preserve null and array index · explosion tradeoff · map filter reduce

