Skip to content

High-Yield Scenarios and Patterns

CRUD Operation Scenarios

Upsert Pattern

Scenario: Application needs to update a user's last login time, or create the user record if it does not exist.

Solution Pattern:

db.users.updateOne(
  { email: "user@example.com" },
  {
    $set: { lastLogin: new Date(), name: "John" },
    $inc: { loginCount: 1 }
  },
  { upsert: true }
);

Key Points: - upsert: true inserts if no match found - $set creates or updates fields - $inc creates field with value if not exists, increments if exists - Returns upsertedId if a new document was created

Common Distractors: - Using findOneAndUpdate without upsert (wrong - will not create) - Using insertOne with try/catch (wrong - race condition, not atomic) - Using replaceOne with upsert (wrong - replaces entire document)

Atomic Array Update

Scenario: Add a product to a user's cart if not already present, and update quantity if present.

Solution Pattern:

// Add if not present
db.carts.updateOne(
  { userId: "U123", "items.productId": { $ne: "P456" } },
  { $push: { items: { productId: "P456", quantity: 1, price: 29.99 } } }
);

// Update quantity if present
db.carts.updateOne(
  { userId: "U123", "items.productId": "P456" },
  { $inc: { "items.$.quantity": 1 } }
);

Common Distractors: - Using $addToSet (wrong - compares entire object, not just productId) - Not using positional $ operator (wrong - updates wrong array element) - Using two separate operations without checking (wrong - race condition)

Bulk Write for Data Migration

Scenario: Need to update 10,000 documents - add a new field and remove an old one.

Solution Pattern:

const ops = documents.map(doc => ({
  updateOne: {
    filter: { _id: doc._id },
    update: {
      $set: { newField: transformValue(doc.oldField) },
      $unset: { oldField: "" }
    }
  }
}));

db.collection.bulkWrite(ops, { ordered: false });

Key Points: - ordered: false allows parallel execution and continues on error - ordered: true (default) stops on first error - Returns bulkWriteResult with counts for inserts, updates, deletes

Common Distractors: - Individual updateOne calls (wrong - much slower, more network round trips) - Using updateMany without filter (wrong - cannot transform per-document) - Using ordered: true for independent operations (wrong - slower, unnecessary)

Index Scenarios

Optimizing a Slow Query

Scenario: Query db.orders.find({status: "active", customerId: "C123"}).sort({orderDate: -1}) is slow.

Solution Pattern:

// Apply ESR rule: Equality, Sort, Range
db.orders.createIndex({ status: 1, customerId: 1, orderDate: -1 });
// Or more optimized:
db.orders.createIndex({ customerId: 1, status: 1, orderDate: -1 });

Verify with explain():

db.orders.find({status: "active", customerId: "C123"})
  .sort({orderDate: -1})
  .explain("executionStats");
// Look for: IXSCAN (not COLLSCAN), nReturned close to totalKeysExamined

Common Distractors: - Index on {orderDate: -1} only (wrong - does not help filter) - Index on {orderDate: -1, status: 1} (wrong - sort field after range/equality) - Separate indexes on each field (wrong - compound index is more efficient)

TTL Index for Session Cleanup

Scenario: User sessions should automatically expire after 30 minutes of inactivity.

Solution Pattern:

db.sessions.createIndex(
  { lastActivity: 1 },
  { expireAfterSeconds: 1800 }  // 30 minutes
);

Key Points: - TTL index must be on a field with Date type - Background thread checks every 60 seconds - Only works on single-field indexes (not compound) - Documents deleted when lastActivity + 1800 seconds < now

Common Distractors: - TTL on a string field (wrong - must be Date type) - TTL on compound index (wrong - not supported) - Expecting instant deletion (wrong - background thread runs every 60s)

Data Modeling Scenarios

One-to-Many: Blog Posts and Comments

Scenario: Blog with posts that can have 0-1000 comments. Most reads fetch the post with recent comments.

Solution Pattern (Subset Pattern):

// Posts collection - embed recent comments
{
  _id: ObjectId("..."),
  title: "My Post",
  content: "...",
  recentComments: [  // Last 10 comments embedded
    { author: "Alice", text: "Great!", date: ISODate("...") }
  ],
  commentCount: 156
}

// Comments collection - all comments with reference
{
  _id: ObjectId("..."),
  postId: ObjectId("..."),
  author: "Alice",
  text: "Great!",
  date: ISODate("...")
}

Common Distractors: - Embedding all comments (wrong - can exceed 16 MB for popular posts) - Only referencing (wrong - extra query for every post read) - Separate collection without subset (wrong - misses optimization)

Many-to-Many: Students and Courses

Scenario: Students enroll in courses. Need to query both directions efficiently.

Solution Pattern:

// Students collection
{
  _id: "S001",
  name: "Alice",
  enrolledCourses: ["C101", "C201", "C301"]  // Array of course IDs
}

// Courses collection
{
  _id: "C101",
  title: "Math 101",
  enrolledStudents: ["S001", "S002", "S003"]  // Array of student IDs
}

Key Points: - Two-way referencing for bidirectional queries - Keep arrays bounded (reasonable enrollment limits) - Use multikey indexes on both arrays for efficient queries - For unbounded relationships, use a junction collection

Polymorphic Pattern

Scenario: E-commerce products with different attributes (books have ISBN, electronics have voltage).

Solution Pattern:

// All products in one collection with shared and specific fields
{
  _id: ObjectId("..."),
  name: "Laptop",
  price: 999.99,
  type: "electronics",
  voltage: "110V",
  warranty_months: 24
}
{
  _id: ObjectId("..."),
  name: "Clean Code",
  price: 39.99,
  type: "book",
  isbn: "978-0132350884",
  author: "Robert Martin"
}

Key Points: - Single collection enables simple queries across all products - type field identifies the document shape - Partial indexes can optimize type-specific queries - Schema validation can use oneOf for type-specific rules

Aggregation Scenarios

Sales Report with Grouping

Scenario: Calculate total revenue and order count per product category for the last 30 days.

Solution Pattern:

db.orders.aggregate([
  { $match: {
    orderDate: { $gte: new Date(Date.now() - 30*24*60*60*1000) }
  }},
  { $unwind: "$items" },
  { $group: {
    _id: "$items.category",
    totalRevenue: { $sum: { $multiply: ["$items.price", "$items.quantity"] } },
    orderCount: { $sum: 1 },
    avgOrderValue: { $avg: { $multiply: ["$items.price", "$items.quantity"] } }
  }},
  { $sort: { totalRevenue: -1 } },
  { $project: {
    category: "$_id",
    totalRevenue: { $round: ["$totalRevenue", 2] },
    orderCount: 1,
    avgOrderValue: { $round: ["$avgOrderValue", 2] },
    _id: 0
  }}
]);

Key Points: - $match early for index usage and filtering - $unwind before $group for array element aggregation - $project reshapes output for readability

Cross-Collection Join

Scenario: Get all orders with customer details for customers in "premium" tier.

Solution Pattern:

db.orders.aggregate([
  { $lookup: {
    from: "customers",
    localField: "customerId",
    foreignField: "_id",
    as: "customer"
  }},
  { $unwind: "$customer" },
  { $match: { "customer.tier": "premium" } },
  { $project: {
    orderId: 1,
    total: 1,
    customerName: "$customer.name",
    customerEmail: "$customer.email"
  }}
]);

Optimization - Use pipeline form of $lookup:

{ $lookup: {
  from: "customers",
  let: { custId: "$customerId" },
  pipeline: [
    { $match: {
      $expr: { $eq: ["$_id", "$$custId"] },
      tier: "premium"  // Filter inside lookup
    }}
  ],
  as: "customer"
}}

Multi-Faceted Search Results

Scenario: Return search results with counts by category, price range, and total count.

Solution Pattern:

db.products.aggregate([
  { $match: { $text: { $search: "laptop" } } },
  { $facet: {
    results: [
      { $sort: { score: { $meta: "textScore" } } },
      { $limit: 10 },
      { $project: { name: 1, price: 1, category: 1 } }
    ],
    categoryCount: [
      { $group: { _id: "$category", count: { $sum: 1 } } },
      { $sort: { count: -1 } }
    ],
    priceRanges: [
      { $bucket: {
        groupBy: "$price",
        boundaries: [0, 100, 500, 1000, 5000],
        default: "5000+",
        output: { count: { $sum: 1 } }
      }}
    ],
    totalCount: [
      { $count: "count" }
    ]
  }}
]);

Atlas Tools Scenarios

Atlas Search Implementation

Scenario: Implement autocomplete search for product names.

Search Index Definition:

{
  "mappings": {
    "dynamic": false,
    "fields": {
      "name": {
        "type": "autocomplete",
        "tokenization": "edgeGram",
        "minGrams": 3,
        "maxGrams": 15
      }
    }
  }
}

Query:

db.products.aggregate([
  { $search: {
    autocomplete: {
      query: "lap",
      path: "name"
    }
  }},
  { $limit: 10 },
  { $project: { name: 1, price: 1 } }
]);