CPCODELAB
Practice

Practice MongoDB

You’ve read the lessons — now prove it. Take the quiz for instant feedback, then work the coding exercises. Try each one yourself before you reveal the solution.

Quick quiz

16 questions
Score: 0 / 16
0/16 answered
  1. 1

    In MongoDB, what is the equivalent of a row in a relational database?

  2. 2

    Which method inserts multiple documents into a collection at once?

  3. 3

    What does db.students.find({ level: { $gt: 2 } }) return?

  4. 4

    Which operator checks whether a field's value matches any value in a given array?

  5. 5

    How do you retrieve only the name field (plus _id) from all student documents?

  6. 6

    Which update operator adds a value to a numeric field without replacing it?

  7. 7

    What does the $push update operator do?

  8. 8

    In an aggregation pipeline, which stage filters documents (like a WHERE clause)?

  9. 9

    What is the output of this aggregation stage? { $group: { _id: "$level", count: { $sum: 1 } } }

  10. 10

    When should you embed a related document rather than reference it by ID?

  11. 11

    In the ESR rule for compound indexes, what does each letter stand for?

  12. 12

    What is the main disadvantage of using skip() for deep pagination?

  13. 13

    Which accumulator in $group collects all matching field values into an array?

  14. 14

    What does the Bucket pattern solve in MongoDB schema design?

  15. 15

    Which of the following correctly creates a text index on both the title and body fields?

  16. 16

    When using the Extended Reference pattern, which type of field should you avoid embedding in the referencing document?

🛠️

Coding exercises

8 tasks
1

Compound Index: ESR Rule

Medium

Create a compound index on the students collection that efficiently supports this query: `` db.students.find({ enrolled: true, age: { $gt: 18 } }).sort({ age: 1 }) Apply the ESR rule (Equality → Sort → Range) when ordering the fields. Then run explain("executionStats") to confirm an IXSCAN` is used.

Starter
js
// Create the compound index
db.students.createIndex({ /* fields in ESR order */ });

// Run the query with explain
db.students.find({ enrolled: true, age: { $gt: 18 } })
  .sort({ age: 1 })
  .explain("executionStats");
💡 Show hint

Equality field (enrolled) goes first, then the sort/range field (age). Both ascending.

✅ Show solution
js
// ESR order: enrolled (equality) → age (sort + range)
db.students.createIndex({ enrolled: 1, age: 1 });

db.students.find({ enrolled: true, age: { $gt: 18 } })
  .sort({ age: 1 })
  .explain("executionStats");
// Check winningPlan.stage === "IXSCAN"
2

Cursor Pagination

Medium

Implement a nextPage(lastId, pageSize) function using range (cursor) pagination on the students collection sorted by _id. The function should return an object { docs, lastId } where lastId is the _id of the last returned document (or null if the page is empty). On the first call, pass null as lastId.

Starter
js
async function nextPage(lastId, pageSize) {
  // build filter
  // query, sort, limit
  // return { docs, lastId }
}
💡 Show hint

When lastId is null use an empty filter {}; otherwise use { _id: { $gt: lastId } }. Always sort by { _id: 1 } and .limit(pageSize).

✅ Show solution
js
async function nextPage(lastId, pageSize) {
  const filter = lastId ? { _id: { $gt: lastId } } : {};
  const docs = await db.collection("students")
    .find(filter)
    .sort({ _id: 1 })
    .limit(pageSize)
    .toArray();
  const newLastId = docs.length > 0 ? docs[docs.length - 1]._id : null;
  return { docs, lastId: newLastId };
}
3

Transaction: Transfer Credits

Hard

Using the MongoDB Node.js driver (not Mongoose), write a transferCredits(client, fromId, toId, amount) function that: 1. Starts a session and a transaction. 2. Deducts amount from the credits field of the fromId wallet document. 3. Adds amount to the credits field of the toId wallet document. 4. Commits on success, aborts on any error, and always ends the session. Both updateOne calls must use the same session.

Starter
js
async function transferCredits(client, fromId, toId, amount) {
  const session = client.startSession();
  try {
    session.startTransaction();
    const wallets = client.db("bank").collection("wallets");
    // deduct
    // add
    // commit
  } catch (err) {
    // abort
    throw err;
  } finally {
    // end session
  }
}
💡 Show hint

Pass { session } as the options object to every updateOne call so both operations participate in the same transaction.

✅ Show solution
js
async function transferCredits(client, fromId, toId, amount) {
  const session = client.startSession();
  try {
    session.startTransaction();
    const wallets = client.db("bank").collection("wallets");
    await wallets.updateOne(
      { _id: fromId },
      { $inc: { credits: -amount } },
      { session }
    );
    await wallets.updateOne(
      { _id: toId },
      { $inc: { credits: amount } },
      { session }
    );
    await session.commitTransaction();
  } catch (err) {
    await session.abortTransaction();
    throw err;
  } finally {
    session.endSession();
  }
}
4

Find Students by Skill

Easy

Write a query that returns the name and skills of every student whose skills array contains "JavaScript". Sort the results alphabetically by name.

Starter
js
db.students.find(
  { /* filter */ },
  { /* projection */ }
).sort({ /* sort */ });
💡 Show hint

Use the $in operator (or simply a direct equality match on an array field) to check array membership, and pass a projection object as the second argument to find().

✅ Show solution
js
db.students.find(
  { skills: "JavaScript" },
  { name: 1, skills: 1, _id: 0 }
).sort({ name: 1 });
5

Insert a New Student

Easy

Insert a new student document into the students collection with the following data: - name: "Ada Lovelace" - level: 3 - skills: ["Python", "Math"] Then, using insertMany, add two more students in a single call.

Starter
js
// Single insert
db.students.insertOne({ /* document */ });

// Bulk insert
db.students.insertMany([ /* documents */ ]);
💡 Show hint

insertOne() takes a single document object; insertMany() takes an array of document objects.

✅ Show solution
js
// Single insert
db.students.insertOne({
  name: "Ada Lovelace",
  level: 3,
  skills: ["Python", "Math"]
});

// Bulk insert
db.students.insertMany([
  { name: "Alan Turing", level: 4, skills: ["C", "Algorithms"] },
  { name: "Grace Hopper", level: 5, skills: ["COBOL", "Debugging"] }
]);
6

Update Student Data

Medium

For the student named "Ada Lovelace": 1. Set her level to 4. 2. Add "TypeScript" to her skills array (avoid duplicates). 3. Increment her level by 1 more (making it 5). Write three separate updateOne calls.

Starter
js
// 1. Set level
db.students.updateOne(
  { name: "Ada Lovelace" },
  { /* update */ }
);

// 2. Add skill (no duplicates)

// 3. Increment level
💡 Show hint

Use $set to assign a value, $addToSet instead of $push to avoid duplicates, and $inc to increment a numeric field.

✅ Show solution
js
// 1. Set level to 4
db.students.updateOne(
  { name: "Ada Lovelace" },
  { $set: { level: 4 } }
);

// 2. Add skill without duplicates
db.students.updateOne(
  { name: "Ada Lovelace" },
  { $addToSet: { skills: "TypeScript" } }
);

// 3. Increment level by 1
db.students.updateOne(
  { name: "Ada Lovelace" },
  { $inc: { level: 1 } }
);
7

Aggregate: Count Students per Level

Medium

Using the aggregation pipeline, produce a report that shows how many students are at each level, but only include levels where there are at least 2 students. Sort the output from highest count to lowest.

Starter
js
db.students.aggregate([
  // Stage 1: group by level and count
  // Stage 2: filter groups with count >= 2
  // Stage 3: sort by count descending
]);
💡 Show hint

Use $group with $sum: 1 to count, then $match on the count field, and finally $sort with -1 for descending order.

✅ Show solution
js
db.students.aggregate([
  {
    $group: {
      _id: "$level",
      count: { $sum: 1 }
    }
  },
  {
    $match: { count: { $gte: 2 } }
  },
  {
    $sort: { count: -1 }
  }
]);
8

Full Pipeline: Top Skilled Students

Hard

Build an aggregation pipeline that: 1. Filters students at level 3 or above. 2. Adds a computed field skillCount equal to the size of the skills array. 3. Filters to only students with 3 or more skills. 4. Projects only name, level, and skillCount (exclude _id). 5. Sorts by skillCount descending, then by name ascending. 6. Limits to the top 5 results. Also create an index on level to support the first filter stage efficiently.

Starter
js
// Index
db.students.createIndex({ /* fields */ });

// Pipeline
db.students.aggregate([
  // your stages here
]);
💡 Show hint

Use $match for filtering, $addFields with $size to compute the array length, $project with 0 to exclude _id, and chain $sort then $limit at the end.

✅ Show solution
js
// Create index on level for efficient $match
db.students.createIndex({ level: 1 });

// Aggregation pipeline
db.students.aggregate([
  {
    $match: { level: { $gte: 3 } }
  },
  {
    $addFields: {
      skillCount: { $size: "$skills" }
    }
  },
  {
    $match: { skillCount: { $gte: 3 } }
  },
  {
    $project: {
      _id: 0,
      name: 1,
      level: 1,
      skillCount: 1
    }
  },
  {
    $sort: { skillCount: -1, name: 1 }
  },
  {
    $limit: 5
  }
]);