Unlike relational databases, MongoDB doesn't enforce a rigid table structure by default â instead, it gives you the flexibility to shape documents around how your application actually reads and writes data. Good schema design is arguably the single biggest factor in whether a MongoDB application performs well at scale. This tutorial covers modeling fundamentals, relationship strategies, common design patterns, and the tradeoffs behind each.
Information
đ 1. Introduction
In relational databases, schema design starts with normalization to eliminate redundancy. In MongoDB, schema design starts with a different question: "How will this data be read and written?" The right structure depends far more on access patterns than on theoretical data purity.
Tip
đ§ą 2. Data Modeling Fundamentals
Every modeling decision in MongoDB is a tradeoff between two core strategies: embedding related data directly inside a document, or referencing it in a separate document and joining when needed.
đ 3. Schema Design
Designing a schema in MongoDB typically follows a different order than in SQL: instead of modeling entities first and queries second, effective MongoDB design starts by listing your application's most important queries and shaping documents to answer them efficiently.
- Identify the application's read and write patterns.
- Determine which data is frequently accessed together.
- Decide between embedding and referencing for each relationship.
- Validate the design against expected document size and growth.
đ 4. Flexible Schema
MongoDB doesn't require every document in a collection to share the same structure. This flexibility is powerful, but it also means schema discipline becomes an application-level responsibility.
Documents with differing shapes
// Both documents can coexist in the same collection
{ name: "Mouse", price: 25.99 }
{ name: "Keyboard", price: 49.99, wireless: true, tags: ["accessory"] }Caution
đĻ 5. Embedding Documents
Embedding stores related data as a nested sub-document or array within the parent document, enabling retrieval in a single read.
Embedding an address inside a user document
{
_id: ObjectId("..."),
name: "Alice",
address: {
street: "12 MG Road",
city: "Chennai",
zip: "600001"
}
}Tip
đ 6. Referencing Documents
Referencing stores a related document's _id instead of the full data, requiring a separate query or $lookup to retrieve it.
Referencing a customer by ObjectId
// orders collection
{
_id: ObjectId("..."),
customerId: ObjectId("64f1a2b3c4d5e6f7890a1b2c"),
amount: 59.99
}Tip
1ī¸âŖâī¸1ī¸âŖ 7. One-to-One Relationships
A one-to-one relationship is most often best modeled by embedding, unless the embedded data is large or rarely accessed.
One-to-one via embedding
// Embedded (preferred for small, always-needed data)
{
name: "Alice",
profile: { bio: "Software engineer", avatarUrl: "..." }
}1ī¸âŖâī¸đĸ 8. One-to-Many Relationships
The right approach depends on how many "many" really is, and whether the child documents need to be queried independently.
Embedding a small, bounded list of reviews
{
name: "Wireless Mouse",
reviews: [
{ author: "Alice", rating: 5 },
{ author: "Bob", rating: 4 }
]
}Referencing when the child list could grow unbounded
// orders collection, referencing a customer
{
_id: ObjectId("..."),
customerId: ObjectId("64f1a2b3c4d5e6f7890a1b2c"),
amount: 59.99
}Important
đĸâī¸đĸ 9. Many-to-Many Relationships
Many-to-many relationships are typically modeled with references on both sides, or occasionally with an array of IDs on the side that's queried more frequently.
Modeling a many-to-many relationship with ID arrays
// students collection
{ name: "Alice", courseIds: [ObjectId("c1"), ObjectId("c2")] }
// courses collection
{ title: "Intro to MongoDB", studentIds: [ObjectId("s1"), ObjectId("s2")] }đ 10. Normalization
Normalization â splitting data into separate collections and referencing it â reduces duplication and keeps each source of truth in one place.
Note
đ 11. Denormalization
Denormalization â duplicating data across documents â optimizes for read performance at the cost of needing to keep copies in sync.
Denormalizing a product name into an order
// Denormalized: product name duplicated into the order
{
_id: ObjectId("..."),
productId: ObjectId("p1"),
productName: "Wireless Mouse", // duplicated for fast display
price: 25.99
}Tip
đ 12. Document Growth
Documents that grow unboundedly â such as an array that gains new elements forever â eventually cause performance issues and, in extreme cases, hit the 16MB document size limit.
Warning
â 13. Schema Validation
MongoDB supports optional JSON Schema validation at the collection level, enforcing structure even in a schema-flexible database.
Applying schema validation on collection creation
await db.createCollection("products", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "price"],
properties: {
name: { bsonType: "string" },
price: { bsonType: "number", minimum: 0 }
}
}
}
});đ 14. Data Integrity
Since MongoDB doesn't enforce foreign keys, referential integrity between collections must be maintained by the application layer or through periodic consistency checks.
Caution
đ 15. Embedded Arrays
Arrays embedded within a document are ideal for small, bounded lists that are always read together with the parent.
A simple embedded array
{
name: "Wireless Mouse",
tags: ["electronics", "wireless", "sale"]
}đī¸ 16. Nested Documents
Nested documents group related fields into a sub-object, improving organization without requiring a separate collection.
Grouping related fields with nesting
{
name: "Wireless Mouse",
dimensions: { length: 10.5, width: 6.2, height: 3.8, unit: "cm" }
}đĒŖ 17. Bucket Pattern
Groups many small, related records â typically time-series data â into fixed-size buckets instead of one document per data point, reducing index overhead.
Bucketing sensor readings by day
{
sensorId: "sensor-42",
date: "2026-08-02",
readings: [
{ time: "00:00", value: 21.5 },
{ time: "00:05", value: 21.7 }
// up to N readings per bucket
]
}Example
đ 18. Subset Pattern
Embeds only a small, frequently accessed subset of a large related dataset â like the 5 most recent reviews â while the full dataset lives in a separate collection.
Embedding only the most recent subset
{
name: "Wireless Mouse",
recentReviews: [
{ author: "Alice", rating: 5 },
{ author: "Bob", rating: 4 }
]
// full review history lives in a separate "reviews" collection
}đ§Ž 19. Computed Pattern
Precomputes and stores derived values â like totals or averages â to avoid recalculating them on every read.
Storing precomputed aggregate fields
{
name: "Wireless Mouse",
reviewCount: 128,
averageRating: 4.6 // recalculated periodically, not on every read
}Tip
đ 20. Polymorphic Pattern
Stores documents with different shapes in the same collection when they share common query patterns, distinguished by a type field.
Polymorphic documents distinguished by a type field
{ type: "book", title: "MongoDB Basics", pages: 240 }
{ type: "ebook", title: "MongoDB Basics", fileSizeMb: 4.2 }đ 21. Schema Evolution
As applications evolve, schemas inevitably change. MongoDB's flexibility allows incremental migration â old and new document shapes can coexist during a transition.
Gracefully handling schema evolution
// Handle both old and new shapes in application code
const price = doc.price ?? doc.pricing?.amount ?? 0;Hint
đī¸ 22. Real-World Design Patterns
Combining the patterns above produces schemas tailored to real application needs. A typical e-commerce order might combine embedding, referencing, and denormalization together.
Combining multiple patterns in one schema
{
_id: ObjectId("..."),
customerId: ObjectId("c1"), // reference
customerName: "Alice", // denormalized for display
items: [ // embedded (bounded per order)
{ productId: ObjectId("p1"), name: "Mouse", price: 25.99, qty: 2 }
],
totalAmount: 51.98, // computed
status: "completed",
createdAt: new Date()
}đ 23. Best Practices
- Design around access patterns, not abstract entity relationships.
- Embed data that's small, bounded, and read together; reference data that's large or independently queried.
- Apply $jsonSchema validation to catch malformed documents early.
- Watch for unbounded array growth and apply the bucket or subset pattern when needed.
- Revisit schema decisions as query patterns change over the application's lifetime.
â ī¸ 24. Common Mistakes
- Modeling MongoDB schemas exactly like relational tables, leading to excessive $lookup joins.
- Embedding unbounded arrays that grow indefinitely, eventually approaching the 16MB document limit.
- Over-normalizing data that's always read together, adding unnecessary query complexity.
- Skipping schema validation entirely, allowing inconsistent document shapes to accumulate.
- Denormalizing frequently changing fields, leading to expensive synchronization across many documents.
Best Practice
â 25. Frequently Asked Questions
Yes. Every MongoDB document has a maximum size of 16MB, which is one reason to avoid unbounded embedding.
No. $lookup is a normal, supported part of MongoDB's query capabilities â the goal is to minimize unnecessary joins, not eliminate them entirely.
Yes. MongoDB's flexible schema allows documents of different shapes to coexist, enabling gradual, low-risk migrations.