Map common relationship cardinalities to MongoDB document structures, including parent/child and extended-reference patterns, while making ownership and duplication semantics explicit.
One-to-One, One-to-Many, Many-to-Many, Parent/Child, and Extended Reference Patterns
Batch heterogeneous writes safely, interpret partial success, compare ordered and unordered execution, and use modern cross-namespace bulk APIs without assuming all-or-nothing behavior.
Learning outcomes
Entity-relationship diagrams describe cardinality, but they do not prescribe one MongoDB shape. AtlasMart has one-to-one account settings, one-to-many order lines, one-to-many reviews, many-to-many product categories, a hierarchical catalog, and orders that need a small purchase-time customer snapshot. Each relationship has different ownership, cardinality, and query behavior, so each deserves a different physical representation.
Map one-to-one and bounded one-to-many relationships to embedded structures when ownership and access align.
Use references for high-cardinality or independently managed one-to-many relationships.
Represent many-to-many relationships without copying entire mutable entities into every partner document.
Model trees with explicit parent/child references and understand traversal/query consequences.
Use an extended reference—a reference plus selected duplicated fields—only with an explicit freshness or snapshot contract.
Mandatory examples use MongoDB Community Server
8.3.8 in the pinned
mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim
image, a disposable standalone mongod published
only on 127.0.0.1:27044, and mongosh 2.10.0.
PyMongo examples target the 4.17 line where driver behavior
matters. Authentication and Transport Layer Security (TLS) are
intentionally disabled only inside this isolated loopback lab;
do not copy that posture to a shared or remotely reachable
server. The lab uses standalone default read/write concern
semantics, no replica-set or sharding guarantee is implied,
and cleanup removes atlasmart-mongo-ch06-l3. The
lab deliberately avoids $lookup and $graphLookup so
relationship modeling remains separate from the aggregation
material taught later. Query counts and stored structures are
enough to expose the tradeoffs here.
1. Cardinality describes count; ownership decides shape
A one-to-one relationship connects one parent to one related object. If that object is meaningless without the parent and is read/updated with it—such as account display settings—embedding usually makes the ownership explicit. If the related object has its own security, retention, or lifecycle boundary, a separate document may be more appropriate even though cardinality is one.
A one-to-many relationship can mean two very different things. An order normally owns a bounded set of line items; deleting the order may delete the lines, and reading the order needs them. Reviews of a popular product can grow indefinitely and be moderated independently. Both are one-to-many, but only the former naturally fits one aggregate document.
2. Many-to-many relationships usually demand a deliberate reference strategy
Products can belong to multiple categories, and each category contains many products. Embedding full category documents into products and full product documents into categories creates two-sided duplication and difficult update fan-out. A simpler model stores canonical category documents and bounded identifiers on the product (or a relationship collection when relationship metadata/cardinality demands it). Indexes on those identifier arrays can support reverse lookup later.
Many-to-many modeling is not “references always win.” If one side contains immutable, tiny labels that are always read with the other side, a duplicated subset may be justified. The key is to identify which side owns truth and whether the copy is a cache, snapshot, or independent value.
3. Parent/child and extended-reference patterns solve different problems
A parent reference stores each node with the identifier of its parent. It keeps node documents small and makes direct-parent lookup trivial; walking the full ancestor/descendant tree requires repeated queries or later aggregation traversal. A child reference stores child identifiers on the parent, which is attractive only while the child set remains safely bounded.
An extended reference stores a normal reference
plus selected fields copied from the referenced document so the
common read avoids another fetch. AtlasMart’s order can store
customer.id plus
displayNameAtOrder and tierAtOrder.
Those copied fields are historical snapshots. If they were
intended to represent current customer data instead, AtlasMart
would need synchronization and reconciliation.
That preserves normalized structure but throws away document-locality and single-document atomicity opportunities. The opposite mistake—copying every related object into every document—creates unbounded growth and synchronization. Repair by assigning an aggregate owner and a bound for each relationship.
4. Run the relationship-pattern lab
docker rm -f atlasmart-mongo-ch06-l3 2>/dev/null || truedocker run --name atlasmart-mongo-ch06-l3 -p 127.0.0.1:27044:27017 -d mongodb/mongodb-community-server:8.3.8-ubuntu2204-slimmongosh "mongodb://127.0.0.1:27044/atlasmart?directConnection=true" --quiet --eval 'printjson({version:db.version(), hello:db.hello().isWritablePrimary}); printjson(db.getSiblingDB("admin").runCommand({getParameter:1,featureCompatibilityVersion:1}))'
db.accounts_rel.drop();db.products_rel.drop();db.reviews_rel.drop();db.categories_rel.drop();db.nodes_rel.drop();db.orders_extref.drop();// One-to-one: settings are owned by and usually read with the account.db.accounts_rel.insertOne({_id:"acct-1",email:"buyer@example.test",settings:{locale:"en",marketing:false}});// One-to-many bounded/owned: order lines live inside an order.const order={_id:"order-700",customerId:"cust-7",lines:[ {sku:"p-1",qty:1,unitPriceCents:2500}, {sku:"p-2",qty:2,unitPriceCents:900}]};db.orders_extref.insertOne({...order,customer:{id:"cust-7",displayNameAtOrder:"Mina",tierAtOrder:"gold"}});// One-to-many high-cardinality: reviews reference product.db.products_rel.insertOne({_id:"p-1",name:"Atlas Camera"});db.reviews_rel.insertMany(Array.from({length:12},(_,i)=>({_id:`rev-${i+1}`,productId:"p-1",rating:(i%5)+1})));// Many-to-many: category documents and product carries bounded category identifiers.db.categories_rel.insertMany([{_id:"cat-photo",name:"Photography"},{_id:"cat-travel",name:"Travel"}]);db.products_rel.updateOne({_id:"p-1"},{$set:{categoryIds:["cat-photo","cat-travel"]}});// Parent references: each node points to its immediate parent; root has null parent.db.nodes_rel.insertMany([ {_id:"root",name:"Catalog",parentId:null}, {_id:"cameras",name:"Cameras",parentId:"root"}, {_id:"mirrorless",name:"Mirrorless",parentId:"cameras"}]);printjson({ oneToOneQueries:1, orderLineCount:db.orders_extref.findOne({_id:"order-700"}).lines.length, reviewCount:db.reviews_rel.countDocuments({productId:"p-1"}), manyToManyCategoryIds:db.products_rel.findOne({_id:"p-1"}).categoryIds, parentTraversalQueries:3, parentChain:[db.nodes_rel.findOne({_id:"mirrorless"}),db.nodes_rel.findOne({_id:"cameras"}),db.nodes_rel.findOne({_id:"root"})].map(x=>x._id), extendedReference:db.orders_extref.findOne({_id:"order-700"}).customer});print("extended reference update-amplification thought experiment");printjson({ordersContainingCustomerCopy:db.orders_extref.countDocuments({"customer.id":"cust-7"}),meaning:"copies are purchase-time historical fields; no live fan-out update is required"});
Expected evidence
The account needs one document read for settings. The order line count is two and remains inside its order aggregate. Twelve reviews exist as independently addressable documents. The product carries two category identifiers rather than duplicated full category objects. The tree shows an explicit three-node parent chain. The order’s extended reference contains only a small purchase-time customer subset.
Verification checklist
- One-to-one settings are clearly owned by the account.
- Bounded order lines and high-cardinality reviews use different one-to-many shapes.
- Many-to-many category relationships do not duplicate entire mutable entities bidirectionally.
- Tree nodes expose parent ownership explicitly.
- The extended reference documents the meaning of every duplicated field.
Check your understanding
- Why can two one-to-many relationships require different models?
- What is the risk of embedding full objects on both sides of many-to-many?
- What does a parent-reference tree optimize?
- What is an extended reference?
- How do you make an extended-reference copy safe?
Review the answers
Because cardinality alone does not capture bound, ownership, read locality, update timing, lifecycle, or retention.
Updates can fan out across many copies, and the system may no longer have a clear canonical owner.
Small independently addressable nodes and direct parent lookup; full traversal requires additional query/aggregation work.
A reference identifier plus a deliberately selected duplicated subset of the target document to optimize a common read.
Define whether it is immutable snapshot data or mutable derived data, then define freshness, update, reconciliation, and failure behavior accordingly.
docker rm -f atlasmart-mongo-ch06-l3
The next lesson tackles what happens after a once-reasonable aggregate starts growing: large arrays, subsets, buckets, computed fields, and explicit bounds.
5. Production judgment: relationship labels are not aggregate boundaries
Use one-to-one and bounded one-to-many embedding when ownership, reads, writes, and lifecycle align. Use references or relationship documents when many-to-many cardinality, independent ownership, or growth would make embedded copies hard to maintain. Parent references keep tree nodes small but move traversal work to the query layer; extended references reduce read fan-out only by accepting deliberate duplication.
Operationally, monitor relationship cardinality distributions, lookup/batch counts, orphan/reference integrity checks, duplicate-copy drift, document sizes, and index footprint. Under sharding, decide which identifier drives locality and whether relationship traversal crosses shards. Under replication, read/write concern still determines visibility/durability; document structure alone does not. Security rules must prevent cross-tenant reference traversal and must validate both parent and child ownership.
Test deletion/reparenting, many-to-many churn, stale extended references, outlier cardinalities, and migration from embedded to referenced forms. Keep rollback by preserving canonical identifiers even inside snapshots where useful. No paid capability is required for the chapter. Lesson 4 addresses the next failure mode: aggregates that keep growing after the initial relationship choice looked reasonable.
Authoritative references
- MongoDB release notes — Current stable server series and patch history.
- MongoDB 8.3 release notes — 8.3.8 is the latest released 8.3 patch at review time; 8.3.9 is upcoming.
- mongosh release notes — mongosh 2.10.0 was released August 13, 2026.
- PyMongo release notes — Current PyMongo 4.17 line and driver changes.
- MongoDB schema design process — Official workload-first process: identify workload, map relationships, apply patterns, and create supporting indexes.
- Data modeling best practices — Official embedding-versus-referencing decision guidance.
- Document relationships — Official relationship-modeling entry point.
- One-to-one embedded relationship — Official one-to-one embedding example.
- One-to-many embedded relationship — Embedded child pattern when children belong to the parent.
- One-to-many references — Referenced child pattern for independently managed or growing children.
- Tree structures with parent references — Parent-reference hierarchy modeling.