🍃

Top 56 MongoDB Interview Questions & Answers (2026)

56 Questions 30 Beginner 17 Intermediate 9 Advanced

About MongoDB

Top 100 MongoDB interview questions covering documents, collections, CRUD operations, aggregation pipeline, indexing, replication, sharding, and performance optimization. Companies hiring for MongoDB roles test this knowledge at every stage — from first-round screens that check the basics to final-round conversations about real-world trade-offs.

What to Expect in a MongoDB Interview

Expect a mix of conceptual and practical MongoDB questions: clear definitions and core-concept checks for junior roles, hands-on scenario questions for mid-level roles, and architecture or trade-off discussions for senior roles. Interviewers usually move from foundational topics toward the kind of problems you'd actually face on the job.

How to Use This Guide

Work through the MongoDB questions in order — Beginner, then Intermediate, then Advanced — so each concept builds on the last. Every question has its own page; bookmark the ones that trip you up and revisit them the day before your interview.

Curated by Tech Baithak Editorial Team  ·  Last updated: May 2026

Beginner 30 questions

Core concepts every MongoDB developer must know.

01

What is MongoDB?

MongoDB is a free, open-source, document-oriented NoSQL database developed by MongoDB, Inc. (formerly 10gen). Released in 2009, it stores data as flexible BSON (Binary JSON) documents rather than in rows and columns as traditional relational databases do. Key features: Document model — related data stored together in a single document (no joins needed for most queries); Schema flexibility — documents in the same collection can have different fields; Horizontal scalability — built-in sharding distributes data across multiple servers; Rich query language — supports filtering, sorting, projections, aggregations; High availability — replica sets provide automatic failover; ACID transactions — multi-document transactions supported since MongoDB 4.0; Atlas — MongoDB's managed cloud service on AWS/GCP/Azure. MongoDB is used extensively for: content management, catalogs, real-time analytics, mobile apps, IoT, and any use case where data structures vary or schema evolves frequently. It is the most popular NoSQL database and consistently ranks in the top 5 of all databases worldwide.

Open this question on its own page
02

What is a document in MongoDB?

A document is the basic unit of data in MongoDB, analogous to a row in a relational database. Documents are stored as BSON (Binary JSON) objects — ordered sets of key-value pairs. BSON supports all JSON types (string, number, boolean, array, object, null) plus additional types: ObjectId, Date, Binary, Decimal128, Int32, Int64, Timestamp, Regular Expression. Example document: { "_id": ObjectId("507f1f77bcf86cd799439011"), "name": "Alice Johnson", "email": "alice@example.com", "age": 30, "address": { "street": "123 Main St", "city": "New York" }, "hobbies": ["reading", "coding"], "createdAt": ISODate("2024-01-15T10:00:00Z") }. Key characteristics: (1) Every document must have a unique _id field (auto-generated ObjectId if not provided); (2) Documents can contain nested objects and arrays (embedded documents); (3) Documents in the same collection can have different sets of fields (flexible schema); (4) Maximum document size: 16MB. Documents directly map to objects in application code — no ORM/mapping layer needed. Related data can be embedded (avoiding joins) or referenced (using a foreign-key-like pattern).

Open this question on its own page
03

What is a collection in MongoDB?

A collection is a group of MongoDB documents, analogous to a table in a relational database. Collections don't enforce a schema by default — documents in the same collection can have different structures (though in practice, they usually store the same type of data). Key points: (1) Collections exist within a database; a MongoDB deployment can have multiple databases, each with multiple collections; (2) Collections are created lazily — when you first insert a document; (3) Capped collections: fixed-size collections that maintain insertion order and automatically overwrite oldest documents when full. Useful for logs, caches; (4) Naming: collection names can contain any UTF-8 characters except null, $, and system prefix; recommended: lowercase, snake_case (e.g., user_profiles); (5) Schema validation: MongoDB supports optional JSON Schema validation rules on collections, enforcing field types and required fields while keeping flexibility. Commands: db.createCollection("orders") (explicit creation); show collections (list all); db.orders.drop() (remove collection + all documents).

Open this question on its own page
04

What is BSON?

BSON (Binary JSON) is a binary-encoded serialization format used by MongoDB to store documents. It extends JSON to include additional data types and is optimized for space efficiency and traversal speed. Key differences from JSON: (1) Binary format: BSON is not human-readable like JSON but is more compact for some data types; (2) Additional types: BSON supports types not in JSON: Date (UTC datetime), ObjectId (12-byte unique ID), Binary (byte array), Decimal128 (high-precision decimal for financial data), Int32/Int64 (specific integer sizes — JSON only has generic "number"), Timestamp (internal MongoDB use), Regular Expression, Null (also in JSON), Undefined (deprecated); (3) Length-prefixed: each BSON string and document is prefixed with its length, enabling fast traversal and skipping without parsing; (4) Ordered: BSON documents preserve field order. ObjectId: MongoDB's default _id type — a 12-byte (24 hex char) unique identifier. Structure: 4-byte timestamp (seconds since epoch), 5-byte random value (unique to machine+process), 3-byte incrementing counter. ObjectIds are sortable by creation time. ObjectId("507f1f77bcf86cd799439011").getTimestamp() returns the creation time. BSON is the wire format between MongoDB driver and server, and the storage format on disk.

Open this question on its own page
05

What is the difference between MongoDB and a relational database?

Key differences between MongoDB and relational databases (MySQL, PostgreSQL): Data model: RDBMS uses tables with fixed schema (rows + columns); MongoDB uses collections of flexible documents (JSON-like). Schema: RDBMS enforces schema at the database level (ALTER TABLE to change); MongoDB is schema-flexible by default (documents can have different fields). Relationships: RDBMS uses JOINs and foreign keys for related data; MongoDB uses embedded documents (embed related data in one document) or references (store the related document's _id — like a foreign key, but no enforced referential integrity). Scaling: RDBMS scales vertically well, horizontal sharding is complex; MongoDB has built-in horizontal sharding. Transactions: RDBMS has mature ACID transactions; MongoDB added multi-document ACID transactions in v4.0. Query language: RDBMS uses SQL (standardized); MongoDB uses its own JSON-based query language. Indexing: both support B-tree indexes; MongoDB adds text, geo, and array indexes. When to choose MongoDB: flexible/evolving schema, document-oriented data (user profiles, product catalogs), rapid development, need to embed related data to avoid joins. When to choose RDBMS: complex relationships, strict ACID requirements, complex ad-hoc queries with JOINs, financial data, well-defined stable schema.

Open this question on its own page
06

How do you insert documents in MongoDB?

MongoDB provides several methods to insert documents into a collection: insertOne(): inserts a single document. db.users.insertOne({ name: "Alice", email: "alice@example.com", age: 30 }). Returns an acknowledgment with the inserted document's _id. If no _id is provided, MongoDB generates an ObjectId automatically. insertMany(): inserts multiple documents at once (more efficient than multiple insertOne calls). db.users.insertMany([ { name: "Bob", age: 25 }, { name: "Carol", age: 35 } ]). Options: ordered: false — continues inserting even if one document fails (e.g., duplicate key). Default is ordered: true (stops on first error). Insert behavior: if a document with the same _id already exists, insertOne/insertMany throws a duplicate key error; use replaceOne() or upsert to overwrite existing documents. Bulk write: db.collection.bulkWrite([]) — perform multiple operations (insert, update, delete) in a single command with configurable ordering. Validation: if schema validation is configured on the collection, inserted documents are validated against the rules. Auto-generated _id: ObjectId is unique across all machines and sortable by time — use it as a creation timestamp: new ObjectId().getTimestamp().

Open this question on its own page
07

How do you query documents in MongoDB?

MongoDB's find() method retrieves documents matching a query filter: db.collection.find(query, projection). find() examples: All documents: db.users.find({}); Exact match: db.users.find({ name: "Alice" }); Multiple conditions (implicit AND): db.users.find({ name: "Alice", age: 30 }); Projection (include/exclude fields): db.users.find({}, { name: 1, email: 1, _id: 0 }) — 1 = include, 0 = exclude, can't mix includes and excludes (except _id). findOne(): returns the first matching document (or null). Query operators: Comparison: $eq, $ne, $gt, $gte, $lt, $ltedb.users.find({ age: { $gte: 18, $lt: 65 } }); $in: db.users.find({ status: { $in: ["active", "pending"] } }); $nin: not in list; Logical: $and, $or, $not, $nor; Element: $exists, $type; Evaluation: $regex, $expr, $where; Array: $all, $elemMatch, $size. Sorting: .sort({ age: 1 }) (1 ascending, -1 descending). Limiting and skipping: .limit(10).skip(20) — page 3 of 10. Count: db.users.countDocuments({ active: true }).

Open this question on its own page
08

How do you update documents in MongoDB?

MongoDB provides several update methods: updateOne(): updates the first document matching the filter. db.users.updateOne({ _id: id }, { $set: { name: "Alice Smith", updatedAt: new Date() } }). updateMany(): updates all matching documents. db.users.updateMany({ status: "inactive" }, { $set: { status: "archived" } }). replaceOne(): replaces the entire document (except _id). Update operators: $set: set field values (add if not exists); $unset: remove fields — { $unset: { tempField: "" } }; $inc: increment numeric field — { $inc: { views: 1, balance: -50 } }; $push: add element to array — { $push: { tags: "mongodb" } }; $pull: remove elements from array matching condition; $addToSet: add to array only if not already present; $pop: remove first (-1) or last (1) array element; $rename: rename a field; $mul: multiply a field's value; $min/$max: update only if new value is less/greater. Upsert: update if exists, insert if not: db.users.updateOne({ email: "new@example.com" }, { $set: { name: "New User" } }, { upsert: true }). findOneAndUpdate(): atomically finds and updates, returning the document before or after update.

Open this question on its own page
09

How do you delete documents in MongoDB?

MongoDB provides three methods to remove documents: deleteOne(): deletes the first document matching the filter. db.users.deleteOne({ _id: ObjectId("...") }). Returns: { acknowledged: true, deletedCount: 1 }. deleteMany(): deletes all documents matching the filter. db.users.deleteMany({ status: "deleted" }). db.users.deleteMany({}) — deletes ALL documents (but keeps the collection and its indexes). findOneAndDelete(): atomically finds, removes, and returns the deleted document. Useful when you need the document's data after deletion (e.g., popping a task from a queue). Soft delete pattern: instead of actually deleting, set a deletedAt field: db.users.updateOne({ _id: id }, { $set: { deletedAt: new Date() } }). Add { deletedAt: { $exists: false } } to all queries. Preserves data for auditing. drop(): removes an entire collection including all documents and indexes: db.users.drop(). Much faster than deleteMany({}) for large collections. dropDatabase(): removes all collections and the database itself: db.dropDatabase(). TTL index for auto-expiry: instead of manual deletion, create a TTL index on a date field: db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 }) — MongoDB auto-deletes documents an hour after creation.

Open this question on its own page
10

What is the ObjectId in MongoDB?

An ObjectId is MongoDB's default primary key type — a 12-byte (24 hexadecimal characters) BSON value that is unique across all MongoDB instances worldwide. Structure of the 12 bytes: (1) 4 bytes — Unix timestamp (seconds since epoch) of when the ObjectId was created; (2) 5 bytes — random value unique to machine and process; (3) 3 bytes — auto-incrementing counter per process. Properties: Globally unique: virtually guaranteed uniqueness across all machines and processes; Sortable by creation time: since the timestamp is the most significant bytes, ObjectIds sort chronologically; Compact: 12 bytes vs 36-character UUID string; Creation timestamp extractable: ObjectId("507f1f77bcf86cd799439011").getTimestamp() returns the creation date. Creating ObjectIds: new ObjectId() — auto-generated; ObjectId("507f1f77bcf86cd799439011") — from string; ObjectId.createFromTime(timestamp) — for range queries. Range queries by time: since ObjectIds are sortable, you can find documents created in a time range without a separate timestamp field: db.orders.find({ _id: { $gte: ObjectId.createFromTime(startDate), $lt: ObjectId.createFromTime(endDate) } }). Custom _id: you can use any unique value as _id — string, integer, custom UUID — not required to use ObjectId.

Open this question on its own page
11

What are indexes in MongoDB?

Indexes in MongoDB improve query performance by allowing the database to find documents without a full collection scan. Without an index, MongoDB must scan every document (COLLSCAN). With an index, it can quickly jump to matching documents (IXSCAN). Default index: every collection has an index on _id automatically. Creating indexes: db.users.createIndex({ email: 1 }) — single field index, ascending (1); db.users.createIndex({ email: -1 }) — descending; db.users.createIndex({ lastName: 1, firstName: 1 }) — compound index (multiple fields). Index types: Single field: on one field; Compound: multiple fields — order matters (leftmost prefix rule); Multikey: automatically created when indexing array fields — one index entry per array element; Text: for full-text search; 2dsphere: for GeoJSON geospatial; Hashed: for hash-based sharding; Wildcard: indexes all fields or a subset dynamically. Index options: unique: true — enforces uniqueness; sparse: true — only indexes documents that contain the field; expireAfterSeconds — TTL index; background: true (legacy) — build without blocking. View indexes: db.users.getIndexes(). Drop index: db.users.dropIndex("email_1"). Indexes trade write performance for read performance — don't over-index.

Open this question on its own page
12

What is the aggregation pipeline in MongoDB?

The aggregation pipeline is MongoDB's framework for processing and transforming documents through a sequence of stages. Each stage transforms the documents and passes the output to the next stage — like a Unix pipe. Common stages: $match: filter documents (like WHERE) — place early to reduce data volume: { $match: { status: "active", age: { $gte: 18 } } }; $group: group documents by a key and compute aggregations: { $group: { _id: "$department", totalSalary: { $sum: "$salary" }, count: { $sum: 1 }, avgSalary: { $avg: "$salary" } } }; $project: reshape documents — include/exclude fields, add computed fields: { $project: { fullName: { $concat: ["$firstName", " ", "$lastName"] }, _id: 0 } }; $sort: sort documents: { $sort: { salary: -1 } }; $limit: return first N documents; $skip: skip first N documents (pagination); $unwind: deconstruct an array field into separate documents — one per array element; $lookup: left join with another collection; $addFields: add new fields; $count: count documents; $bucket / $bucketAuto: categorize into buckets (histogram); $facet: multiple aggregations on same data. Pipeline execution: MongoDB optimizes — pushes $match and $limit early; uses indexes where possible. db.orders.aggregate([ { $match: ... }, { $group: ... }, { $sort: ... } ])

Open this question on its own page
13

What is the $lookup stage in MongoDB aggregation?

$lookup performs a left outer join between two collections in the same database. It adds a new array field to each document containing the matching documents from the joined collection. Syntax: { $lookup: { from: "orders", localField: "_id", foreignField: "userId", as: "userOrders" } }. This adds a userOrders array to each user document containing all their orders. Pipeline-based $lookup (correlated subquery): more flexible, allows conditions beyond simple equality: { $lookup: { from: "orders", let: { userId: "$_id" }, pipeline: [ { $match: { $expr: { $eq: ["$userId", "$$userId"] } } }, { $match: { status: "completed" } }, { $sort: { createdAt: -1 } }, { $limit: 5 } ], as: "recentOrders" } }. Handling joined results: use $unwind after $lookup if you need to flatten the array (treat each matched document as a separate document in the pipeline). $lookup limitations: only works within the same database; can be expensive on large collections without proper indexes — ensure the foreignField is indexed. Sharded collections: $lookup on sharded collections requires the from collection to be on the same shard or unsharded. When to embed vs reference: use $lookup when the data is accessed separately more than together, the related data is large/frequently updated, or the relationship is many-to-many. Embed when data is always accessed together, the sub-document is small, and the parent-child relationship is one-to-few.

Open this question on its own page
14

What is a replica set in MongoDB?

A replica set is a group of MongoDB servers that maintain the same dataset, providing redundancy and high availability. A replica set consists of: (1) Primary: the single node that receives all write operations; (2) Secondaries (1+): maintain copies of the primary's data by asynchronously applying operations from the primary's oplog; (3) Arbiter (optional): participates in elections but holds no data — used when you want an odd number of voting members for elections without adding a full data node. Oplog (Operations Log): a special capped collection on the primary that records all data modification operations. Secondaries tail the oplog to stay in sync. Automatic failover: if the primary becomes unreachable, the remaining replica set members hold an election to choose a new primary. The election uses the Raft-based protocol; the new primary is chosen within ~10-30 seconds. Read preferences: by default, reads go to the primary. Configure read preference to use secondaries: primary, primaryPreferred, secondary, secondaryPreferred, nearest. Secondary reads may be slightly stale (replication lag). Write concern: w: "majority" — write acknowledged by majority of replica set members. Ensures durability even if primary fails after the write. Minimum recommended replica set: 3 members (one primary, two secondaries) — tolerates one failure and can elect a new primary with the remaining two (majority of 3). Replica sets are the foundation for MongoDB Atlas clusters.

Open this question on its own page
15

What is MongoDB sharding?

Sharding is MongoDB's horizontal scaling mechanism that distributes data across multiple servers (shards). Each shard is a replica set, holding a subset of the data. Components of a sharded cluster: (1) Shards: each stores a portion of the data (each is a replica set); (2) Config servers: store cluster metadata — which chunks are on which shards. Must be a 3-member replica set for HA; (3) mongos (query router): the interface between client applications and the cluster. Routes queries to the correct shards, merges results. Applications connect to mongos, not shards directly. Shard key: the field (or fields) MongoDB uses to distribute documents across shards. Choosing the right shard key is critical — affects data distribution, query routing, and performance. Good shard key: high cardinality, even write distribution, frequently used in queries. Bad shard key: monotonically increasing (like timestamp — all writes go to one shard "hot shard"), low cardinality (like boolean — only 2 shards possible). Chunk: a contiguous range of shard key values; the unit of data migration between shards. MongoDB auto-balances chunks across shards. Range-based sharding: documents with similar shard key values are grouped — efficient range queries. Hashed sharding: hash of shard key value determines placement — more even distribution but no range query efficiency. When to shard: when a single replica set can't handle data volume, query throughput, or working set size exceeds RAM.

Open this question on its own page
16

What is the difference between embed and reference in MongoDB schema design?

Embedding (denormalization): store related data inside the same document as nested objects or arrays. Example: store a user's address embedded in the user document instead of a separate addresses collection. Embedding pros: single query to get all related data (no joins); atomic updates to the whole document; better read performance (single I/O); intuitive document structure. Embedding cons: document size can grow large (MongoDB 16MB limit); if the embedded data is updated frequently, the entire document must be rewritten; if embedded data needs to be accessed independently, it's hard; data duplication if the same data is embedded in multiple documents. When to embed: "one-to-few" relationship (user → addresses, order → order items); data accessed together; child data not needed independently; child data is bounded in size. Referencing (normalization): store only the related document's _id and look it up separately (like a foreign key). Referencing pros: avoids data duplication; documents stay small and focused; referenced data can be accessed independently; works for large or unbounded relationships. Referencing cons: requires additional queries or $lookup for joins; no referential integrity enforcement (MongoDB won't prevent orphan references). When to reference: "one-to-many" (blog → comments — potentially thousands), "many-to-many" (students ↔ courses), when the related data is large, frequently updated, or accessed independently. Hybrid: denormalize frequently-read fields (store author name with each post) but reference the full author document.

Open this question on its own page
17

What are MongoDB data types?

MongoDB's BSON format supports more data types than JSON. String: UTF-8 encoded strings — most common type for text. Integer: Int32 (32-bit) and Int64 (64-bit) — numbers without decimal. Double: 64-bit floating-point — JSON "number" maps to this. Decimal128: high-precision 128-bit decimal for financial calculations (avoids floating-point errors). Use for money values. Boolean: true/false. Date: 64-bit integer representing milliseconds since Unix epoch. new Date() in mongo shell; ISODate("2024-01-15"). Always store dates as Date type, not strings. Timestamp: special BSON type for internal MongoDB use (not for application dates — use Date instead). ObjectId: 12-byte unique identifier, default _id type. Array: ordered list of values. Values can be any BSON type. Embedded document: nested document (object) as a value. Binary Data: byte arrays — for images, files (though large files should use GridFS or S3). Null: represents missing or undefined value. Regular Expression: stored as PCRE regex. JavaScript: JavaScript code (rare use). MinKey/MaxKey: special values that compare lower/higher than any other BSON value — used in shard key bounds. Symbol: deprecated. Type checking in queries: db.users.find({ age: { $type: "int" } }) or { $type: 16 } (BSON type number for Int32).

Open this question on its own page
18

What is the MongoDB query language and how does it differ from SQL?

MongoDB's query language uses JSON-like syntax instead of SQL text. Comparison: SELECT equivalent: SQL: SELECT name, email FROM users WHERE age > 18 ORDER BY name ASC LIMIT 10; MongoDB: db.users.find({ age: { $gt: 18 } }, { name: 1, email: 1, _id: 0 }).sort({ name: 1 }).limit(10). INSERT equivalent: SQL: INSERT INTO users (name, email) VALUES ("Alice", "alice@example.com"); MongoDB: db.users.insertOne({ name: "Alice", email: "alice@example.com" }). UPDATE equivalent: SQL: UPDATE users SET status="active" WHERE _id=1; MongoDB: db.users.updateOne({ _id: ObjectId("...") }, { $set: { status: "active" } }). DELETE: SQL: DELETE FROM users WHERE status="inactive"; MongoDB: db.users.deleteMany({ status: "inactive" }). GROUP BY equivalent: SQL: SELECT dept, COUNT(*) FROM users GROUP BY dept; MongoDB: db.users.aggregate([{ $group: { _id: "$dept", count: { $sum: 1 } } }]). JOIN equivalent: SQL uses JOIN; MongoDB uses $lookup in aggregation or manual application-level joins. Key differences: MongoDB queries are composable objects (can be built programmatically); no need to parse SQL strings; MongoDB queries work directly on nested fields and arrays; SQL is a string-based language with standardized syntax across databases.

Open this question on its own page
19

How does MongoDB handle transactions?

MongoDB added multi-document ACID transactions in version 4.0 (for replica sets) and 4.2 (for sharded clusters). Before this, only single-document operations were atomic. Single-document atomicity: MongoDB has always provided atomic operations on a single document — all fields in a document update atomically. This covers many use cases if data is well-modeled (use embedded documents). Multi-document transactions: for operations spanning multiple documents/collections. Use like a traditional database transaction. Session-based: const session = client.startSession(); session.startTransaction(); try { await users.insertOne({ name: "Alice" }, { session }); await orders.insertOne({ userId: aliceId }, { session }); await session.commitTransaction(); } catch(e) { await session.abortTransaction(); } finally { session.endSession(); }. ACID guarantees: Atomicity (all or nothing), Consistency (constraints enforced), Isolation (read concern "snapshot" — reads a consistent snapshot), Durability (write concern "majority" for durability). Performance considerations: transactions add overhead vs non-transactional operations — MongoDB uses multi-version concurrency control (MVCC). Transactions have a 60-second timeout by default. Keep transactions short. Best practice: first redesign schema to use single-document operations (embedding) — use transactions as a last resort when schema can't be restructured. MongoDB recommends using transactions for multi-document writes that require atomicity.

Open this question on its own page
20

What is the MongoDB Atlas?

MongoDB Atlas is MongoDB's fully managed cloud database service, available on AWS, Google Cloud Platform, and Microsoft Azure. It handles all operational complexity so developers can focus on applications. Key features: (1) Automated provisioning: deploy a replica set cluster in minutes with a GUI or API; (2) Automated backups: continuous backups with point-in-time restore and scheduled snapshots; (3) Automated scaling: vertical and horizontal scaling on demand; auto-scaling based on CPU/connections; (4) Built-in monitoring: real-time performance metrics, query profiler, index suggestions, alerts; (5) Global clusters: geo-distributed data across multiple regions/clouds with configurable write/read regions; (6) Atlas Search: full-text search powered by Apache Lucene, built directly into Atlas; (7) Atlas Data Lake: query data in S3 with MongoDB query language; (8) Atlas App Services: serverless backend (GraphQL, REST, triggers, authentication, sync); (9) Vector Search: store and query vector embeddings for AI/ML applications; (10) Free tier: M0 cluster with 512MB storage — great for development and small projects. Tiers: M0 (free), M10+, M30+ (dedicated clusters with more resources). Atlas manages: hardware, software updates, security patches, monitoring, backups, failover. Pricing: pay per cluster tier and data transfer.

Open this question on its own page
21

What is MongoDB Compass?

MongoDB Compass is the official GUI (Graphical User Interface) for MongoDB — a visual tool for exploring and manipulating your data without writing shell commands. Key features: (1) Schema visualization: visual representation of document structure and data types, showing distribution of values for each field — helps understand data shape; (2) CRUD operations: insert, edit, and delete documents with a visual editor; (3) Aggregation pipeline builder: build complex aggregation pipelines with a visual stage-by-stage editor and real-time preview of results; (4) Index management: view existing indexes, create new ones, and see their usage statistics; (5) Query bar: build queries with syntax validation and auto-completion; (6) Explain plan viewer: visual display of query execution plan — see if indexes are used, identify bottlenecks; (7) Performance insights: real-time server performance metrics; (8) MongoDB Shell (mongosh): embedded shell in Compass for running commands; (9) Connection management: save and manage multiple database connections. Compass is free and available for Windows, Mac, and Linux. It connects to any MongoDB deployment: local, cloud, Atlas. Alternative tools: Studio 3T (paid, more powerful), NoSQLBooster, Robo 3T (deprecated in favor of Studio 3T). Most developers use Compass for visual exploration and switch to mongosh or drivers for scripting.

Open this question on its own page
22

What is the oplog in MongoDB?

The oplog (operations log) is a special capped collection in the local database on each MongoDB replica set member that records all data modification operations in the order they occurred. It is the mechanism enabling replication — secondaries continuously tail the primary's oplog and replay the operations to stay synchronized. Oplog characteristics: (1) Capped collection: fixed size, automatically overwrites oldest entries when full. Default size: 5% of free disk space (at least 1GB). Configure with oplogSizeMB; (2) Idempotent operations: operations in the oplog are idempotent — can be applied multiple times with the same result. MongoDB transforms operations if needed (e.g., $inc: 1 becomes $set: newValue); (3) Oplog window: how far back in time the oplog goes. Determines recovery window — if a secondary falls this far behind, it can no longer replicate and needs a full resync; (4) Oplog entry structure: { "ts": Timestamp, "op": "i/u/d/c", "ns": "database.collection", "o": { document }, "o2": { query } }. ops: i (insert), u (update), d (delete), c (command), n (noop). Change Data Capture (CDC): change streams (MongoDB 3.6+) provide a higher-level, resumable, filtered view of the oplog — preferred over directly reading the oplog in applications. Monitoring oplog lag: rs.printReplicationInfo() shows oplog window and secondary lag.

Open this question on its own page
23

What are change streams in MongoDB?

Change streams allow applications to access real-time data changes in MongoDB collections, databases, or entire deployments without the complexity of tailing the oplog. Introduced in MongoDB 3.6, they provide a higher-level, resumable, filtered stream of change events. Opening a change stream: const changeStream = db.orders.watch(); changeStream.on("change", (change) => { console.log(change); });. Change event structure: each event includes: _id (resume token — allows resuming from a specific point after reconnection), operationType (insert/update/delete/replace/drop/rename), fullDocument (the affected document), updateDescription (what fields changed for update operations), ns (namespace: database + collection). Filtering: pass an aggregation pipeline to filter events: db.orders.watch([{ $match: { operationType: "insert" } }]). Resuming: store the last processed _id (resume token); on reconnect: db.orders.watch([], { resumeAfter: lastToken }) — continues from where you left off. Requirements: change streams require a replica set or sharded cluster (not standalone). Use cases: real-time notifications, syncing data to cache/search index, event-driven microservices (trigger workflow on data change), audit logging, replicating data to other systems (CDC). Fullness: the fullDocument: "updateLookup" option returns the full document after an update (not just the changed fields) — note this is slightly stale if concurrent updates occur.

Open this question on its own page
24

What is GridFS in MongoDB?

GridFS is MongoDB's specification for storing and retrieving files that exceed the BSON document size limit of 16MB. It divides files into smaller chunks (default 255KB each) and stores them in two collections: fs.files (file metadata: filename, uploadDate, length, chunkSize, contentType, custom metadata) and fs.chunks (file data chunks). When to use GridFS: (1) Files larger than 16MB; (2) You want to access sections of a large file without loading it all into memory (random reads); (3) You want to store file metadata alongside the file in MongoDB; (4) You want to replicate files across replica set members automatically. When NOT to use GridFS: (1) Files are under 16MB — just store as binary BSON; (2) You need high-performance CDN serving — S3 + CloudFront is much better; (3) You need very frequent updates to files — GridFS stores files as immutable chunks. GridFS vs S3: S3 is almost always preferable for file storage in production — much cheaper, scales infinitely, integrates with CDNs, has pre-signed URL support. Use GridFS only if you're already using MongoDB and want to avoid another infrastructure dependency. Driver usage: all MongoDB drivers support GridFS: const bucket = new GridFSBucket(db); fs.createReadStream("large.mp4").pipe(bucket.openUploadStream("large.mp4")).

Open this question on its own page
25

What is the explain() method in MongoDB?

The explain() method returns information about how MongoDB executes a query — the query execution plan. Essential for query optimization. Usage: db.users.find({ email: "alice@example.com" }).explain("executionStats"). Verbosity levels: "queryPlanner" (default): shows the winning plan without executing; "executionStats": executes the query, shows execution statistics; "allPlansExecution": executes all candidate plans, shows statistics for each. Key fields in the output: winningPlan.stage: the primary operation — IXSCAN (index scan — good!), COLLSCAN (collection scan — slow for large collections!), SORT (in-memory sort — may be slow), FETCH (retrieve documents from disk); executionStats.nReturned: number of documents returned; executionStats.totalKeysExamined: index keys examined; executionStats.totalDocsExamined: documents examined; executionStats.executionTimeMillis: total execution time. Efficiency ratio: good query: nReturned ≈ totalKeysExamined ≈ totalDocsExamined (minimal scanning). Bad: totalDocsExamined >> nReturned (scanning many docs to find few). Covered query: when all fields needed are in the index — no document fetch needed. IXSCAN only, totalDocsExamined = 0. Forcing an index: .hint({ email: 1 }) — force use of a specific index for testing. Use explain() with every new query in production to verify index usage.

Open this question on its own page
26

What is the difference between find() and aggregate() in MongoDB?

find() is optimized for simple queries — filtering, projecting, sorting, and limiting documents from a collection. It's direct and efficient for most read operations. Best for: fetching documents with simple filters and field selection. Performance: uses indexes directly, low overhead. Syntax: db.collection.find(query, projection).sort().limit(). aggregate() is a powerful pipeline framework for complex data processing — transformations, grouping, joining, computing, and reshaping data. Best for: GROUP BY operations, JOINs ($lookup), complex transformations, computed fields, multi-stage data pipelines, analytics. Performance: more overhead than find(), but can be optimized with early $match and $project stages. When to use each: find(): simple lookups, pagination, filtering with projections — it's faster and simpler; aggregate(): any grouping, joining, or transformation — whenever you need more than find() can do. Can find() do everything aggregate() can? No — find() can't do GROUP BY, JOINs, computed aggregations, $unwind of arrays, or complex transformations. Aggregation with $match only: db.users.aggregate([{ $match: { age: { $gte: 18 } } }]) is equivalent to find() but slightly less efficient — use find() for simple filters. Using aggregate() for pagination: [{ $match: filter }, { $sort: sort }, { $skip: skip }, { $limit: limit }, { $project: projection }] — allows combining count and data in one pass with $facet.

Open this question on its own page
27

What are TTL indexes in MongoDB?

TTL (Time-To-Live) indexes are special single-field indexes on Date fields that automatically delete documents after a specified time period — MongoDB runs a background process every 60 seconds that removes expired documents. Creating a TTL index: db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 }) — documents are deleted 1 hour after their createdAt value. db.logs.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }) — documents are deleted at the exact time stored in expiresAt (when expireAfterSeconds is 0, MongoDB deletes documents when the indexed Date field's value is in the past). Behavior: (1) The indexed field must be a BSON Date type or array of Date types; (2) MongoDB checks for and deletes expired documents every 60 seconds — documents may live up to 60 seconds beyond their expiry; (3) TTL indexes on replica sets: only the primary deletes documents (secondaries delete by replicating the deletes from primary); (4) On sharded collections: each shard's primary handles TTL deletions independently. Use cases: session expiration, log rotation, cache entries, temporary tokens (password reset, email verification), sensor data retention, shopping cart cleanup. Modifying TTL: db.runCommand({ collMod: "sessions", index: { keyPattern: { createdAt: 1 }, expireAfterSeconds: 7200 } }). Limitation: only one TTL index per collection. No TTL on compound indexes.

Open this question on its own page
28

What is a sparse index in MongoDB?

A sparse index only contains entries for documents that have the indexed field, even if the value is null — it ignores documents where the field is absent. This is different from the default (dense) index that includes all documents, storing null for documents without the field. Creating a sparse index: db.users.createIndex({ phone: 1 }, { sparse: true }). Why use sparse indexes: (1) Space efficiency: if only 10% of documents have a "phone" field, a sparse index stores only 10% of the entries vs a full index; (2) Unique + sparse: a unique sparse index allows multiple documents to lack the field (they're not in the index) while still enforcing uniqueness among documents that DO have the field. This is common for "optional unique" fields like phone numbers; (3) Selective queries: queries that filter for documents where the field exists benefit from sparse indexes. Caveat: if a query doesn't explicitly filter for the indexed field, MongoDB may not use the sparse index (it would miss documents lacking the field). For example, db.users.find({}).sort({ phone: 1 }) might COLLSCAN instead of using the sparse index, as sorting with the sparse index would exclude documents without "phone." Partial indexes (MongoDB 3.2+) are more powerful: db.users.createIndex({ phone: 1 }, { partialFilterExpression: { phone: { $exists: true } } }) — explicitly define which documents to include, with full support for any filter expression.

Open this question on its own page
29

What is the MongoDB wire protocol?

The MongoDB Wire Protocol is the binary protocol used for communication between MongoDB clients (drivers) and the MongoDB server over TCP. It defines the message format for all database operations. Key aspects: (1) TCP-based: runs over standard TCP (default port 27017); (2) OP_MSG: the current primary message format (replaced older OP_QUERY, OP_INSERT, etc. in MongoDB 3.6). Uses BSON documents for both request and response; (3) Message structure: header (length, requestId, responseTo, opCode) + body (BSON document with operation details); (4) Compression: wire protocol supports zlib, Snappy, and zstd compression to reduce network bandwidth; (5) Authentication: SCRAM-SHA-1, SCRAM-SHA-256, MONGODB-X509, GSSAPI (Kerberos), PLAIN (LDAP) authentication mechanisms; (6) TLS/SSL: optional encryption of the wire protocol for security. MongoDB drivers: handle the wire protocol for you — you never interact with it directly. Drivers exist for all major languages: Node.js, Python (PyMongo), Java (MongoDB Java Driver), Go, C#, PHP, Ruby, Rust, etc. Connection pooling: drivers maintain connection pools to MongoDB — multiple socket connections to the server, reused across requests. Pool size is configurable (default: 100 per MongoClient). Create ONE MongoClient per application and reuse it — don't create/destroy per request.

Open this question on its own page
30

What is a compound index and when should you use it?

A compound index is an index on multiple fields, allowing efficient queries on combinations of those fields. db.users.createIndex({ lastName: 1, firstName: 1, age: -1 }). Leftmost prefix rule: a compound index on (A, B, C) supports queries on: A; A + B; A + B + C; but NOT B alone, C alone, or B + C (without A). The index can be used for queries that use a prefix of the index fields from left to right. ESR rule (Equality, Sort, Range): when designing compound indexes for queries with equality, sort, and range conditions, put: Equality fields first (they narrow the search most efficiently), Sort fields next (allows index-based sort, avoiding SORT stage), Range fields last. Example query: find({ status: "active", age: { $gte: 18 } }).sort({ name: 1 }) → index: { status: 1, name: 1, age: 1 }. Sort optimization: a compound index can serve as a sort if the sort fields appear in the index in the correct prefix order. Covered queries: if the query only needs fields in the index (including _id: 0 in projection), MongoDB never reads the documents — ultra-fast. Index intersection: MongoDB can combine multiple single-field indexes for some queries, but compound indexes are almost always more efficient. Limit: maximum 32 fields per compound index. Index key size limit: 8,192 bytes per index entry.

Open this question on its own page
Intermediate 17 questions

Practical knowledge for developers with hands-on experience.

01

What is the MongoDB aggregation pipeline optimization?

MongoDB performs several automatic optimizations on aggregation pipelines, plus best practices you should follow: Automatic optimizations: (1) $match + $sort coalescence: if a $sort immediately follows a $match, MongoDB applies the sort before the match if the sort uses an index — enables index scan for matching in sorted order; (2) $limit + $skip coalescence: consecutive $limit and $skip stages are combined; (3) $match + $match merging: two consecutive $match stages are merged into one; (4) Stage reordering: MongoDB may move $match before $project, $unwind, or $group to reduce document count early. Manual optimizations: (1) Put $match early: filter documents ASAP before other stages — reduce data volume flowing through the pipeline; (2) Use indexes: the first $match stage can use a collection index. If $match appears after other stages, it can't use indexes; (3) Projection early: $project to remove unneeded fields early — reduces memory and processing; (4) $limit early: if only N results needed, add $limit after $sort to avoid sorting all documents; (5) Avoid $unwind + $group when $group alone works: e.g., sum of array elements can use $sum directly on the array field in $group; (6) Index intersection for $match: design indexes to support the $match filter; (7) allowDiskUse: for large aggregations exceeding 100MB memory limit: db.orders.aggregate([...], { allowDiskUse: true }). Monitor with explain(): db.orders.explain("executionStats").aggregate([...]).

Open this question on its own page
02

What is MongoDB's write concern?

Write concern specifies the level of acknowledgment requested from MongoDB for write operations — it controls the trade-off between write speed and data durability. Write concern options: w (required acknowledgment): 0 — fire and forget, no acknowledgment (fastest, data loss possible); 1 — primary acknowledges (default — primary has written to memory); "majority" — majority of replica set members have acknowledged (most durable — survives primary failure); N — N members must acknowledge. j (journal): false (default) — acknowledged when in memory; true — acknowledged only after written to the on-disk journal (WAL) — survives process crash. wtimeout: timeout in milliseconds for the write concern — throws WriteConcernError if not acknowledged within this time. Common configurations: w:1, j:false — default, in-memory acknowledgment — fast but minor data loss window (MongoDB crash before journal flush); w:1, j:true — on-disk durability on primary — survives crashes; w:"majority", j:true — strongest guarantee — survives primary failure with on-disk durability. Setting at operation level: db.orders.insertOne({ ... }, { writeConcern: { w: "majority", j: true, wtimeout: 5000 } }). Setting at connection level: in the connection string: mongodb://host/?w=majority&j=true. For financial or critical data, always use w:"majority", j:true.

Open this question on its own page
03

What is MongoDB's read concern?

Read concern controls the consistency and isolation of data returned by read operations — which version of data is visible to a read. Read concern levels: local (default): returns the most recent data on the queried server. For primaries: data may not be replicated to majority. May be rolled back if primary fails before replication. available: same as local for replica sets; for sharded clusters, returns data from local shard even if rebalancing. May return orphaned data. majority: returns data acknowledged by a majority of replica set members — data will not be rolled back. Requires enableMajorityReadConcern (default true). Might return slightly older data than "local." linearizable: most strict — reads reflect all successful majority-acknowledged writes before the read. Slower — may wait for replication. Only for primary reads. snapshot: for transactions — reads from a consistent snapshot taken at the start of the transaction. Available in multi-document transactions and "majority" committed. Choosing read concern: local — high performance, slight staleness acceptable; majority — strong consistency, data won't be rolled back; linearizable — strongest, real-time read; snapshot — use in transactions. Setting: db.orders.find({}).readConcern("majority"). Combine write concern and read concern for strong consistency: write with w:"majority" and read with rc:"majority".

Open this question on its own page
04

What are partial indexes in MongoDB?

Partial indexes (MongoDB 3.2+) index only the documents in a collection that meet a specified filter expression, rather than all documents. More flexible and expressive than sparse indexes. Creating a partial index: db.users.createIndex({ email: 1 }, { partialFilterExpression: { status: "active" } }) — only indexes users where status is "active". Partial filter expressions support: equality ($eq), comparison ($gt, $gte, $lt, $lte), $exists, $type, $and (top-level only). Benefits over sparse indexes: (1) Can specify any filter condition, not just field existence; (2) Significantly reduces index size for selective conditions; (3) Faster index maintenance (fewer entries to update/insert); (4) Useful for frequently queried subsets. Query must include filter conditions: to use a partial index, the query must include a condition guaranteed to match the partial filter expression. db.users.find({ email: "alice@example.com", status: "active" }) — will use the partial index. db.users.find({ email: "alice@example.com" }) — will NOT use the partial index (might return inactive users, which aren't in the index). Partial unique index: enforce uniqueness only among documents matching the filter: db.orders.createIndex({ userId: 1 }, { unique: true, partialFilterExpression: { status: "active" } }) — allows multiple completed orders per user but only one active order. vs Sparse: sparse only handles field existence; partial handles any condition.

Open this question on its own page
05

What is the MongoDB aggregation $bucket and $bucketAuto stage?

$bucket categorizes documents into user-defined groups (buckets) based on a field's value — like a histogram or GROUP BY with value ranges. Syntax: { $bucket: { groupBy: "$price", boundaries: [0, 50, 100, 200, 500], default: "Other", output: { count: { $sum: 1 }, avgPrice: { $avg: "$price" }, products: { $push: "$name" } } } }. Each document is placed in the bucket whose lower boundary ≤ value < upper boundary. Documents outside all boundaries go to the "default" bucket. $bucketAuto: automatically determines bucket boundaries to evenly distribute documents across a specified number of buckets. { $bucketAuto: { groupBy: "$price", buckets: 5, output: { count: { $sum: 1 }, avgPrice: { $avg: "$price" } } } }. MongoDB computes boundaries to make each bucket have approximately the same number of documents. Use cases: (1) Price range analysis: "how many products in each price range?"; (2) Age distribution: "user count by age group"; (3) Performance histograms: "request count by latency bucket"; (4) Grade distribution. granularity option in $bucketAuto: allows buckets based on standard progression series (POWERSOF2, E12, R5, etc.) for physically meaningful ranges. $bucket vs $group with $switch: both achieve similar results; $bucket is more concise for range-based grouping; $group + $switch offers more flexibility.

Open this question on its own page
06

How does MongoDB handle schema validation?

MongoDB supports optional schema validation using JSON Schema, allowing you to enforce document structure while retaining flexibility. Creating a collection with validation: db.createCollection("users", { validator: { $jsonSchema: { bsonType: "object", required: ["name", "email"], properties: { name: { bsonType: "string", description: "must be a string and is required" }, email: { bsonType: "string", pattern: "^.+@.+$", description: "must be a valid email" }, age: { bsonType: "int", minimum: 0, maximum: 120 }, tags: { bsonType: "array", items: { bsonType: "string" } } } } }, validationLevel: "strict", validationAction: "error" }). Validation levels: strict (default): validates all inserts and updates; moderate: validates inserts and updates to existing valid documents (skips pre-existing invalid documents). Validation actions: error (default): rejects invalid documents; warn: allows invalid documents but logs a warning — useful for migrating existing data. Adding validation to existing collection: db.runCommand({ collMod: "users", validator: {...}, validationLevel: "strict" }). Check validation errors: when validation fails, the error message includes which schema rule was violated. Using Mongoose (ODM): Mongoose adds application-level schema validation for Node.js, which is more developer-friendly and runs before the database. Best practice: use both Mongoose validation (developer experience) and MongoDB validation (database-level guarantee).

Open this question on its own page
07

What is the $facet stage in MongoDB aggregation?

The $facet stage allows you to create multiple independent aggregation pipelines within a single aggregation stage, processing the same input documents in different ways simultaneously. Each sub-pipeline produces its own array of result documents. Output: a single document where each field is the result array from its sub-pipeline. Example — e-commerce faceted search: db.products.aggregate([ { $match: { category: "electronics" } }, { $facet: { "priceBuckets": [ { $bucket: { groupBy: "$price", boundaries: [0,50,100,500], default: "500+" } } ], "brandCounts": [ { $group: { _id: "$brand", count: { $sum: 1 } } }, { $sort: { count: -1 } }, { $limit: 10 } ], "totalCount": [ { $count: "total" } ], "ratingDistribution": [ { $group: { _id: "$rating", count: { $sum: 1 } } } ] } } ]). All four sub-pipelines process the same electronics documents in one pass. Benefits: (1) Single database round-trip for multiple aggregations; (2) Consistent snapshot — all sub-pipelines see the same data; (3) Perfect for faceted navigation (search filters + counts). Use cases: e-commerce faceted search (price ranges, brand filter counts, rating distribution), analytics dashboards (multiple metrics in one query), reporting (multiple breakdowns of same dataset). Limitations: $facet cannot contain certain stages: $facet, $out, $indexStats, $geoNear. Memory: each sub-pipeline has its own 100MB memory limit.

Open this question on its own page
08

What is the difference between $project and $addFields in MongoDB?

Both stages reshape documents in an aggregation pipeline, but with different effects on existing fields: $project: reshapes each document — you explicitly include or exclude fields. Any field not mentioned is excluded by default (except _id). Must explicitly set fields you want to keep. { $project: { name: 1, email: 1, fullName: { $concat: ["$firstName", " ", "$lastName"] }, _id: 0 } } — the output document has only: name, email, and the computed fullName. All other fields are dropped. Can both include and add computed fields in one stage. $addFields (alias: $set): adds new fields or overwrites existing fields — ALL existing fields are preserved. { $addFields: { fullName: { $concat: ["$firstName", " ", "$lastName"] }, ageCategory: { $cond: { if: { $gte: ["$age", 18] }, then: "adult", else: "minor" } } } } — the output has ALL original fields PLUS the two new computed fields. When to use each: use $addFields when you want to add computed fields while keeping everything else — cleaner and less verbose than $project when many fields must be preserved; use $project when you want to select a specific subset of fields (reduce document size, implement projections, shape for output). $unset: removes specified fields while keeping all others (opposite of $addFields): { $unset: ["password", "internalId"] }. Performance tip: use $project early to reduce document size flowing through the pipeline.

Open this question on its own page
09

What are multikey indexes in MongoDB?

Multikey indexes are automatically created by MongoDB when you create an index on a field that contains an array value. MongoDB creates separate index entries for each element in the array, allowing efficient queries for any element in the array. Automatic creation: db.products.createIndex({ tags: 1 }) — if tags is an array like ["electronics", "portable", "wireless"], MongoDB creates 3 index entries, one for each tag. Query: db.products.find({ tags: "electronics" }) uses the multikey index efficiently. Array element queries: equality (find docs where array contains value), $all (contains all specified values), $elemMatch (at least one element matches all conditions), $size (array has N elements). Compound multikey index restrictions: a compound index can have at most ONE array field. You cannot create a compound index on two fields that both contain arrays for the same document — this would create an exponential number of index entries. Covered queries with multikey: multikey indexes cannot fully cover a query — MongoDB must still access the document to return data (even if all projected fields are in the index) because array membership queries need document verification. Index size consideration: a document with a 1,000-element array creates 1,000 index entries — can significantly increase index size and write overhead. Consider whether indexing array fields is worth the cost. Embedded document arrays: db.orders.createIndex({ "items.productId": 1 }) — creates a multikey index on the productId field within each array element of "items."

Open this question on its own page
10

What is the MongoDB profiler?

The MongoDB profiler collects data about database operations — queries, writes, aggregations — to help identify slow queries and performance bottlenecks. It stores profiling data in the system.profile capped collection in each database. Profiling levels: 0 — off (default); 1 — slow operations only (those exceeding slowms threshold, default 100ms); 2 — all operations. Setting the profiler: db.setProfilingLevel(1, { slowms: 50 }) — capture queries taking more than 50ms. Viewing profiled queries: db.system.profile.find().sort({ millis: -1 }).limit(10) — slowest 10 operations. Profile document fields: op (operation type: query, insert, update, delete, command), ns (namespace), millis (execution time), planSummary (IXSCAN/COLLSCAN), nreturned, keysExamined, docsExamined, ts (timestamp), query (the filter). Finding slow queries: look for COLLSCAN (collection scan) in planSummary or high docsExamined/nreturned ratios. Atlas Performance Advisor: in MongoDB Atlas, the Performance Advisor automatically analyzes queries and suggests indexes — much more convenient than manual profiling. currentOp(): db.currentOp() shows in-progress operations — useful for finding long-running operations or locks. db.killOp(opId) to kill a running operation.

Open this question on its own page
11

What is the WiredTiger storage engine?

WiredTiger is MongoDB's default and primary storage engine since MongoDB 3.2, developed by the creators of BerkeleyDB. It replaced the older MMAPv1 engine. Key features: (1) MVCC (Multi-Version Concurrency Control): document-level concurrency — multiple readers/writers can access different documents simultaneously without blocking each other. Old MMAPv1 used collection-level locking (one write at a time per collection); (2) Compression: WiredTiger compresses all data on disk. Default: Snappy compression (fast, moderate ratio). Options: zlib (better compression, slower), zstd (best compression, modern), none. Typically reduces storage by 50-80%; (3) Data integrity: checksum verification for data corruption detection — WiredTiger validates data on read; (4) Write-ahead logging (journaling): WiredTiger uses a WAL (journal) for crash recovery and data durability; (5) Cache management: WiredTiger has its own internal cache (default: 50% of RAM minus 1GB, min 256MB) separate from the OS page cache. Configure: storage.wiredTiger.engineConfig.cacheSizeGB; (6) B-tree storage: data and indexes stored as B-trees. MongoDB In-Memory engine: alternative storage engine — keeps all data in RAM, no persistence. Ultra-fast for temporary/cache data. Enterprise-only. Encrypted storage engine: WiredTiger supports at-rest encryption (MongoDB Enterprise). Monitoring WiredTiger: db.serverStatus().wiredTiger shows cache usage, evictions, and compression stats.

Open this question on its own page
12

What is MongoDB's approach to concurrency?

MongoDB (with WiredTiger) uses Multi-Version Concurrency Control (MVCC) and document-level locking for fine-grained concurrency. Lock types: (1) Document-level locks: the default for WiredTiger — a write to document A doesn't block reads or writes to document B in the same collection. Much better than old collection-level locking; (2) Collection-level locks: certain operations lock the entire collection (e.g., creating/dropping an index, collection rename); (3) Database-level locks: some administrative operations; (4) Global-level locks: rare — some global maintenance operations. Intent locks: MongoDB uses intent lock hierarchy — before taking a document lock, acquires intent locks at collection and database levels (allows multiple concurrent document locks while exclusive collection-level operations can still be expressed). MVCC behavior: readers see a consistent snapshot (as of the start of their read), not blocked by concurrent writers; writers operate on the current version; concurrent writers to the same document block each other (first-write-wins with retry). Transactions: multi-document transactions use optimistic concurrency by default for reads (no read locks) but acquire write locks when modifying documents. Lock yield: long operations yield locks to allow other operations through. Monitoring: db.serverStatus().globalLock shows lock stats; db.currentOp() shows lock status per operation. WiredTiger's MVCC makes MongoDB well-suited for mixed read-write workloads.

Open this question on its own page
13

What is the difference between $unwind and $lookup in MongoDB?

$unwind: deconstructs an array field from input documents to output one document for each element of the array. Given a document with an array of 3 elements, $unwind produces 3 documents, each with a single value instead of the array. { $unwind: "$tags" }. Options: includeArrayIndex: "tagIndex" (add field with element's index); preserveNullAndEmpty: true (keep documents where the field is null, missing, or an empty array — otherwise they're removed). Use cases: group by array elements (count documents per tag), filter array elements (match inside arrays), join arrays with $lookup. $lookup: joins another collection to the current documents — adds data from another collection as an embedded array. { $lookup: { from: "products", localField: "productId", foreignField: "_id", as: "productDetails" } }. Together: $lookup + $unwind is a common pattern. After $lookup adds an array of matched documents, $unwind flattens it (like a JOIN with multiple matches becoming multiple rows): [ { $lookup: { from: "orders", localField: "_id", foreignField: "userId", as: "orders" } }, { $unwind: "$orders" }, { $group: { _id: "$_id", name: { $first: "$name" }, totalSpent: { $sum: "$orders.total" } } } ]. Key difference: $unwind works on arrays within a document; $lookup brings in arrays from another collection. They solve different problems but frequently work together for multi-collection aggregations.

Open this question on its own page
14

What is the aggregation $group stage and its accumulator operators?

The $group stage groups input documents by a specified identifier expression and applies accumulator operators to compute aggregated values for each group. It's MongoDB's equivalent of SQL's GROUP BY. Syntax: { $group: { _id: groupByExpression, field1: { accumulatorOp: expression }, ... } }. The _id specifies the group key; set _id to null to aggregate over all documents. Accumulator operators: $sum: sum of numeric values ({ $sum: "$price" }) or count with { $sum: 1 }; $avg: arithmetic mean; $min / $max: minimum/maximum value; $first / $last: first/last value in the group (depends on sort order); $push: creates an array of all values in the group; $addToSet: like $push but with unique values only; $count: count of documents (MongoDB 5.0+); $stdDevPop / $stdDevSamp: standard deviation; $mergeObjects: merges documents into one; $accumulator: custom accumulator with JavaScript (flexible but slow). Multi-field grouping: _id: { year: "$year", month: "$month" } — group by multiple fields. $group + $sort + $limit = Top-N per group: group → push all into array → $slice to take first N. Or use $topN/$bottomN (MongoDB 5.2+): { $topN: { n: 3, sortBy: { score: -1 }, output: "$name" } }.

Open this question on its own page
15

How does MongoDB handle geospatial data?

MongoDB has native support for geospatial data through GeoJSON objects and specialized indexes that enable location-based queries. GeoJSON format: MongoDB stores geospatial data in GeoJSON format. Point: { type: "Point", coordinates: [-73.97, 40.77] } ([longitude, latitude]). Polygon, MultiPolygon, LineString, etc. also supported. Note: coordinates are always [longitude, latitude], not [lat, lng]. 2dsphere index: for GeoJSON data on a spherical surface (Earth). db.places.createIndex({ location: "2dsphere" }). Handles Earth's curvature for accurate distance calculations. Geospatial operators: $near: returns documents near a point, sorted by distance. db.places.find({ location: { $near: { $geometry: { type: "Point", coordinates: [-73.97, 40.77] }, $maxDistance: 1000 } } }) — restaurants within 1km; $nearSphere: uses spherical geometry; $geoWithin: documents within a shape (polygon, circle, box) — does not sort by distance, no index required but faster with one; $geoIntersects: documents whose geometry intersects with the given shape; $centerSphere: specify a circle using spherical coordinates for $geoWithin. Aggregation: $geoNear pipeline stage — sorts by proximity, returns distance field. Must be the first stage. Use cases: find nearby restaurants, delivery radius checks, geofencing (is a user inside a zone), store locators, ride-sharing proximity queries. 2d index: for legacy coordinate pairs (not GeoJSON) on a flat surface — limited use.

Open this question on its own page
16

What is the MongoDB connection string format?

The MongoDB connection string specifies how to connect to a MongoDB deployment. Two formats: Standard format: mongodb://[username:password@]host[:port][/database][?options]. SRV format (DNS Seedlist): mongodb+srv://[username:password@]host[/database][?options] — used by Atlas; the SRV record provides the host list automatically. Examples: Local: mongodb://localhost:27017/mydb; With auth: mongodb://admin:password@host:27017/admin?authSource=admin; Replica set: mongodb://host1:27017,host2:27017,host3:27017/mydb?replicaSet=myReplicaSet; Atlas: mongodb+srv://username:password@cluster.abc.mongodb.net/mydb. Common connection string options: authSource=admin — database to authenticate against; replicaSet=name — replica set name; w=majority — write concern; readPreference=secondary; maxPoolSize=100 — connection pool size (default 100); connectTimeoutMS=30000 — connection timeout; socketTimeoutMS=360000 — socket timeout; ssl=true / tls=true — enable TLS; retryWrites=true — retry transient write errors (default true since MongoDB 4.0); retryReads=true. Security: never hardcode credentials in connection strings in source code. Use environment variables: process.env.MONGODB_URI. URL-encode special characters in passwords.

Open this question on its own page
17

What is the MongoDB aggregation $expr operator?

The $expr operator allows use of aggregation expressions within the $match stage (and $filter, $lookup pipeline conditions). It enables comparing two fields within the same document, something regular query operators ($eq, $gt) cannot do. Comparing two document fields: db.orders.find({ $expr: { $gt: ["$actualRevenue", "$estimatedRevenue"] } }) — finds orders where actual exceeds estimated revenue. Without $expr, you can't compare two fields from the same document in a query. Complex expressions in $match: db.products.find({ $expr: { $and: [ { $gte: ["$discount", 0.1] }, { $lt: ["$price", { $multiply: ["$originalPrice", 0.8] }] } ] } }) — products with 10%+ discount AND price less than 80% of original. Using $expr in $lookup pipeline conditions: { $lookup: { from: "inventory", let: { productId: "$productId", quantity: "$quantity" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: ["$$productId", "$product_id"] }, { $gte: ["$stock", "$$quantity"] } ] } } } ], as: "available_stock" } }. $expr vs regular query operators: regular operators ({ age: { $gt: 18 } }) compare field to literal; $expr compares field to field or evaluates aggregation expressions. Index usage with $expr: $expr can use indexes but may be less optimal than regular query operators — profile queries using $expr to verify index usage.

Open this question on its own page
Advanced 9 questions

Deep expertise questions for senior and lead roles.

01

How does MongoDB replication work internally?

MongoDB replication uses an oplog (operations log)-based, eventually-consistent replication protocol with Raft-inspired leader election. Oplog structure: the primary's oplog (local.oplog.rs) is a capped collection storing every write operation as an idempotent operation entry. Entries include: ts (timestamp), h (hash), op (operation type: i/u/d/c/n), ns (namespace), o (original operation), o2 (update criteria). Secondary replication flow: each secondary has a dedicated thread ("oplog fetcher") that polls the primary for new oplog entries using a tailable cursor — like tail -f but over the network. Retrieved entries are batched and applied to the secondary's data store. Oplog application: secondaries apply oplog entries in order (monotonically increasing timestamp). The application is idempotent — safe to re-apply if needed. Election protocol (Raft-based): each member has a priority (default 1); when the primary is unreachable (no heartbeat for electionTimeoutMillis = 10s), eligible secondaries start an election. Candidate increments term, requests votes from peers. Member votes for the first candidate it receives with a log at least as up-to-date as itself and it hasn't voted in this term. Candidate with majority wins, becomes primary. Replication lag monitoring: rs.printSecondaryReplicationInfo() shows each secondary's oplog lag. Read from secondary: secondaries may lag — reads with readPreference: secondary may return stale data. Use readPreference: "secondaryPreferred" to read from secondary only when available, falling back to primary.

Open this question on its own page
02

How does MongoDB sharding distribute data internally?

MongoDB sharding distributes data through a chunks and balancer system. Chunk: a contiguous range of shard key values, initially covering the entire key space. Default chunk size: 128MB. The chunks table in config servers maps: chunk range → shard. Chunk splitting: when a chunk grows beyond the threshold (chunkSize), MongoDB automatically splits it into two smaller chunks. The split creates a new chunk boundary at the median key value. Balancer: the balancer process (runs on mongos or config server primary) periodically checks chunk distribution across shards. If a shard has significantly more chunks than others (threshold: ≥9 chunk difference), the balancer migrates chunks to even the load. Migration: chunk data is copied from source shard to destination shard, then config metadata is updated, then cleanup. Migrations happen in background and are transparent to the application. Jumbo chunks: chunks that can't be split (all documents have the same shard key value — cardinality is too low) and therefore can't be migrated. Mark as "jumbo" — they stay on one shard, creating hotspots. Solution: choose a higher-cardinality shard key or use hashed sharding. Zone sharding: assign certain shard key ranges to specific shards (geographic routing: EU data → EU shard). Shard key strategies: hashed shard key (hash(field)) — evenly distributes inserts but can't do range-based routing; ranged shard key — enables range queries on one shard but risks hotspots with monotonically increasing keys. Pre-splitting: for large imports, pre-split chunks before inserting to distribute data evenly from the start.

Open this question on its own page
03

What is the WiredTiger cache and how does it affect performance?

The WiredTiger internal cache is an in-memory buffer pool that holds frequently accessed data and indexes, reducing disk I/O. Understanding and tuning it is critical for MongoDB performance. Default size: max(50% of (total RAM - 1GB), 256MB). On a 16GB server: (16-1) × 0.5 = 7.5GB. Cache structure: stores B-tree pages from data and index files. WiredTiger's cache uses different compression than disk — data in cache is uncompressed for faster access. Disk has zstd/snappy/zlib; cache is uncompressed. This means the cache holds 3-5x less data than the compressed disk representation. Working set: the portion of data actively accessed. Performance degrades sharply when working set exceeds cache size — frequent evictions require disk reads (I/O bottleneck). Monitor: if evictions are high and disk I/O is high, the cache is too small. Configuration: storage.wiredTiger.engineConfig.cacheSizeGB: 10 in mongod.conf. Don't set more than 70% of RAM — leave memory for OS page cache, aggregation sorting, connection buffers. OS page cache: WiredTiger intentionally leaves room for OS page cache, which also buffers database files. Total effective cache = WiredTiger cache + OS page cache. Monitoring: db.serverStatus().wiredTiger.cache — look at "bytes currently in the cache," "tracked dirty bytes in the cache," "pages evicted by application threads" (high = memory pressure). Eviction: WiredTiger evicts dirty pages (flushing to disk) and clean pages (just removing from cache) when cache is under pressure. Background eviction threads run proactively; foreground eviction (application threads help) indicates severe memory pressure.

Open this question on its own page
04

What is the Aggregation $setWindowFields stage?

The $setWindowFields stage (MongoDB 5.0+) adds window function computation to MongoDB aggregation — similar to SQL window functions (OVER, PARTITION BY, ORDER BY). It computes values based on a "window" of documents surrounding each document, without collapsing documents into groups (unlike $group). Syntax: { $setWindowFields: { partitionBy: "$customerId", sortBy: { orderDate: 1 }, output: { runningTotal: { $sum: "$amount", window: { documents: ["unbounded", "current"] } }, movingAvg: { $avg: "$amount", window: { documents: [-2, 0] } }, rank: { $rank: {} } } } }. Window types: documents: based on number of documents relative to current ([-2, 0] = 2 before + current); range: based on value range of the sort field (["-1 day", "current"] for time-based); unbounded: from start/end of partition. Available functions: $sum, $avg, $min, $max, $count — cumulative or sliding; $rank, $denseRank — ranking within partition; $documentNumber — row number; $first, $last — first/last in window; $shift — value from N documents before/after; $linearFill, $locf — interpolation for missing values. Use cases: (1) Running totals (cumulative sales); (2) Moving averages (7-day rolling average); (3) Ranking (rank customers by spend per region); (4) Comparing a row to previous row (calculate change vs last period); (5) Fill missing time-series data.

Open this question on its own page
05

How does MongoDB handle index builds on large collections?

Building indexes on large collections is a significant operation. MongoDB 4.2+ introduced Hybrid Index Build which replaced the two previous approaches (foreground and background builds). Hybrid Index Build (MongoDB 4.2+): holds an exclusive lock only briefly at the start and end of the build. During the bulk of the build (scanning and sorting all documents), only an intent lock is held — reads and writes can continue normally. At the end, a brief exclusive lock finalizes the index. This effectively makes all index builds non-blocking in practice. Index build flow: (1) Lock the collection briefly; start the build; (2) Scan all documents and insert into a sorted buffer; (3) As new writes come in, track them in a "side writes" table; (4) Flush the sorted buffer to disk creating the initial index structure; (5) Drain the "side writes" table — apply writes that arrived during the build; (6) Acquire a brief exclusive lock; finalize the index; commit; (7) Release lock. Performance impact: index builds consume significant I/O (scanning all data + sort) and CPU. On a replica set, builds are coordinated: (1) Primary builds the index first; (2) After commit, replication propagates the index creation to secondaries; (3) Secondaries build the index one at a time (rolling build). Rolling index build (manual): for zero-impact builds on production: build on secondaries first (they're not serving primary reads), then step down primary, build on new secondary. Monitoring: db.currentOp({ "command.createIndexes": { $exists: true } }) shows index build progress. db.adminCommand({ currentOp: 1, $all: true }) for all operations.

Open this question on its own page
06

What is the MongoDB Aggregation $merge and $out stage?

$out and $merge are output stages that write aggregation results to a collection rather than returning them to the caller. $out: replaces an entire collection with the aggregation results. Atomically: aggregates, writes to a temp collection, then renames to the target (old collection is replaced). All or nothing — either the whole output succeeds or the original collection is untouched. Limitations: can't output to a sharded collection; creates or replaces the target collection; must be the last stage. { $out: "monthly_revenue_summary" }. $merge (MongoDB 4.2+): more flexible — can merge results into an existing collection with configurable behavior. Options: { $merge: { into: "summary", on: "_id", whenMatched: "merge", whenNotMatched: "insert" } }. on: the field(s) to match against existing documents (like a primary key for the merge). whenMatched: what to do when a document with the same "on" key exists: "replace" (replace existing), "keepExisting" (don't update), "merge" (merge new fields into existing), "fail" (throw error), [custom pipeline] (transform the matched document). whenNotMatched: "insert" (insert new document) or "discard" (ignore). Use cases for $merge: incremental updates (add daily data to monthly summary without recomputing all history), upsert from aggregation (update a cache collection), maintaining denormalized views, real-time dashboards updated by change streams + $merge.

Open this question on its own page
07

What are MongoDB Atlas Search and Vector Search?

Atlas Search is a full-text search capability built directly into MongoDB Atlas, powered by Apache Lucene. It eliminates the need for a separate search infrastructure (like Elasticsearch) for many use cases. How it works: Atlas creates and maintains Lucene indexes on your MongoDB data in a synchronized Atlas Search node (local or on a separate dedicated tier). Changes to MongoDB documents are automatically propagated to the Lucene index via the oplog. Searches use the $search aggregation stage. $search example: db.products.aggregate([ { $search: { index: "default", text: { query: "wireless headphones", path: ["name", "description"], fuzzy: { maxEdits: 1 } } } }, { $limit: 10 }, { $project: { name: 1, score: { $meta: "searchScore" } } } ]). Atlas Search features: fuzzy matching, autocomplete, highlighting, facets, filters with AND/OR/NOT, relevance scoring, synonym support, phrase queries, wildcard, regex, date/geo range queries, language-specific analyzers (stemming, stop words). Atlas Vector Search (MongoDB 6.0+): store, index, and query vector embeddings for AI-powered semantic search, recommendations, and RAG (Retrieval Augmented Generation). Store embeddings as arrays in documents: { text: "...", embedding: [0.1, 0.3, ...] }. Create a vector index specifying the dimensions and similarity function (cosine, euclidean, dotProduct). Query with $vectorSearch stage: find documents with embeddings most similar to a query vector. Enables: semantic search, image similarity, recommendation systems, LLM memory/retrieval.

Open this question on its own page
08

How do you optimize MongoDB query performance?

Systematic MongoDB query optimization involves profiling, indexing, and schema design: (1) Identify slow queries: enable profiler (db.setProfilingLevel(1, { slowms: 100 })); use Atlas Performance Advisor; check system.profile; look at COLLSCAN stages. (2) Add appropriate indexes: use explain("executionStats") to verify IXSCAN vs COLLSCAN; follow ESR rule for compound indexes (Equality → Sort → Range); consider covered queries (all needed fields in the index); use partial indexes for selective queries; use sparse indexes for optional fields with unique constraints. (3) Aggregation optimization: put $match and $limit as early as possible; use $project early to reduce document size; avoid $unwind + $group when $group suffices; use allowDiskUse for large aggregations; monitor with explain(). (4) Schema optimization: embed data that's always accessed together; avoid fetching large documents for small field subsets; use projections to retrieve only needed fields; avoid large arrays (multikey index overhead); store frequently accessed computed values (denormalize). (5) Connection optimization: use a single MongoClient with connection pooling; don't create new connections per request; set appropriate maxPoolSize. (6) Write optimization: use bulk write operations (bulkWrite, insertMany); use appropriate write concern (don't use w:"majority" for non-critical writes); batch operations when possible. (7) WiredTiger cache: ensure working set fits in cache (monitor evictions); properly size cache (50% of RAM). (8) Hardware: SSDs dramatically improve random I/O; adequate RAM for working set.

Open this question on its own page
09

What is the MongoDB Aggregation Framework's $graphLookup stage?

The $graphLookup stage performs a recursive traversal of a collection — like a recursive SQL query or graph traversal. It recursively looks up documents that match a condition, starting from a root document and following a chain of connected documents. Syntax: { $graphLookup: { from: "employees", startWith: "$reportsTo", connectFromField: "reportsTo", connectToField: "_id", as: "reportingChain", maxDepth: 10, depthField: "hierarchyLevel", restrictSearchWithMatch: { department: "$department" } } }. Parameters: from: collection to search; startWith: expression computing the initial set of values; connectFromField: field in the "from" collection to follow recursively; connectToField: field in the "from" collection to match against; as: output array field name; maxDepth: maximum recursion depth (required to prevent infinite loops in cyclic graphs); depthField: optional field to add to each result document indicating its depth; restrictSearchWithMatch: additional filter conditions on each recursive step. Use cases: (1) Organizational hierarchy: find all reports under a manager (recursive); (2) Category trees: find all subcategories of a category; (3) Friend networks: find all friends-of-friends within N degrees; (4) Bill of materials: components of components; (5) Thread replies: nested comment trees. Performance: $graphLookup can be expensive on large collections — index the connectToField for efficient lookups at each depth level.

Open this question on its own page
Back to All Topics 56 questions total