$unwind
Learn how to deconstruct array fields into separate documents using $unwind in MongoDB aggregation pipelines.
$unwind
Arrays are powerful in MongoDB — a student can have multiple subjects, multiple grades, multiple activities all stored in one document. But when you need to analyze array data — count how many students take each subject, find the average score per subject — working with arrays as a whole becomes difficult.
$unwind solves this. It takes a document with an array field and creates one separate document for each element in that array.
How $unwind Works
One document with 3 subjects becomes 3 separate documents — one per subject. All other fields stay the same. Only the array field changes — it becomes a single value instead of an array.
Syntax
{ $unwind: "$arrayField" }Or the full syntax with options:
{
$unwind: {
path: "$arrayField",
includeArrayIndex: "indexField", // optional
preserveNullAndEmpty: true // optional
}
}Basic Example
Let's see $unwind on our students collection. Each student has a subjects array:
// Original document
{
name: "Ali Hassan",
grade: "10th",
subjects: ["Math", "Physics", "English"]
}Apply $unwind:
db.students.aggregate([
{ $match: { name: "Ali Hassan" } },
{ $unwind: "$subjects" }
])Result:
[
{ _id: ObjectId("..."), name: "Ali Hassan", grade: "10th", subjects: "Math" },
{ _id: ObjectId("..."), name: "Ali Hassan", grade: "10th", subjects: "Physics" },
{ _id: ObjectId("..."), name: "Ali Hassan", grade: "10th", subjects: "English" }
]Three documents — one for each subject. The _id is the same in all three because they came from the same original document.
Why $unwind is Useful — Count Students Per Subject
On its own, $unwind just expands documents. The real power comes when you combine it with $group.
Let's count how many students take each subject:
db.students.aggregate([
// Stage 1 — only enrolled students
{ $match: { enrolled: true } },
// Stage 2 — unwind subjects array
// Each student now becomes multiple documents — one per subject
{ $unwind: "$subjects" },
// Stage 3 — group by subject and count
{
$group: {
_id: "$subjects",
studentCount: { $sum: 1 }
}
},
// Stage 4 — sort by most popular subject
{ $sort: { studentCount: -1 } },
// Stage 5 — clean output
{
$project: {
_id: 0,
subject: "$_id",
studentCount: 1
}
}
])Result:
[
{ subject: "Math", studentCount: 89 },
{ subject: "English", studentCount: 85 },
{ subject: "Physics", studentCount: 72 },
{ subject: "Biology", studentCount: 61 },
{ subject: "Computer Science", studentCount: 48 }
]Without $unwind, this would be impossible to calculate inside MongoDB. You would have to pull all student documents into your app and count in JavaScript.
$unwind with Arrays of Objects
$unwind works just as well with arrays of objects — like a student's grades array:
// Original document
{
name: "Ali Hassan",
grade: "10th",
grades: [
{ subject: "Math", score: 88, semester: 1 },
{ subject: "Physics", score: 92, semester: 1 },
{ subject: "English", score: 79, semester: 1 }
]
}db.students.aggregate([
{ $match: { name: "Ali Hassan" } },
{ $unwind: "$grades" }
])Result:
[
{ name: "Ali Hassan", grade: "10th", grades: { subject: "Math", score: 88, semester: 1 } },
{ name: "Ali Hassan", grade: "10th", grades: { subject: "Physics", score: 92, semester: 1 } },
{ name: "Ali Hassan", grade: "10th", grades: { subject: "English", score: 79, semester: 1 } }
]Now combine with $group to calculate average score per subject across all students:
db.students.aggregate([
// Only enrolled students
{ $match: { enrolled: true } },
// Unwind grades array
{ $unwind: "$grades" },
// Group by subject — calculate average score
{
$group: {
_id: "$grades.subject",
averageScore: { $avg: "$grades.score" },
highestScore: { $max: "$grades.score" },
lowestScore: { $min: "$grades.score" },
totalStudents: { $sum: 1 }
}
},
// Sort by average score descending
{ $sort: { averageScore: -1 } },
// Clean output
{
$project: {
_id: 0,
subject: "$_id",
averageScore: { $round: ["$averageScore", 1] },
highestScore: 1,
lowestScore: 1,
totalStudents: 1
}
}
])Result:
[
{ subject: "English", averageScore: 84.2, highestScore: 98, lowestScore: 52, totalStudents: 85 },
{ subject: "Math", averageScore: 81.5, highestScore: 99, lowestScore: 44, totalStudents: 89 },
{ subject: "Physics", averageScore: 78.9, highestScore: 97, lowestScore: 38, totalStudents: 72 }
]includeArrayIndex
When you need to know the position of each element in the original array, use includeArrayIndex:
db.students.aggregate([
{ $match: { name: "Ali Hassan" } },
{
$unwind: {
path: "$subjects",
includeArrayIndex: "subjectPosition"
}
}
])Result:
[
{ name: "Ali Hassan", subjects: "Math", subjectPosition: 0 },
{ name: "Ali Hassan", subjects: "Physics", subjectPosition: 1 },
{ name: "Ali Hassan", subjects: "English", subjectPosition: 2 }
]subjectPosition tells you where each element was in the original array — 0 for first, 1 for second, and so on.
preserveNullAndEmpty
By default, $unwind removes documents where the array field is missing, null, or empty. Use preserveNullAndEmpty: true to keep those documents instead:
// Some students have no subjects assigned yet
{ name: "New Student", grade: "9th", subjects: [] }
{ name: "Another Student", grade: "9th" } // no subjects field at all// Default — these documents are removed by $unwind
db.students.aggregate([
{ $unwind: "$subjects" }
])
// New Student and Another Student do not appear in results
// With preserveNullAndEmpty — these documents are kept
db.students.aggregate([
{
$unwind: {
path: "$subjects",
preserveNullAndEmpty: true
}
}
])
// New Student appears with subjects: null
// Another Student appears with subjects: nullUse preserveNullAndEmpty: true when you need to keep track of documents that have no array elements — for example, when you want to report which students have not been assigned any subjects yet.
$unwind then $group — The Core Pattern
The most important pattern with $unwind is:
$match → $unwind → $groupFilter the documents you need, unwind the array to get individual elements, then group by those elements to calculate something.
// Most common subjects taken by 10th grade students
db.students.aggregate([
{ $match: { grade: "10th", enrolled: true } },
{ $unwind: "$subjects" },
{
$group: {
_id: "$subjects",
count: { $sum: 1 }
}
},
{ $sort: { count: -1 } },
{ $project: { _id: 0, subject: "$_id", count: 1 } }
])// Average score per subject for students in semester 1
db.students.aggregate([
{ $match: { enrolled: true } },
{ $unwind: "$grades" },
{ $match: { "grades.semester": 1 } },
{
$group: {
_id: "$grades.subject",
averageScore: { $avg: "$grades.score" }
}
},
{ $sort: { averageScore: -1 } },
{ $project: { _id: 0, subject: "$_id", averageScore: { $round: ["$averageScore", 1] } } }
])Notice the second $match after $unwind — you can filter on the unwound field values. This is a powerful combination.
School System Examples
// Count how many students take each subject — all grades
db.students.aggregate([
{ $match: { enrolled: true } },
{ $unwind: "$subjects" },
{ $group: { _id: "$subjects", count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $project: { _id: 0, subject: "$_id", count: 1 } }
])
// Most popular subjects in 10th grade
db.students.aggregate([
{ $match: { grade: "10th", enrolled: true } },
{ $unwind: "$subjects" },
{ $group: { _id: "$subjects", count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $limit: 3 }
])
// Average score per subject across all students
db.students.aggregate([
{ $match: { enrolled: true } },
{ $unwind: "$grades" },
{
$group: {
_id: "$grades.subject",
averageScore: { $avg: "$grades.score" },
highestScore: { $max: "$grades.score" },
lowestScore: { $min: "$grades.score" }
}
},
{ $sort: { averageScore: -1 } },
{
$project: {
_id: 0,
subject: "$_id",
averageScore: { $round: ["$averageScore", 1] },
highestScore: 1,
lowestScore: 1
}
}
])
// Find students who failed any subject (score below 50)
db.students.aggregate([
{ $match: { enrolled: true } },
{ $unwind: "$grades" },
{ $match: { "grades.score": { $lt: 50 } } },
{
$group: {
_id: "$name",
failedSubjects: { $push: "$grades.subject" },
lowestScore: { $min: "$grades.score" }
}
},
{ $project: { _id: 0, student: "$_id", failedSubjects: 1, lowestScore: 1 } }
])
// How many subjects does each student take on average
db.students.aggregate([
{ $match: { enrolled: true } },
{ $unwind: "$subjects" },
{ $group: { _id: "$_id", name: { $first: "$name" }, subjectCount: { $sum: 1 } } },
{ $group: { _id: null, averageSubjects: { $avg: "$subjectCount" } } },
{ $project: { _id: 0, averageSubjects: { $round: ["$averageSubjects", 1] } } }
])Quick Reference
| Syntax | What it does |
|---|---|
{ $unwind: "$field" } | Unwind array — removes docs with missing or empty array |
{ $unwind: { path: "$field", includeArrayIndex: "idx" } } | Unwind and include position of each element |
{ $unwind: { path: "$field", preserveNullAndEmpty: true } } | Unwind and keep documents with missing or empty array |
The pattern $unwind followed by $group is one of the most powerful combinations in MongoDB aggregation. Any time you need to analyze what is inside your arrays — count values, calculate averages, find totals — reach for this pattern. Unwind first to flatten the array, then group to calculate what you need.
$project, $sort, $limit, and $skip
Learn how to reshape documents with $project, sort results with $sort, and control output size with $limit and $skip in MongoDB aggregation pipelines.
$lookup
Learn how to join two collections together in MongoDB using $lookup to combine related data in aggregation pipelines.