Embedded Docs and Arrays

The true power of MongoDB lies in its ability to model complex, hierarchical data without shredding it into flat tables. This chapter explores Embedded Documents and Arrays, the building blocks of high-performance document models.

1. First Principles: Data Locality

In a relational database, retrieving a User and their Addresses requires joining two tables. This means the database engine might need to perform random I/O to seek two different locations on disk (or pages in memory).

In MongoDB, if the addresses are embedded in the user document, they are stored contiguously on disk. Reading the User reads the Addresses in a single sequential I/O operation. This Data Locality is key to MongoDB’s read performance.

Interactive: Data Locality vs Joins

Visualize the disk head movement (IOPS) required to fetch a User and their Orders.

User
Ord1
Ord2
User + Ord1 + Ord2
Seeks: 0 Time: 0ms

2. Embedded Documents

You can nest documents inside other documents. This is perfect for “Contains” relationships (e.g., A User contains a Profile).

Dot Notation

To access fields inside an embedded document, use Dot Notation.

[!NOTE] When using dot notation, you must enclose the path in quotes (e.g., "profile.bio").

Code Examples

Java

// Find users in "New York"
collection.find(eq("address.city", "New York"));

// Update the bio
collection.updateOne(
    eq("_id", userId),
    set("profile.bio", "Software Architect")
);

Go

// Find users in "New York"
filter := bson.D{{"address.city", "New York"}}
cursor, err := coll.Find(ctx, filter)

// Update the bio
update := bson.D{{"$set", bson.D{{"profile.bio", "Software Architect"}}}}
_, err := coll.UpdateOne(ctx, bson.D{{"_id", userId}}, update)

3. Working with Arrays

Arrays allow you to store lists of values or objects. This replaces the “Link Table” (Many-to-Many) or “Child Table” (One-to-Many) patterns in SQL.

Array Operators

  • $push: Appends a value to an array.
  • $addToSet: Appends a value only if it doesn’t exist (Set behavior).
  • $pull: Removes all instances of a value.
  • $pop: Removes the first or last element.

Advanced Querying: $elemMatch

When querying an array of objects, simple equality checks might not be enough.

Scenario: Find documents where the grades array contains a grade with score > 90 AND subject = "Math".

// ❌ WRONG: Finds a grade > 90 (maybe English) AND a grade = "Math" (maybe score 60)
db.students.find({ "grades.score": { $gt: 90 }, "grades.subject": "Math" })

// ✅ CORRECT: Finds a SINGLE element matching BOTH criteria
db.students.find({
  grades: { $elemMatch: { score: { $gt: 90 }, subject: "Math" } }
})

Interactive: $elemMatch Visualizer

See why simple dot notation fails for specific array element matching.

Document: Student

{
  "grades": [
  { "subject": "Math", "score": 80 },  // Index 0
  { "subject": "English", "score": 95 } // Index 1
  ]
}
Query: ...
Result will appear here...

Code Examples

Java

// Add a tag "sale" if not present
collection.updateOne(
    eq("_id", prodId),
    addToSet("tags", "sale")
);

// Remove "outdated" tag
collection.updateOne(
    eq("_id", prodId),
    pull("tags", "outdated")
);

Go

// Add a tag "sale" if not present
update := bson.D{{"$addToSet", bson.D{{"tags", "sale"}}}}
_, err := coll.UpdateOne(ctx, bson.D{{"_id", prodId}}, update)

// Remove "outdated" tag
update = bson.D{{"$pull", bson.D{{"tags", "outdated"}}}}
_, err = coll.UpdateOne(ctx, bson.D{{"_id", prodId}}, update)

4. Interactive: The Document Builder

Construct complex documents to understand nesting.

Construct Document

{
  "name": "User 1",
  "address": {},
  "tags": []
}