MongoDB $facet: One Query, Multiple Results
Introduction MongoDB's aggregation framework offers powerful data processing capabilities, and the MongoDB $facet stage is one of its most versatile features. This guide explores how to effectively use $facet to run multiple aggregation pipelines simultaneously, saving you time and improving application performance. What is the MongoDB $facet Stage? The $facet stage lets you process the same input documents through multiple aggregation pipelines within a single operation. Instead of making several database queries, you can execute multiple analyses at once and receive results in a structured document. { $facet: { "outputName1": [ ], "outputName2": [ ], // More pipelines as needed } } Without $facet, you have to send two requests to get outputName1 and outputName2. Key Benefits of Using $facet Reduced database round trips. Parallel processing of the same dataset. Simplified client-side code. More efficient resource utilization. Cleaner API responses Sample Dataset: E-commerce Products Collection To make the examples concrete and easy to follow, we'll use a single e-commerce products collection throughout this guide. You can create this sample dataset in your local MongoDB instance to test the examples yourself. You can simply follow up with me using MongoDB Compass. Data seeding You can use MongoDB Compass to insert the following products in your products collection. [ { "name": "Smartphone X1", "price": 699.99, "category": "Electronics", "brand": "TechCo", "tags": ["smartphone", "5G", "camera"], "stock": 123, "rating": 4.5, "reviews": 382, "createdAt": "2023-06-15T00:00:00.000Z", "updatedAt": "2023-12-01T00:00:00.000Z", "description": "Latest flagship smartphone with advanced camera system", "featured": true, "discountPercentage": 5, "attributes": { "color": "Black", "size": "6.4 inch", "weight": 189 } }, { "name": "Wireless Earbuds Pro", "price": 149.99, "category": "Electronics", "brand": "AudioPhile", "tags": ["wireless", "earbuds", "noise-cancelling"], "stock": 78, "rating": 4.7, "reviews": 204, "createdAt": "2023-08-21T00:00:00.000Z", "updatedAt": "2023-11-15T00:00:00.000Z", "description": "Premium wireless earbuds with active noise cancellation", "featured": true, "discountPercentage": 0, "attributes": { "color": "White", "size": "Standard", "weight": 58 } }, { "name": "Cotton T-Shirt", "price": 19.99, "category": "Clothing", "brand": "FashionBasics", "tags": ["t-shirt", "cotton", "casual"], "stock": 243, "rating": 4.2, "reviews": 119, "createdAt": "2023-04-10T00:00:00.000Z", "updatedAt": "2023-09-20T00:00:00.000Z", "description": "Comfortable everyday cotton t-shirt", "featured": false, "discountPercentage": 15, "attributes": { "color": "Blue", "size": "M", "weight": 150 } }, { "name": "Smart Watch SE", "price": 299.99, "category": "Electronics", "brand": "TechCo", "tags": ["smartwatch", "fitness", "bluetooth"], "stock": 42, "rating": 4.4, "reviews": 157, "createdAt": "2023-07-08T00:00:00.000Z", "updatedAt": "2023-10-30T00:00:00.000Z", "description": "Track your fitness and stay connected with this smart watch", "featured": true, "discountPercentage": 10, "attributes": { "color": "Silver", "size": "One Size", "weight": 48 } }, { "name": "Bluetooth Speaker", "price": 89.99, "category": "Electronics", "brand": "AudioPhile", "tags": ["speaker", "bluetooth", "portable"], "stock": 65, "rating": 4.3, "reviews": 93, "createdAt": "2023-03-25T00:00:00.000Z", "updatedAt": "2023-08-15T00:00:00.000Z", "description": "Portable bluetooth speaker with rich sound", "featured": false, "discountPercentage": 0, "attributes": { "color": "Red", "size": "Medium", "weight": 540 } }, { "name": "Running Shoes", "price": 129.99, "category": "Footwear", "brand": "SportElite", "tags": ["shoes", "running", "sports"], "stock": 85, "rating": 4.6, "reviews": 214, "createdAt": "2023-05-12T00:00:00.000Z", "updatedAt": "2023-11-05T00:00:00.000Z", "description": "Lightweight professional running shoes", "featured": true, "discountPercentage": 0, "attributes": { "color": "Black/Green", "size": "42", "weight": 305 } }, { "name": "Coffee Maker", "price": 149.99, "category": "Appliances", "brand": "HomeComfort", "tags": ["coffee", "kitchen", "automatic"], "stock": 31, "rating": 4.8, "reviews": 176, "createdAt": "2023-02-18T00:00:00.000Z", "updatedAt": "2023-09-10T00:00:00.000Z", "description": "Automatic coffe

Introduction
MongoDB's aggregation framework offers powerful data processing capabilities, and the MongoDB $facet
stage is one of its most versatile features.
This guide explores how to effectively use $facet
to run multiple aggregation pipelines simultaneously, saving you time and improving application performance.
What is the MongoDB $facet Stage?
The $facet
stage lets you process the same input documents through multiple aggregation pipelines within a single operation.
Instead of making several database queries, you can execute multiple analyses at once and receive results in a structured document.
{
$facet: {
"outputName1": [ 1> ],
"outputName2": [ 2> ],
// More pipelines as needed
}
}
Without $facet
, you have to send two requests to get outputName1
and outputName2
.
Key Benefits of Using $facet
- Reduced database round trips.
- Parallel processing of the same dataset.
- Simplified client-side code.
- More efficient resource utilization.
- Cleaner API responses
Sample Dataset: E-commerce Products Collection
To make the examples concrete and easy to follow, we'll use a single e-commerce products collection throughout this guide.
You can create this sample dataset in your local MongoDB instance to test the examples yourself. You can simply follow up with me using MongoDB Compass.
Data seeding
You can use MongoDB Compass to insert the following products in your products
collection.
[
{
"name": "Smartphone X1",
"price": 699.99,
"category": "Electronics",
"brand": "TechCo",
"tags": ["smartphone", "5G", "camera"],
"stock": 123,
"rating": 4.5,
"reviews": 382,
"createdAt": "2023-06-15T00:00:00.000Z",
"updatedAt": "2023-12-01T00:00:00.000Z",
"description": "Latest flagship smartphone with advanced camera system",
"featured": true,
"discountPercentage": 5,
"attributes": {
"color": "Black",
"size": "6.4 inch",
"weight": 189
}
},
{
"name": "Wireless Earbuds Pro",
"price": 149.99,
"category": "Electronics",
"brand": "AudioPhile",
"tags": ["wireless", "earbuds", "noise-cancelling"],
"stock": 78,
"rating": 4.7,
"reviews": 204,
"createdAt": "2023-08-21T00:00:00.000Z",
"updatedAt": "2023-11-15T00:00:00.000Z",
"description": "Premium wireless earbuds with active noise cancellation",
"featured": true,
"discountPercentage": 0,
"attributes": {
"color": "White",
"size": "Standard",
"weight": 58
}
},
{
"name": "Cotton T-Shirt",
"price": 19.99,
"category": "Clothing",
"brand": "FashionBasics",
"tags": ["t-shirt", "cotton", "casual"],
"stock": 243,
"rating": 4.2,
"reviews": 119,
"createdAt": "2023-04-10T00:00:00.000Z",
"updatedAt": "2023-09-20T00:00:00.000Z",
"description": "Comfortable everyday cotton t-shirt",
"featured": false,
"discountPercentage": 15,
"attributes": {
"color": "Blue",
"size": "M",
"weight": 150
}
},
{
"name": "Smart Watch SE",
"price": 299.99,
"category": "Electronics",
"brand": "TechCo",
"tags": ["smartwatch", "fitness", "bluetooth"],
"stock": 42,
"rating": 4.4,
"reviews": 157,
"createdAt": "2023-07-08T00:00:00.000Z",
"updatedAt": "2023-10-30T00:00:00.000Z",
"description": "Track your fitness and stay connected with this smart watch",
"featured": true,
"discountPercentage": 10,
"attributes": {
"color": "Silver",
"size": "One Size",
"weight": 48
}
},
{
"name": "Bluetooth Speaker",
"price": 89.99,
"category": "Electronics",
"brand": "AudioPhile",
"tags": ["speaker", "bluetooth", "portable"],
"stock": 65,
"rating": 4.3,
"reviews": 93,
"createdAt": "2023-03-25T00:00:00.000Z",
"updatedAt": "2023-08-15T00:00:00.000Z",
"description": "Portable bluetooth speaker with rich sound",
"featured": false,
"discountPercentage": 0,
"attributes": {
"color": "Red",
"size": "Medium",
"weight": 540
}
},
{
"name": "Running Shoes",
"price": 129.99,
"category": "Footwear",
"brand": "SportElite",
"tags": ["shoes", "running", "sports"],
"stock": 85,
"rating": 4.6,
"reviews": 214,
"createdAt": "2023-05-12T00:00:00.000Z",
"updatedAt": "2023-11-05T00:00:00.000Z",
"description": "Lightweight professional running shoes",
"featured": true,
"discountPercentage": 0,
"attributes": {
"color": "Black/Green",
"size": "42",
"weight": 305
}
},
{
"name": "Coffee Maker",
"price": 149.99,
"category": "Appliances",
"brand": "HomeComfort",
"tags": ["coffee", "kitchen", "automatic"],
"stock": 31,
"rating": 4.8,
"reviews": 176,
"createdAt": "2023-02-18T00:00:00.000Z",
"updatedAt": "2023-09-10T00:00:00.000Z",
"description": "Automatic coffee maker with timer",
"featured": true,
"discountPercentage": 5,
"attributes": {
"color": "Silver/Black",
"size": "Standard",
"weight": 2500
}
},
{
"name": "Backpack",
"price": 59.99,
"category": "Accessories",
"brand": "TravelPro",
"tags": ["backpack", "travel", "waterproof"],
"stock": 112,
"rating": 4.5,
"reviews": 88,
"createdAt": "2023-06-30T00:00:00.000Z",
"updatedAt": "2023-10-15T00:00:00.000Z",
"description": "Durable waterproof backpack for travel",
"featured": false,
"discountPercentage": 0,
"attributes": {
"color": "Navy Blue",
"size": "25L",
"weight": 780
}
},
{
"name": "Yoga Mat",
"price": 29.99,
"category": "Fitness",
"brand": "SportElite",
"tags": ["yoga", "fitness", "exercise"],
"stock": 95,
"rating": 4.2,
"reviews": 67,
"createdAt": "2023-04-05T00:00:00.000Z",
"updatedAt": "2023-08-25T00:00:00.000Z",
"description": "Non-slip yoga mat for comfortable exercise",
"featured": false,
"discountPercentage": 0,
"attributes": {
"color": "Purple",
"size": "Standard",
"weight": 900
}
},
{
"name": "4K Smart TV",
"price": 799.99,
"category": "Electronics",
"brand": "ViewTech",
"tags": ["tv", "4k", "smart"],
"stock": 28,
"rating": 4.7,
"reviews": 153,
"createdAt": "2023-05-20T00:00:00.000Z",
"updatedAt": "2023-12-10T00:00:00.000Z",
"description": "55-inch 4K smart TV with HDR",
"featured": true,
"discountPercentage": 8,
"attributes": {
"color": "Black",
"size": "55 inch",
"weight": 15200
}
}
]
Essential Examples Using Our Products Collection
Now that we have our products collection, let's explore how to use $facet
with this dataset.
Example 1: Product Dashboard Analytics
To create a comprehensive product dashboard with multiple metrics in one query, use the following aggregation pipelines:
[
{
$facet: {
"categoryCounts": [
{ $group: { _id: "$category", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
],
"brandAnalysis": [
{ $group: { _id: "$brand", productCount: { $sum: 1 } } },
{ $sort: { productCount: -1 } }
],
"priceStats": [
{ $group: {
_id: null,
avgPrice: { $avg: "$price" },
minPrice: { $min: "$price" },
maxPrice: { $max: "$price" },
totalProducts: { $sum: 1 }
}
}
],
"featuredProducts": [
{ $match: { featured: true } },
{ $sort: { price: -1 } },
{ $limit: 3 },
{ $project: {
_id: 1,
name: 1,
price: 1,
category: 1,
brand: 1
}
}
]
}
}
]
I think things become more clear now. Again without $facet
, to get categoryCounts
, brandAnalysis
, priceStats
, and featuredProducts
, you have to send 4 different requests, but with $facet
, you do it in one request.
This query produces a result with:
- Distribution of products across categories
- Brand distribution analysis
- Price statistics across all products
- Top 3 featured products by price
Example 2: Inventory and Pricing Analysis
To analyze inventory status and pricing in a single query, use the following aggregation pipelines:
[
{
$facet: {
"stockStatus": [
{
$bucket: {
groupBy: "$stock",
boundaries: [0, 30, 50, 100, 200],
default: "200+",
output: {
count: { $sum: 1 },
products: { $push: { id: "$_id", name: "$name", stock: "$stock" } }
}
}
}
],
"priceRanges": [
{
$bucket: {
groupBy: "$price",
boundaries: [0, 50, 100, 300, 500],
default: "500+",
output: {
count: { $sum: 1 },
avgRating: { $avg: "$rating" }
}
}
}
],
"discountedProducts": [
{ $match: { discountPercentage: { $gt: 0 } } },
{ $sort: { discountPercentage: -1 } },
{ $project: {
name: 1,
originalPrice: "$price",
discountPercentage: 1,
finalPrice: {
$multiply: [
"$price",
{ $subtract: [1, { $divide: ["$discountPercentage", 100] }] }
]
}
}
}
]
}
}
]
If you're not familiar with MongoDB's $bucket
stage, think of it as a way to group documents based on a specified range of values---like creating buckets for price ranges. For instance, if your product prices span from 0
to 500
, you can define boundaries like [0, 50, 100, 300, 500]
. The $bucket
stage will then group documents falling within each of those ranges, allowing you to perform separate aggregations or analyses for each price segment.
Anyway, this single query provides:
- Products grouped by stock level
- Products grouped by price range with average rating
- All discounted products with calculated final prices
Example 3: Product Search with Faceted Navigation
To create a powerful product search with faceted navigation options, use the following aggregation pipelines:
[
{
$match: {
$or: [
{ name: { $regex: "smart", $options: "i" } },
{ description: { $regex: "smart", $options: "i" } },
{ tags: "smart" }
]
}
},
{
$facet: {
"searchResults": [
{ $sort: { rating: -1, reviews: -1 } },
{ $limit: 5 },
{ $project: {
name: 1,
price: 1,
rating: 1,
description: 1,
discountPercentage: 1,
category: 1,
brand: 1
}
}
],
"categoryFilters": [
{ $group: { _id: "$category", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
],
"brandFilters": [
{ $group: { _id: "$brand", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
],
"priceStats": [
{ $group: {
_id: null,
avgPrice: { $avg: "$price" },
minPrice: { $min: "$price" },
maxPrice: { $max: "$price" }
}
}
]
}
}
]
This provides a complete search experience with:
- Top 5 search results for "smart" products
- Category filter options with counts
- Brand filter options with counts
- Price statistics for the search results
Example 4: Products Pagination with Total Count
To implement efficient pagination that includes the total count, use the following aggregation pipelines:
// Use these variables in your JSON before trying
const pageSize = 3;
const pageNumber = 2;
const skip = pageSize * (pageNumber - 1);
// JSON
[
{
$facet: {
"paginatedProducts": [
{ $sort: { createdAt: -1 } },
{ $skip: skip },
{ $limit: pageSize },
{ $project: {
name: 1,
price: 1,
category: 1,
brand: 1,
rating: 1,
createdAt: 1
}
}
],
"totalCount": [
{ $count: "count" }
]
}
},
{
$project: {
products: "$paginatedProducts",
total: { $arrayElemAt: ["$totalCount.count", 0] },
page: { $literal: pageNumber },
pageSize: { $literal: pageSize },
totalPages: {
$ceil: {
$divide: [
{ $arrayElemAt: ["$totalCount.count", 0] },
pageSize
]
}
}
}
}
]
This query provides the following results in one request:
- Page 2 of products (items 4-6 when sorted by creation date)
- Total product count
- Current page number
- Page size
- Total number of pages
Optimization Best Practices
Using our products collection as context, here are key optimization strategies:
1. Filter Early
Always place a $match
stage before $facet
to reduce the document set:
[
{ $match: { stock: { $gt: 0 } } }, // Process only in-stock products
{
$facet: {
"categories": [ /* pipeline */ ],
"brands": [ /* pipeline */ ]
}
}
]
2. Project Only Necessary Fields
Reduce memory usage by keeping only needed fields:
[
{
$project: {
name: 1,
price: 1,
category: 1,
brand: 1,
stock: 1
}
},
{
$facet: {
/* pipelines using only these fields */
}
}
]
Keep in mind, $facet
can't exceed 100MB output per sub-pipeline, have a look here for more considerations.
3. Handle Empty Results
Transform empty arrays into usable values:
[
{
$facet: {
"outOfStock": [
{ $match: { stock: 0 } },
{ $count: "count" }
]
}
},
{
$project: {
outOfStockCount: {
$cond: {
if: { $eq: [{ $size: "$outOfStock" }, 0] },
then: 0,
else: { $arrayElemAt: ["$outOfStock.count", 0] }
}
}
}
}
]
When to Use MongoDB $facet vs. Multiple Queries
From previous examples, you might think, "$facet is awesome. OK, I will use it in everything".
Actually, our previous examples are very good candidates to use $facet
. However, you should not use it blindly.
Use $facet When:
- You need multiple aggregations on the same dataset:
$facet
lets you run several pipelines in parallel on the same input data, reducing duplication. - Atomicity is important: All facets run in a single aggregation, ensuring consistency (all data is from the same snapshot).
- You want all results returned in a single response: Convenient when you need structured, grouped data (e.g. counts, lists, stats) in one JSON document.
- You want to minimize round trips: Fewer database calls = lower latency and easier error handling compared to multiple individual queries.
- You're building dashboards or reports:
$facet
is great for generating multiple data views (e.g. filters, charts, summaries) in one call. - Input filtering is expensive: You can filter once and reuse the result across multiple facets instead of repeating the same filter logic.
Use Multiple Queries When:
- Performance is a concern:
If
$facet
is causing performance issues due to processing multiple pipelines in a single aggregation. - Queries are unrelated: When different parts of the data are not logically connected, and running them separately is cleaner.
- You need to run them in parallel: Multiple queries can be executed in parallel in your application to reduce potential latency.
- Results don't need to be in the same document: If you don't need all the results bundled together, separate queries can be simpler and more modular.
- Query complexity is too high for one pipeline:
Large and complex
$facet
operations can become hard to manage; splitting them improves readability and maintainability. - Each query uses different indexes:
Separate queries can take better advantage of indexing, while
$facet
runs on a single pipeline input. - Different queries have different execution frequencies: You may not need to run all the logic every time---multiple queries give you more control.
Conclusion
MongoDB's $facet
operator provides an elegant solution for executing multiple aggregation pipelines on your data in parallel.
By understanding its capabilities and limitations, you can create more efficient queries, reduce database load, and simplify your application architecture.
Think Of It
If you enjoyed this article, I'd truly appreciate it if you could share it--it really motivates me to keep creating more helpful content!
If you're interested in exploring more, check out these articles.
- MongoDB GridFS, Made Simple
- Do You Really Know, What Is Single Responsibility?
- Open-Closed Principle: The Hard Parts
- Strategy vs State vs Template Design Patterns
Thanks for sticking with me until the end--I hope you found this article valuable and enjoyable!