Common Modeling Patterns

Introduction

Beyond embed-vs-reference, MongoDB practitioners reuse named patterns for catalogs, time series, social feeds, and tags. This chapter walks through one-to-few, one-to-many, many-to-many, bucket, attribute, and subset patterns with mongosh examples you can adapt to Blog project and Product catalog project.

Prerequisites

One-to-Few: Embed in Parent

Use when: child count is small and stable (≤ ~20).

javascript
db.users.insertOne({
  name: "Ada",
  addresses: [
    { label: "home", city: "London", zip: "SW1A" },
    { label: "office", city: "London", zip: "EC1A" }
  ]
})

Query:

javascript
db.users.find({ "addresses.label": "home" })

One-to-Many: Array vs Child Collection

Pattern A — Array of subdocuments (bounded)

Good for order with few line items:

javascript
db.orders.insertOne({
  orderNumber: "ORD-1001",
  customerId: ObjectId("665f0123456789abcdef01234"),
  items: [
    { sku: "KB-01", qty: 1, price: NumberDecimal("129.99") },
    { sku: "MS-02", qty: 2, price: NumberDecimal("29.99") }
  ],
  total: NumberDecimal("189.97"),
  createdAt: new Date()
})

Pattern B — Child collection (unbounded)

order_items with orderId:

javascript
db.order_items.insertMany([
  { orderId: ObjectId("..."), sku: "KB-01", qty: 1 },
  { orderId: ObjectId("..."), sku: "MS-02", qty: 2 }
])

Index { orderId: 1 } for fast find({ orderId }).

Choose B when line items or comments can grow without bound.

Many-to-Many: Array of IDs vs Junction Collection

Tags on posts — array of tag IDs

javascript
const tagDb = db.tags.insertOne({ name: "mongodb", slug: "mongodb" }).insertedId
const tagPy = db.tags.insertOne({ name: "python", slug: "python" }).insertedId
 
db.posts.insertOne({
  title: "PyMongo Tips",
  slug: "pymongo-tips",
  tagIds: [tagDb, tagPy]
})
 
db.posts.find({ tagIds: tagDb })

Junction collection (with extra fields)

When relationship has metadata (taggedAt, weight):

javascript
db.post_tags.insertOne({
  postId: ObjectId("..."),
  tagId: tagDb,
  taggedAt: new Date()
})

Query posts for a tag via junction:

javascript
const links = db.post_tags.find({ tagId: tagDb }).map((l) => l.postId)
db.posts.find({ _id: { $in: links } })

Attribute Pattern (Polymorphic Products)

Different product types share name, price, but specs differ:

Query phones with 256GB:

javascript
db.products.find({
  category: "phone",
  "attributes.storage": "256GB"
})

Alternative: attributes as array of { key, value } pairs for dynamic faceted search—trade query simplicity for flexibility.

Bucket Pattern (Time Series)

IoT or logs: bucket readings by hour/day in one document.

javascript
db.sensor_buckets.insertOne({
  sensorId: "temp-01",
  date: "2026-05-28",
  hour: 14,
  readings: [
    { t: new Date("2026-05-28T14:00:00Z"), v: 22.1 },
    { t: new Date("2026-05-28T14:05:00Z"), v: 22.3 }
  ],
  count: 2,
  avg: 22.2
})

Append with $push + $inc; cap array with $slice in $push.

Benefits: fewer documents than one reading per doc; queries by bucket key.

Compare Redis streams for real-time ingest — Redis Streams vs MongoDB for durable analytics storage.

Subset Pattern (Latest N Embedded)

Store only recent comments on post; full history elsewhere.

javascript
db.posts.updateOne(
  { slug: "pymongo-tips" },
  {
    $push: {
      recentComments: {
        $each: [{ author: "Dan", body: "Thanks!", at: new Date() }],
        $sort: { at: -1 },
        $slice: 5
      }
    }
  }
)

$slice: 5 keeps newest five in array. Archive all comments in comments collection.

Extended Reference (URL + Metadata)

Store denormalized snippet + id for display without join:

javascript
db.posts.insertOne({
  title: "Weekly Update",
  author: {
    id: ObjectId("665f0123456789abcdef01234"),
    name: "Ada Lovelace",
    avatarUrl: "/avatars/ada.png"
  }
})

Update author.name in all posts when user renames—or accept staleness and refresh on read from users.

Pattern Selection Table

PatternGrowthQueryUpdate
One-to-few embedLowSingle readParent rewrite
One-to-many arrayMediumSingle read$push / $pull
One-to-many collectionHighIndexed parentIdChild docs only
Many-to-many junctionHighTwo-step or $lookupJunction rows
AttributeVariesField per categoryPartial $set
BucketHigh volumeBy bucket key$push readings
SubsetHigh childrenFast homepageSync subset job

FAQ

Which pattern for blog comments?

Few comments: embed. Production CMS: comments collection + optional subset on post — Blog project.

Duplicate SQL EAV table?

Attribute pattern resembles EAV—MongoDB makes it natural without extra join tables.

Sharding large buckets?

Shard key on sensorId + datechapter 15.

Validate attribute shape?

$jsonSchema per category via partial validators — advanced; see chapter 14.

ORM for patterns?

PyMongo maps documents directly—design in code + validation — PyMongo.