█
LastWrite
  • > Curriculum
  • > Pricing
  • > For Educators
  • > About
  • > Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
LastWrite

Structured computer science lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 LastWrite. All rights reserved.
Curriculum/Programming Languages/MongoDB/Schema Design (Embed vs Reference)
55 minIntermediate

Schema Design (Embed vs Reference)

After this lesson, you will be able to: Decide between embedding and referencing documents, design for query patterns, apply one-to-many + many-to-many patterns.

Schema design is where Mongo developers separate from beginners. Embed for read speed; reference for write flexibility. Design for HOW you query, not just what you store.

Prerequisites:Indexing in MongoDB

Embed vs reference

Embed (nested doc): data lives inside the parent. Fast read; updates touch one doc. Use when the embedded data is bounded + always loaded with the parent. Reference (separate collection + ObjectId): data lives in another doc. Update once, reflected everywhere. Use when the data grows unbounded, is shared, or is frequently queried alone.

One-to-few (EMBED)

User + addresses (a person has ~1-3 addresses).

tsx
{
_id: ObjectId("..."),
name: "Alex",
addresses: [
{ type: "home", street: "123 Main", city: "NYC" },
{ type: "work", street: "456 5th", city: "NYC" }
]
}
// Read: ONE query gets user + addresses
// Update: db.users.updateOne({ _id }, { $push: { addresses: { ... } } })

One-to-many (REFERENCE)

User + posts (a user can have thousands of posts).

css
// users collection
{ _id: ObjectId("u1"), name: "Alex" }
// posts collection
{ _id: ObjectId("p1"), userId: ObjectId("u1"), title: "...", body: "..." }
{ _id: ObjectId("p2"), userId: ObjectId("u1"), title: "...", body: "..." }
// Index posts.userId for fast lookup
db.posts.createIndex({ userId: 1, createdAt: -1 });
// Read user's posts
db.posts.find({ userId: ObjectId("u1") }).sort({ createdAt: -1 });

Many-to-many

Students + courses.

css
// Pattern 1: array of refs on one side (works if relationship is small)
{ _id: ObjectId("u1"), name: "Alex", courseIds: [ObjectId("c1"), ObjectId("c2")] }
// Pattern 2: explicit junction collection (relational style; works at any scale)
// enrollments
{ _id, userId, courseId, enrolledAt, grade }
// Pick by query pattern + scale:
// - Mostly 'what courses is Alex in?' → array of refs is fine
// - Need per-enrollment metadata (grade, dates)? → junction collection

💡 Design for the query

Mongo schema decisions are based on HOW you query, not what data fits theoretically. Make a list of your top 5 query patterns BEFORE designing the schema. If you always read X with its Y → embed Y. If Y can grow unbounded or is shared → reference Y. If you don't know your access patterns yet, stay simple + refactor when it hurts.

The 16 MB doc limit

Each Mongo document is capped at 16 MB. Sounds huge until you embed comments / messages / events. If a document grows unboundedly, you MUST reference, not embed. Common pattern: 'subset embedding', keep the last 10 comments embedded for fast first-page rendering; rest in separate collection with reference.

💡 Multi-document transactions (and when you need them)

Good schema design (embedding things that change together) means most updates touch one document, and a single-document update in MongoDB is atomic by default. But sometimes you must update several documents all-or-nothing, the classic example being moving money between two accounts. For that, MongoDB has multi-document ACID transactions (4.0+ on replica sets): `session.startTransaction()`, do your writes, then `commitTransaction()` or `abortTransaction()`. They work, but they are heavier than single-doc writes, so the idiomatic MongoDB approach is to design your schema so you rarely need them. If you find yourself reaching for transactions constantly, that is a signal your data might be relational and belongs in SQL.

Common mistakes

Embedding unbounded arrays (eventually hits 16 MB). Over-referencing (every query becomes a $lookup, using Mongo as bad SQL). Forgetting indexes on reference fields (slow joins via $lookup). Reaching for multi-document transactions on every write instead of designing the schema so single-document atomic updates suffice. Designing schema without knowing the queries.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←Indexing in MongoDB
Back to MongoDB
Mongoose (ODM)→