RizTech Academy logo
RizTech Academy
Document Databases in DepthLesson 2 of 740 min

Embed or reference: the modelling decision

This is the decision that defines document modelling, and the one people get wrong. For any two related things — a book and its author, a post and its comments, an order and its customer — you choose: embed the related data inside the document, or reference it by id and fetch it separately.

Get this right and MongoDB is a pleasure. Get it wrong and you have a slow, inconsistent mess that a relational database would have prevented.

The two options

Embed — the related data lives inside:

{
  _id: 1,
  title: "The Long Monsoon",
  author: { name: "Meera Kulkarni", city: "Pune" },     // embedded
  categories: ["fiction", "literary"]                   // embedded
}

Reference — the related data lives elsewhere, pointed at by id:

// book
{ _id: 1, title: "The Long Monsoon", author_id: 17, category_ids: [3, 8] }
// author, in its own collection
{ _id: 17, name: "Meera Kulkarni", city: "Pune" }

Reference is the relational approach — a foreign key by another name. Embed is the thing document databases are for. The skill is knowing which each relationship wants.

Embed when…

The data is read together. If you almost always want the author's name when you want the book, embedding means one read instead of two. This is the primary reason.

The embedded data belongs to the parent — it has no independent life. A shipping address on an order, the line items of an invoice, the paragraphs of a document. Nobody queries "all shipping addresses" independent of their orders. It is part of the order.

The relationship is one-to-one or one-to-few. A book has one author-credit; an order has a handful of line items. "Few" is the operative word — bounded and small.

You want it to change atomically with the parent. A single-document write in MongoDB is atomic. Embed the line items and adding one is one atomic operation with no transaction needed — this is a genuine strength.

Reference when…

The data is large or unbounded. A post's comments can grow without limit; embedding them marches toward the 16 MB wall and makes loading the post slow, because you fetch 50,000 comments to show the title. Unbounded arrays are the number-one document-modelling mistake.

The data is shared by many parents. An author writes many books. Embed the author in each and renaming them means updating every book — the update anomaly again. Reference the author once and a rename is one write.

The data has independent life. If you query authors on their own — "authors in Pune", "authors with more than five books" — they want to be their own collection, not scattered inside books.

The relationship is many-to-many. Books and categories both ways. This is a join table relationally, and referencing (an array of ids on one or both sides) is the document equivalent.

The embedded data changes far more often than the parent. A book's live loan count, updated on every borrow, does not belong embedded in a rarely-changing book document.

The decision, as a table

Question Embed Reference
Read together? Yes No
Belongs to the parent only? Yes No, shared
How many? One, or a few (bounded) Many, or unbounded
Queried independently? No Yes
Changes with the parent? Yes Independently

When it points both ways, the deciding questions are "how many" and "is it bounded". One and bounded: embed. Many or unbounded: reference. That single test resolves most cases.

The worked cases

Book → author: reference. An author has many books and independent life ("authors in Pune"). Embedding duplicates the name across every book and makes a rename a mass update.

Order → line items: embed. The items belong to the order, are read with it, are bounded (an order has tens of items, not millions), and you want them to change atomically with the order.

Post → comments: reference (usually). Comments are unbounded — a viral post has tens of thousands. Embed a few recent ones for fast display and keep the full set in their own collection. This hybrid is common and sensible.

Product → category: reference. Categories are shared across many products and queried on their own.

User → profile: embed. One-to-one, always read together, belongs to the user.

The extended-reference pattern, which is the pragmatic middle

Pure reference means a $lookup (a join) on every read. Pure embed means duplication and drift. The middle ground — and the pattern you will use most in practice — is to embed the one or two fields you always display, and reference for the rest:

{
  _id: 1,
  title: "The Long Monsoon",
  author: { _id: 17, name: "Meera Kulkarni" },    // just id + name, for display
  // full author record — bio, city, all their books — lives in the authors collection
}

Now listing books shows the author name with no join, and the rare "everything about this author" query follows the id. You have accepted a small, deliberate duplication — the name — to avoid a join on the common path.

The cost is explicit and you must own it: when an author is renamed, you update the authors collection and every book's embedded copy. That is a background job, and it is acceptable precisely because names change rarely and the read is frequent. This is denormalisation from module 8, chosen on purpose, with the reconciliation acknowledged — not stumbled into.

Anti-patterns, each of which I have seen in production

The unbounded array. { user_id: 1, events: [ ... millions ... ] }. It hits 16 MB, or just becomes slow because every read drags the whole array. Events belong in their own collection with a user_id. If an array has no natural upper bound, do not embed it.

The massive document. Cramming everything about an entity into one document because you can. You then transfer and deserialise the whole thing to read one field. Documents should be the size of what you actually read together.

Over-referencing — MongoDB as a relational database. Everything in its own collection, joined with $lookup everywhere. You have a relational schema with a weaker query language and no foreign keys. If your data is this relational, use PostgreSQL — this is the "just use MongoDB" mistake made concrete.

Embedding shared, mutable data. The author's name in every book. Fine if it never changes; a maintenance burden the moment it does.

The question that decides it

For any relationship, ask: "When I read the parent, do I want this data, and does this data belong only to this parent?"

  • Yes to both → embed.
  • Shared, or unbounded, or independently queried → reference.
  • Want the common field cheaply but not the whole thing → extended reference.

And the meta-point: in MongoDB you model for your queries, not for your data. Relational design starts from the data and normalises; document design starts from how you read it and shapes the documents to match. Know your access patterns first. If you do not know them yet, that itself is a reason to start relational, where the shape is not baked into every read.

Check your work

The two options. Embed (data inside the document) or reference (data elsewhere, by id).

Which is the relational approach. Reference — a foreign key by another name.

The primary reason to embed. The data is read together, so it is one read not two.

Three more reasons to embed. It belongs only to the parent, it is one-or-few and bounded, and you want it to change atomically with the parent.

The number-one modelling mistake. Embedding an unbounded array.

Four reasons to reference. Large or unbounded data, data shared by many parents, data with independent life, and many-to-many relationships.

The deciding test when it points both ways. How many, and is it bounded — one and bounded embed, many or unbounded reference.

Why book→author is reference but order→items is embed. Authors are shared and independent; items belong to the order, are bounded, and change with it.

The extended-reference pattern. Embed the one or two display fields, reference the rest — accepting a small deliberate duplication to avoid a join on the common path.

Its acknowledged cost. A rename must update every embedded copy, via a background job.

Four anti-patterns. Unbounded arrays, massive documents, over-referencing (Mongo as relational), embedding shared mutable data.

The meta-principle. Model for your queries, not for your data.

Practice

  1. For book→author, write both the embedded and referenced versions. State which you would pick and why.
  2. Do the same for order→line-items and reach the opposite conclusion.
  3. Model post→comments three ways: fully embedded, fully referenced, and the hybrid. Note when each breaks.
  4. Take a one-to-many where the "many" is unbounded and embed it. Estimate when it hits 16 MB.
  5. Build the extended-reference book with { _id, name } embedded, and write the rename job that keeps the copies in step.
  6. Model a many-to-many (books and categories) with referenced id arrays and query it both directions.
  7. Find a real entity you would model as one big document, then split it by what is actually read together.
  8. Take a schema that is "MongoDB as relational" — everything referenced — and argue whether it should be PostgreSQL instead.
  9. Write the access patterns (the queries) for an application first, then design its documents from them. Notice the design falling out of the queries.

Official documentation

Next: querying documents.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship