$unwind turns one document holding an array of n elements into n documents, each carrying one element in place of the array and a copy of every other field. It is how you get from "orders that contain items" to "line items that know which order they came from".
db.orders.aggregate([
{ $match: { _id: 'o-1001' } },
{ $unwind: '$items' },
{ $project: { channel: 1, 'items.sku': 1, 'items.qty': 1 } }
])[
{ _id: 'o-1001', channel: 'web', items: { sku: 'KB-11', qty: 1 } },
{ _id: 'o-1001', channel: 'web', items: { sku: 'AC-52', qty: 2 } }
]The shorthand { $unwind: '$items' } silently drops documents whose array is empty, missing or null: the most common aggregation bug there is, because an order with no items vanishes from the report and the totals quietly disagree with the order count. The long form fixes that — { $unwind: { path: '$items', includeArrayIndex: 'pos', preserveNullAndEmptyArrays: true } }. An order with items: [] then survives as { _id: 'o-9999', pos: null } with no items field at all; every other row gains pos, the element's original index as a Long.
$unwind never buffers, so it is cheap in memory, but it multiplies the document count and everything downstream pays. Put $match before it, and drop unneeded fields first.