Chapter 13 of 13
Top 45 MongoDB Interview Questions
Documents and collections, CRUD operations, query operators, schema design, indexes, aggregation, and Mongoose - organized by topic and difficulty, with explanations that go beyond a one-line definition and a code example for each one. Search or filter to find what you need, and share a direct link to any question.
Showing 45 of 45 questions
The first 15 of 45 questions are free to read. Sign up free to unlock every answer and track your progress.
Unlock all 45MongoDB is a document-oriented NoSQL database that stores data as flexible, JSON-like documents (backed by BSON) rather than rows in fixed tables. Unlike a relational database, it does not require every record in a collection to share the same fields, and it favors embedding related data within a single document over enforcing joins across many tables. A common mistake is assuming MongoDB has no structure at all - most applications still design an implicit schema, they just enforce it in the application layer or through optional validation rules instead of the database engine.
Example
db.users.insertOne({ name: "Ada", email: "ada@example.com" })
db.users.insertOne({ name: "Alan", email: "alan@example.com", roles: ["admin"] })
// Both documents coexist fine in the same collectionBSON (Binary JSON) is the binary-encoded format MongoDB actually stores and transmits documents in. It extends JSON with additional types plain JSON lacks - a proper Date, a 64-bit integer, Decimal128, and ObjectId - and its binary encoding is faster to parse and traverse than text-based JSON. A common mistake is assuming documents are literally stored as JSON text; mongosh only displays them in a JSON-like syntax for readability.
A collection is a named group of documents, roughly analogous to a table, but it does not require every document to have the same fields or types by default. This flexible schema is a deliberate design choice that lets applications evolve document shapes without a migration step, though most teams still add validation rules or ODM-level schemas to avoid accidental inconsistency. A common mistake is treating "flexible schema" as "no schema at all" and designing without any consistent shape in mind.
Every document requires a unique "_id" field acting as its primary key within the collection. If none is supplied, MongoDB auto-generates a 12-byte ObjectId encoding a timestamp, a machine/process identifier, and an incrementing counter, guaranteeing uniqueness without central coordination. A common mistake is assuming "_id" can be changed after insertion - it is immutable, so correcting it requires deleting and re-inserting the document.
Example
db.users.insertOne({ name: "Grace" })
// _id auto-assigned, e.g. ObjectId("64f1c2a5e4b0a1b2c3d4e5f6")
db.users.insertOne({ _id: "grace@example.com", name: "Grace" })
// custom _id used insteadMongoDB tends to be a weaker fit for heavily relational data that requires constant cross-entity queries under strict consistency, such as a double-entry accounting ledger, since relational databases have decades of mature tooling built specifically for enforced constraints and transactional joins. It is also less ideal when your access patterns are unpredictable ad-hoc reporting across many normalized entities. A common mistake is forcing a document model onto data that is fundamentally tabular and highly interrelated just because MongoDB is trendy or already in the stack.
mongosh is the modern MongoDB shell, a JavaScript REPL that connects directly to a MongoDB server so you can run commands, inspect data, and prototype queries interactively before writing application code. Commands like db.collection.find() are actual JavaScript method calls evaluated in that REPL, not a separate query language. A common mistake is writing complex application logic entirely inside mongosh instead of using it for exploration and quick fixes, then implementing the real logic in the driver or an ODM.
insertOne() adds a single document and returns its acknowledgment (including the generated or supplied "_id"), while insertMany() inserts an array of documents in a single round trip, which is far more efficient than looping insertOne() calls for bulk data. A common mistake is inserting large batches one document at a time, which multiplies network overhead unnecessarily.
Example
db.users.insertMany([
{ name: "Grace", age: 45 },
{ name: "Alan", age: 41 }
])find() returns a cursor over every document matching the filter, which you typically iterate or convert with .toArray(), while findOne() returns just the first matching document (or null) directly, without a cursor. A common mistake is calling find() when only a single expected result is needed, adding unnecessary cursor handling to the code.
Example
db.users.find({ age: { $gte: 30 } }).toArray()
db.users.findOne({ email: "ada@example.com" })$set changes or adds only the fields you specify inside the update object, leaving every other field on the document untouched. Passing a plain object without any operators to updateOne(), by contrast, replaces the entire matched document with that object. A common mistake is forgetting $set and unintentionally wiping out fields that were not included in the update payload.
Example
db.users.updateOne({ name: "Ada" }, { $set: { age: 31 } })
// only "age" changes; all other fields remain untouchedPassing { upsert: true } tells MongoDB to insert a new document (built from the filter and update) if no document matches the filter, instead of doing nothing. It is commonly used for settings-style or counter-style documents keyed by an identifier, where you want "create if missing, update if present" in a single atomic call. A common mistake is relying on upsert with a loosely-specified filter, which can accidentally create duplicate near-identical documents if the filter does not uniquely identify the intended record.
Example
db.settings.updateOne(
{ userId: "u1" },
{ $set: { theme: "dark" } },
{ upsert: true }
)deleteOne() removes only the first document matching the filter, while deleteMany() removes every matching document. Passing an empty filter object {} to deleteMany() matches every document in the collection, deleting everything in it - a command worth double-checking before running against production data. A common mistake is assuming an empty filter means "match nothing" when it actually means "match everything."
bulkWrite() accepts an array of operation descriptors (insertOne, updateOne, updateMany, deleteOne, replaceOne, etc.) and executes them together, either ordered (stopping at the first error) or unordered (continuing past failures), which is far more efficient than issuing each operation as a separate network call. A common mistake is not understanding ordered vs. unordered semantics - an ordered bulk write halts on the first failure, potentially leaving only some operations applied, while unordered continues and reports all failures together at the end.
Example
db.products.bulkWrite([
{ insertOne: { document: { name: "Mouse", price: 20 } } },
{ updateOne: { filter: { name: "Keyboard" }, update: { $set: { price: 45 } } } },
{ deleteOne: { filter: { name: "Old Item" } } }
])$gt and $lt filter documents where a field is greater than or less than a given value (with $gte/$lte for inclusive bounds), while $in matches documents where the field equals any value in a supplied array. A common mistake is trying to check array membership with a plain equality comparison, which only matches an exact full-array value rather than "any of these values."
Example
db.products.find({ price: { $gt: 10, $lt: 50 } })
db.products.find({ category: { $in: ["books", "toys"] } })$exists checks whether a field is present (or, with $exists: false, absent) on a document at all, independent of its value - which is especially useful given MongoDB's flexible schema, where not every document in a collection necessarily has every field. A common mistake is confusing $exists: true with checking for a non-null value; a field explicitly set to null still satisfies $exists: true.
Example
db.users.find({ phone: { $exists: true } })$or and $nor take an array of conditions and match documents satisfying at least one, or none, of them respectively. $and also takes an array of conditions requiring all to match, but MongoDB already implicitly ANDs separate top-level fields in a filter object, so explicit $and is mainly needed when repeating conditions on the same field name or nesting logical operators. A common mistake is wrapping every query in $and unnecessarily when a plain multi-field filter object would already express the same logic more simply.
Example
db.products.find({
$or: [{ category: "electronics" }, { price: { $lt: 20 } }]
})