Embedding: Benefits, Limits, and Safe Array Design
When embedding is a good fit
Embedding places related values inside the parent document as nested documents or arrays. It is especially useful when:
- Child data is usually read with the parent.
- The child has no meaningful independent lifecycle.
- The number of children is bounded or naturally small.
- The parent and child should be updated atomically.
- A snapshot is needed even if the source record changes later.
Embedded one-to-one data
{
_id: ObjectId("65a000000000000000000020"),
username: "asha",
profile: {
displayName: "Asha Rao",
locale: "en-IN",
theme: "dark"
}
}
Embedded one-to-few data
{
_id: ObjectId("65a000000000000000000021"),
title: "MongoDB Notes",
tags: ["database", "mongodb", "backend"],
settings: {
visibility: "private",
allowComments: false
}
}
Array growth is a design constraint
An array is not automatically wrong, but an unbounded array is dangerous. For example, storing every click event, notification, or transaction inside a user's document causes the document to grow continuously. This can increase document rewrite work, create contention, complicate pagination, and eventually hit MongoDB's 16 MiB maximum BSON document size.
Avoid using an ever-growing array as a substitute for a queryable history collection. If the parent only needs the latest few items, keep a bounded subset or use a separate collection for the full history.
Bounded recent-items pattern
db.users.updateOne(
{ _id: userId },
{
$push: {
recentItems: {
$each: [{ itemId, viewedAt: new Date() }],
$sort: { viewedAt: -1 },
$slice: 10
}
}
}
);
This keeps only the ten most recent entries in recentItems. It is appropriate only if older entries are intentionally disposable or are stored elsewhere.
Nested document updates
Use dot notation to update a nested field without replacing the entire subdocument:
db.customers.updateOne(
{ _id: customerId },
{ $set: { "preferences.theme": "dark" } }
);
Setting preferences to a new object instead may replace the existing subdocument and remove keys that are not included in the replacement.
- Embedding:
Store related data inside the parent document. - Bounded array:
An array whose maximum size is intentionally constrained. - Dot notation:
Target a nested field without replacing the full nested object.
MongoDB documents have a 16 MiB BSON size limit. More importantly, arrays with unlimited growth can become operationally expensive long before reaching that limit.
// Retain only the five latest status events in this compact summary
db.jobs.updateOne(
{ _id: jobId },
{
$push: {
recentStatusEvents: {
$each: [{ status: "running", at: new Date() }],
$sort: { at: -1 },
$slice: 5
}
},
$set: { "settings.notifications": true }
}
);- Embed small, bounded, co-accessed data.
- Use
$slicewith$pushto maintain intentionally bounded recent-item lists. - Store complete histories separately when they can grow without limit.
Which design is most appropriate for a user's complete, potentially millions-of-events activity history?