The golden rules for deciding how to structure your MongoDB data.
Every time you have related data, ask yourself one question:
Does this data belong to ONE parent, or is it SHARED across many?
| Situation | Strategy | How |
|---|---|---|
| Data belongs to one parent | Embed it | Put it inside the document |
| Data is shared across many | Reference it | Use $lookup |
Rule: The data has one owner and is always read together with it.
Example: Comments on a blog post.
{
"_id": "post_1",
"title": "My First Post",
"comments": [
{ "author": "Alice", "text": "Great post!" },
{ "author": "Bob", "text": "Thanks for sharing." }
]
}Why embed?
- Comments are meaningless without the post.
- You always read comments at the same time as the post.
- No other document needs to reference the same comment.
Rule: The data is shared or reused across multiple documents.
Example: A product used in many orders.
// Orders collection
{ "_id": "order_1", "product_id": "prod_42", "qty": 2 }
{ "_id": "order_2", "product_id": "prod_42", "qty": 1 }
// Products collection (separate)
{ "_id": "prod_42", "name": "Keyboard", "price": 79.99 }Why reference?
- The product is shared across many orders.
- If the price changes, you update it in one place.
- Embedding would duplicate the product data everywhere.
If you are using
$lookupfor every single query, you are likely trying to build a SQL database in NoSQL.
This is the most common MongoDB mistake. If your queries always look like this:
// Bad sign — doing this for EVERY query
db.orders.aggregate([
{ $lookup: { from: "customers", ... } },
{ $lookup: { from: "products", ... } },
{ $lookup: { from: "discounts", ... } }
])Consider:
- Embedding more data directly into documents.
- Or switching to a relational database (SQL) if your data is truly relational.
Is the related data accessed together with the parent document?
│
├── YES → Is it owned by only ONE parent?
│ ├── YES → EMBED IT ✅
│ └── NO → REFERENCE IT 🔗
│
└── NO → Is it a small, rarely-changing lookup?
├── YES → EMBED A SNAPSHOT (e.g., store product name at time of order)
└── NO → REFERENCE IT 🔗
| Concept | SQL | MongoDB |
|---|---|---|
| Store related data | Foreign key + JOIN table | Embed array in document |
| Shared/reusable data | Foreign key | $lookup reference |
| Query an array | Complex JOIN | { field: "value" } |
| Filter | WHERE |
$match / .find({ }) |
| Select columns | SELECT col1, col2 |
{ col1: 1, col2: 1 } |
| Aggregate | GROUP BY + SUM() |
$group + $sum |
| Sort | ORDER BY |
$sort |
| Limit rows | LIMIT n |
$limit: n |