← Back to the Journal
Architecture · MongoDB

MongoDB Schema Design: Embedding vs. Referencing in Practice

AI-authored · reviewed by Connect·IT DBAs ·Jun 28, 2026·6 min read

Every MongoDB developer eventually faces the same question: should I put this data inside the document or keep it in a separate collection? The choice between embedding and referencing is the single most impactful schema design decision you will make. Get it right, and your queries are fast, your writes are atomic, and your data stays consistent. Get it wrong, and you will chase performance problems, hit document size limits, or wrestle with multi-document transactions that could have been avoided. This post gives you the practical rules, concrete examples, and a decision framework you can apply today.

Understanding Embedding and Referencing in MongoDB

Embedding means storing related data directly inside a document as a nested sub-document or array. Referencing means storing only an identifier (usually an ObjectId) and keeping the related data in a separate collection. MongoDB's document model supports both, and each has distinct trade-offs.

When you embed, you get atomicity for free—a single write operation updates the entire document. Reads are fast because all needed data comes back in one round trip. The cost is that you cannot query embedded data independently, and documents can grow beyond the 16 MB limit if you embed too much.

When you reference, you keep documents small and focused. You can update referenced data without touching the parent document. But reads often require additional queries or lookups (using $lookup), and maintaining consistency across collections may require multi-document transactions.

When to Embed: One-to-One and One-to-Few Relationships

Embedding is the natural choice for relationships where the related data is tightly coupled to the parent and rarely accessed on its own. One-to-one relationships—like a user and their profile—are perfect candidates. One-to-few relationships, such as a blog post with a small, bounded list of tags, also belong inside the document.

// User with embedded address (one-to-one)
{
  "_id": ObjectId("..."),
  "name": "Alice",
  "email": "alice@example.com",
  "address": {
    "street": "123 Main St",
    "city": "Springfield",
    "zip": "12345"
  }
}

// Blog post with embedded tags (one-to-few)
{
  "_id": ObjectId("..."),
  "title": "MongoDB Schema Design",
  "content": "...",
  "tags": ["database", "nosql", "mongodb"]
}

Embed when the child data is always accessed with the parent, when the number of children is small and bounded (typically fewer than a few hundred), and when you need atomic updates that affect both parent and child together. If you ever find yourself querying the embedded data alone, reconsider.

When to Reference: One-to-Many and Many-to-Many Relationships

When a parent can have thousands or millions of related items, embedding becomes impractical. A user with thousands of orders, or a product that appears in millions of line items, must be referenced. One-to-many and many-to-many relationships almost always require separate collections.

// User document (references orders)
{
  "_id": ObjectId("..."),
  "name": "Bob",
  "orders": [
    ObjectId("order1"),
    ObjectId("order2"),
    ObjectId("order3")
  ]
}

// Order document (referenced)
{
  "_id": ObjectId("order1"),
  "user_id": ObjectId("..."),
  "total": 99.99,
  "items": [...],
  "created_at": ISODate("2024-01-15T10:00:00Z")
}

Reference when the child collection grows unboundedly, when you need to query children independently (e.g., "find all orders from last month"), or when the same data is shared across multiple parents. Many-to-many relationships—like students and courses—also demand referencing, typically with a junction collection or arrays of IDs on both sides.

Rule of thumb: If you can count the related items on two hands, embed. If you need a scrollbar, reference.

Performance and Query Patterns: Read vs. Write Considerations

Embedding shines on read-heavy workloads where you always fetch the parent with its children. A single query returns everything, and you avoid the overhead of joins or application-side lookups. Referencing adds latency: every $lookup or additional query costs an extra round trip and more CPU on the database server.

On the write side, embedding gives you atomic updates—you can modify the parent and child in one operation. But updating a deeply nested array element requires the positional operator ($[elem]) and can be tricky. Referencing lets you update the child independently, which is simpler when children change frequently. However, maintaining consistency between collections may require transactions, which add overhead.

Consider your access patterns. If you read the parent and children together 90% of the time, embedding wins. If you update children constantly without touching the parent, referencing is cleaner.

Handling Data Growth and Document Size Limits

MongoDB enforces a 16 MB document size limit. Embedded arrays that grow without bound will eventually hit this ceiling. Even before that, large documents degrade write performance because the entire document must be rewritten on any field change (with the default power-of-two allocation).

Monitor document sizes using Object.bsonsize() in the shell or $bsonSize in aggregation. If a document regularly exceeds 1–2 MB, consider splitting it. A good rule is to keep documents under a few hundred kilobytes for most workloads.

// Check document size in the shell
db.products.find().forEach(function(doc) {
  print(Object.bsonsize(doc));
});

For embedded arrays that grow, use a sliding window or pagination pattern. Alternatively, move the growing data to a separate collection and reference it.

Practical Example: E-Commerce Product and Order Schema

Consider an e-commerce system. Products have attributes, categories, and reviews. Orders contain line items, shipping details, and payment info. A naive design might embed everything, but that leads to problems.

Embed product details (name, price, description) inside the product document. Reviews, however, are one-to-many and should be referenced—embedding thousands of reviews would blow past the 16 MB limit. Store only the latest few reviews or an average rating in the product document, and keep the full review list in a separate reviews collection.

// Product document (embeds attributes, references reviews)
{
  "_id": ObjectId("..."),
  "name": "Wireless Mouse",
  "price": 29.99,
  "attributes": { "color": "black", "connection": "bluetooth" },
  "avg_rating": 4.5,
  "recent_reviews": [
    { "user": "Alice", "rating": 5, "text": "Great mouse!" }
  ]
}

// Order document (embeds line items, references product IDs)
{
  "_id": ObjectId("..."),
  "user_id": ObjectId("..."),
  "items": [
    { "product_id": ObjectId("..."), "qty": 2, "price": 29.99 }
  ],
  "shipping": { "address": "...", "method": "ground" },
  "total": 59.98
}

Line items are embedded in orders because they are always read together and bounded (usually fewer than 50 items per order). Product IDs are referenced because products change independently and are shared across many orders.

Choosing the Right Approach: A Decision Framework

When designing a relationship, ask these questions in order:

  1. How often do you access the child data with the parent? If always, prefer embedding. If sometimes, consider referencing with selective embedding of summary data.
  2. How many children can exist per parent? Fewer than 100? Embed. Thousands or millions? Reference.
  3. Do you need to query children independently? If yes, reference. If no, embedding is simpler.
  4. Does the child data change independently? If the child updates frequently without the parent, reference. If they change together, embed.
  5. What is the growth rate? Unbounded growth means reference. Bounded, stable data can be embedded.
  6. Do you need atomicity? Embedding gives atomic updates. Referencing may require transactions.

When in doubt, start with referencing. It is easier to denormalize later by embedding frequently accessed fields than to split a bloated document.

The Short Version

Embed for one-to-one and one-to-few relationships where data is always read together and bounded. Reference for one-to-many and many-to-many relationships where children are independent or unbounded. Keep documents small, monitor sizes, and let your query patterns drive the decision. There is no universal right answer—only the right answer for your workload.

Tags Architecture MongoDB Databases

About this article

Drafted by Connect·IT's AI authoring agents and reviewed by our senior DBAs before publishing. Need this applied to your own systems? Talk to our team →