Curated questions covering CRUD operations, aggregation pipeline, indexing, replication, sharding, transactions, and Mongoose ODM.
MongoDB is a NoSQL document database that stores data as BSON (Binary JSON) documents. Differences from RDBMS: no fixed schema (flexible documents), no JOINs (embed or reference), horizontal scaling via sharding, no ACID transactions by default (added in v4.0), and collections instead of tables.
BSON (Binary JSON) is the binary-encoded serialization format MongoDB uses to store documents. It extends JSON with additional types: Date, ObjectId, Binary, Decimal128, and more. BSON is more efficient to parse than JSON and supports richer data types.
{\n "_id": ObjectId("507f1f77bcf86cd799439011"),\n "name": "Alice",\n "age": 30,\n "createdAt": ISODate("2024-01-01"),\n "scores": [95, 87, 92],\n "address": { "city": "NYC", "zip": "10001" }\n}// Embedding\n{ name: "Alice", address: { city: "NYC", zip: "10001" } }\n\n// Referencing\n{ name: "Alice", addressId: ObjectId("...") }db.users.insertOne({ name: "Alice", age: 30 });\ndb.users.find({ age: { $gt: 25 } }, { name: 1, _id: 0 });\ndb.users.updateOne({ name: "Alice" }, { $set: { age: 31 } });\ndb.users.deleteOne({ name: "Alice" });db.users.findOne({ email: "alice@example.com" }); // single doc or null\ndb.users.find({ age: { $gte: 18 } }).limit(10); // cursordb.products.find({\n price: { $gte: 10, $lte: 100 },\n category: { $in: ["electronics", "books"] },\n tags: { $all: ["sale", "new"] }\n});The aggregation pipeline processes documents through a sequence of stages, each transforming the data. Common stages: $match, $group, $project, $sort, $limit, $skip, $lookup, $unwind, $addFields, $count.
db.orders.aggregate([\n { $match: { status: "completed" } },\n { $group: { _id: "$customerId", total: { $sum: "$amount" } } },\n { $sort: { total: -1 } },\n { $limit: 10 }\n]);$lookup performs a left outer join between two collections, similar to SQL JOIN. It adds an array field to each document with matching documents from the joined collection.
db.orders.aggregate([\n {\n $lookup: {\n from: "customers",\n localField: "customerId",\n foreignField: "_id",\n as: "customer"\n }\n },\n { $unwind: "$customer" }\n]);$unwind deconstructs an array field, outputting one document per array element. Used before $group or $lookup when working with array fields.
// Document: { name: "Alice", scores: [90, 85, 92] }\ndb.students.aggregate([\n { $unwind: "$scores" }\n]);\n// Outputs 3 documents:\n// { name: "Alice", scores: 90 }\n// { name: "Alice", scores: 85 }\n// { name: "Alice", scores: 92 }db.sales.aggregate([{\n $group: {\n _id: "$category",\n count: { $sum: 1 },\n avgPrice: { $avg: "$price" },\n allTags: { $push: "$tag" },\n uniqueTags: { $addToSet: "$tag" }\n }\n}]);Indexes improve query performance by allowing MongoDB to find documents without scanning the entire collection. Without an index, MongoDB performs a collection scan (COLLSCAN). With an index, it performs an index scan (IXSCAN).
db.users.createIndex({ email: 1 }); // ascending\ndb.users.createIndex({ age: -1 }); // descending\ndb.users.createIndex({ email: 1 }, { unique: true }); // unique\ndb.users.getIndexes(); // list indexes\ndb.users.find({ email: "a@b.com" }).explain("executionStats");db.users.createIndex({ lastName: 1, firstName: 1 }); // compound\ndb.products.createIndex({ tags: 1 }); // multikey (tags is array)\ndb.articles.createIndex({ content: "text" }); // text indexdb.users.updateOne({ _id: id }, {\n $set: { age: 31, "address.city": "LA" },\n $unset: { tempField: "" },\n $inc: { loginCount: 1 },\n $push: { tags: "premium" }\n});// updateOne - only changes age\ndb.users.updateOne({ _id: id }, { $set: { age: 31 } });\n\n// replaceOne - entire document replaced\ndb.users.replaceOne({ _id: id }, { name: "Alice", age: 31 });MongoDB 4.0+ supports multi-document ACID transactions. Use sessions to group operations. Transactions work across multiple documents and collections.
const session = client.startSession();\ntry {\n session.startTransaction();\n await db.accounts.updateOne(\n { _id: fromId }, { $inc: { balance: -100 } }, { session }\n );\n await db.accounts.updateOne(\n { _id: toId }, { $inc: { balance: 100 } }, { session }\n );\n await session.commitTransaction();\n} catch (e) {\n await session.abortTransaction();\n} finally {\n session.endSession();\n}A replica set is a group of MongoDB instances that maintain the same dataset. It provides redundancy and high availability. The primary receives all writes; secondaries replicate from the primary. If the primary fails, an election promotes a secondary.
// $project - only name and computed fullName\n{ $project: { name: 1, fullName: { $concat: ["$first", " ", "$last"] } } }\n\n// $addFields - adds fullName, keeps all other fields\n{ $addFields: { fullName: { $concat: ["$first", " ", "$last"] } } }$facet runs multiple sub-pipelines on the same input documents simultaneously, returning results in a single document. Useful for faceted search (multiple categorizations at once).
db.products.aggregate([{\n $facet: {\n byCategory: [{ $group: { _id: "$category", count: { $sum: 1 } } }],\n byPrice: [{ $bucket: { groupBy: "$price", boundaries: [0,50,100,500] } }],\n total: [{ $count: "count" }]\n }\n}]);// Simple count\ndb.users.aggregate([{ $match: { active: true } }, { $count: "activeUsers" }]);\n\n// Count with other stats\ndb.users.aggregate([{ $group: { _id: null, count: { $sum: 1 }, avgAge: { $avg: "$age" } } }]);// ObjectId\nconst id = new ObjectId();\nconsole.log(id.getTimestamp()); // creation time\n\n// Custom _id\ndb.users.insertOne({ _id: "custom-id-123", name: "Alice" });// Prefer $in for same field\ndb.users.find({ status: { $in: ["active", "pending"] } });\n\n// Use $or for different fields\ndb.users.find({ $or: [{ email: "a@b.com" }, { phone: "123" }] });// Sparse index\ndb.users.createIndex({ phone: 1 }, { sparse: true });\n\n// Partial index\ndb.users.createIndex({ email: 1 }, {\n partialFilterExpression: { status: "active" }\n});// Regex (slow for non-prefix)\ndb.articles.find({ title: { $regex: /mongodb/i } });\n\n// Text search (fast with text index)\ndb.articles.createIndex({ title: "text", body: "text" });\ndb.articles.find({ $text: { $search: "mongodb aggregation" } });Mongoose is an ODM (Object Document Mapper) for MongoDB in Node.js. It adds: schema definition with validation, middleware (pre/post hooks), virtuals, population (reference resolution), query building, and TypeScript support.
const userSchema = new Schema({\n email: { type: String, required: true, unique: true },\n age: { type: Number, min: 0, max: 150 }\n});\nuserSchema.pre("save", async function() {\n this.password = await bcrypt.hash(this.password, 10);\n});\nconst User = model("User", userSchema);// Mongoose populate (multiple queries)\nconst order = await Order.findById(id).populate("customer");\n\n// $lookup (single query)\ndb.orders.aggregate([{ $lookup: { from: "customers", localField: "customerId", foreignField: "_id", as: "customer" } }]);// $bucket - manual boundaries\n{ $bucket: { groupBy: "$price", boundaries: [0, 25, 50, 100, 500], default: "Other" } }\n\n// $bucketAuto - automatic\n{ $bucketAuto: { groupBy: "$price", buckets: 4 } }// $out - replaces collection\n{ $out: "monthly_summary" }\n\n// $merge - upsert into existing\n{ $merge: { into: "monthly_summary", on: "_id", whenMatched: "merge", whenNotMatched: "insert" } }// Returns update result, not the document\nawait db.users.updateOne({ _id: id }, { $set: { age: 31 } });\n\n// Returns the updated document\nconst updated = await db.users.findOneAndUpdate(\n { _id: id },\n { $set: { age: 31 } },\n { returnDocument: "after" }\n);db.users.updateOne({ _id: id }, { $push: { tags: "premium" } }); // allows duplicates\ndb.users.updateOne({ _id: id }, { $addToSet: { tags: "premium" } }); // unique onlydb.users.updateOne({ _id: id }, { $pull: { tags: "expired" } }); // remove by value\ndb.users.updateOne({ _id: id }, { $pop: { scores: 1 } }); // remove last\ndb.users.updateOne({ _id: id }, { $pop: { scores: -1 } }); // remove firstawait db.users.countDocuments({ active: true }); // accurate, with filter\nawait db.users.estimatedDocumentCount(); // fast estimate, no filter$expr allows using aggregation expressions within a query filter. It enables comparing fields within the same document, which regular operators cannot do.
// Compare two fields in the same document\ndb.orders.find({\n $expr: { $gt: ["$totalAmount", "$discountAmount"] }\n});\n\n// Cannot do this with regular operators\n// db.orders.find({ totalAmount: { $gt: "$discountAmount" } }) // WRONGconst changeStream = db.collection("orders").watch();\nchangeStream.on("change", (change) => {\n console.log("Change detected:", change.operationType);\n});// Find all ancestors of an employee\ndb.employees.aggregate([{\n $graphLookup: {\n from: "employees",\n startWith: "$managerId",\n connectFromField: "managerId",\n connectToField: "_id",\n as: "reportingHierarchy"\n }\n}]);db.sales.aggregate([{\n $setWindowFields: {\n partitionBy: "$category",\n sortBy: { date: 1 },\n output: {\n runningTotal: { $sum: "$amount", window: { documents: ["unbounded", "current"] } }\n }\n }\n}]);Time series collections (MongoDB 5.0+) are optimized for storing time-stamped data (IoT, metrics, logs). They automatically compress data, improve query performance for time-range queries, and support automatic expiration via TTL.
db.createCollection("sensorData", {\n timeseries: {\n timeField: "timestamp",\n metaField: "sensorId",\n granularity: "seconds"\n },\n expireAfterSeconds: 86400 // auto-delete after 1 day\n});db.users.aggregate([{ $sample: { size: 5 } }]); // 5 random documentsdb.users.updateOne(\n { email: "alice@example.com" },\n { $set: { name: "Alice", lastLogin: new Date() } },\n { upsert: true } // insert if not found\n);db.users.find({ age: { $gt: 25 } }).explain("executionStats");\n// Check: totalDocsExamined vs nReturned\n// Good: IXSCAN (index scan)\n// Bad: COLLSCAN (collection scan)// $cond\n{ $cond: { if: { $gte: ["$score", 90] }, then: "A", else: "B" } }\n\n// $switch\n{ $switch: { branches: [\n { case: { $gte: ["$score", 90] }, then: "A" },\n { case: { $gte: ["$score", 80] }, then: "B" }\n], default: "C" } }// Auto-delete sessions after 1 hour\ndb.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 });Explore 500+ free tutorials across 20+ languages and frameworks.