MongoDB interviews test your understanding of document modelling, the aggregation pipeline, indexing, replication, sharding, and performance tuning. This guide covers the 50 most common questions — with concise answers and shell/code examples.
Quick reference
| Topic | Most asked questions |
|---|---|
| Core concepts | Document model, BSON, collections |
| CRUD | insertOne/Many, find, update operators, delete |
| Aggregation | $match, $group, $lookup, $unwind, $project |
| Indexes | Single, compound, text, TTL, explain() |
| Schema design | Embedding vs referencing, 1:N, M:N patterns |
| Replication | Replica sets, elections, oplog |
| Sharding | Shard keys, chunks, mongos, balancer |
| Transactions | Multi-document ACID, sessions |
| Performance | Query plans, index hints, profiler |
| Security | Authentication, RBAC, TLS |
Core concepts
1. What is MongoDB and how does it differ from relational databases?
MongoDB is a document-oriented NoSQL database. Instead of rows in tables, it stores BSON documents in collections.
| Feature | MongoDB | Relational DB |
|---|---|---|
| Data unit | Document (BSON) | Row (typed columns) |
| Schema | Flexible (schemaless) | Fixed schema |
| Joins | $lookup aggregation |
Native JOINs |
| Transactions | Multi-doc ACID (v4.0+) | Native ACID |
| Scaling | Horizontal (sharding) | Vertical (primarily) |
| Query language | MQL / Aggregation Pipeline | SQL |
| Relationships | Embed or reference | Foreign keys |
Use MongoDB when your data is document-shaped, schema evolves frequently, or horizontal scaling is required.
2. What is BSON?
BSON (Binary JSON) is MongoDB's internal serialisation format. It extends JSON with additional types:
| BSON type | Example use |
|---|---|
| ObjectId | _id field |
| Date | Timestamps (ISODate) |
| Int32 / Int64 | Numeric fields |
| Decimal128 | Financial calculations |
| Binary | File chunks, UUIDs |
| Regex | Pattern fields |
| Array | Ordered lists |
| Embedded document | Nested objects |
BSON is more efficient than JSON for encoding/decoding and supports types JSON doesn't (e.g. dates, binary data).
3. What is a document in MongoDB?
A document is a BSON object — analogous to a JSON object or a table row:
{
_id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e1"),
name: "Alice",
age: 30,
address: { city: "London", postcode: "EC1A" },
tags: ["developer", "mongodb"]
}
- Maximum document size: 16 MB
_idis mandatory and unique per collection; auto-generated as ObjectId if omitted
4. What is a collection? Does it enforce a schema?
A collection is a group of documents — analogous to a table. By default MongoDB is schemaless: documents in the same collection can have different fields.
You can enforce a schema using JSON Schema validation:
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "email"],
properties: {
name: { bsonType: "string" },
email: { bsonType: "string", pattern: "^.+@.+$" }
}
}
}
})
5. What is ObjectId? How is it structured?
ObjectId is MongoDB's default _id type — a 12-byte value:
| Bytes | Content |
|---|---|
| 4 | Unix timestamp (seconds) |
| 5 | Random value (machine + process) |
| 3 | Incrementing counter |
This makes ObjectIds globally unique and roughly sortable by insertion time without a central counter.
const id = new ObjectId()
id.getTimestamp() // Date when the id was created
CRUD operations
6. How do you insert documents?
// Insert one
db.users.insertOne({ name: "Bob", age: 25 })
// Insert many (ordered by default — stops on first error)
db.users.insertMany([
{ name: "Carol", age: 28 },
{ name: "Dave", age: 35 }
], { ordered: false }) // ordered:false continues past errors
insertMany with ordered: false is faster in bulk loads because individual failures don't abort the batch.
7. How do you query documents?
// All documents
db.users.find()
// Equality filter
db.users.find({ age: 30 })
// Comparison operators
db.users.find({ age: { $gte: 25, $lt: 40 } })
// Logical operators
db.users.find({ $or: [{ city: "London" }, { city: "Paris" }] })
// Field projection (1 = include, 0 = exclude)
db.users.find({ age: { $gt: 20 } }, { name: 1, _id: 0 })
// Nested field
db.users.find({ "address.city": "London" })
// Array element match
db.users.find({ tags: "developer" })
// findOne returns first match only
db.users.findOne({ name: "Alice" })
8. What are the main update operators?
// $set — set field values
db.users.updateOne({ name: "Alice" }, { $set: { age: 31 } })
// $unset — remove a field
db.users.updateOne({ name: "Alice" }, { $unset: { age: "" } })
// $inc — increment / decrement
db.users.updateOne({ name: "Alice" }, { $inc: { loginCount: 1 } })
// $push — append to array
db.users.updateOne({ name: "Alice" }, { $push: { tags: "admin" } })
// $addToSet — push only if not already present
db.users.updateOne({ name: "Alice" }, { $addToSet: { tags: "admin" } })
// $pull — remove matching array elements
db.users.updateOne({ name: "Alice" }, { $pull: { tags: "developer" } })
// $rename — rename a field
db.users.updateMany({}, { $rename: { "oldName": "newName" } })
// upsert — insert if not found
db.users.updateOne(
{ email: "new@example.com" },
{ $set: { name: "New User" } },
{ upsert: true }
)
9. What is the difference between updateOne, updateMany, and replaceOne?
| Method | Behaviour |
|---|---|
updateOne |
Modifies first matching document using update operators |
updateMany |
Modifies all matching documents using update operators |
replaceOne |
Replaces entire document (except _id) with new document |
replaceOne does not use $set — the entire document body is swapped:
db.users.replaceOne({ name: "Alice" }, { name: "Alice", age: 32, city: "Berlin" })
// All fields not in the replacement document are removed
10. How do you delete documents?
// Delete first matching document
db.users.deleteOne({ name: "Alice" })
// Delete all matching documents
db.users.deleteMany({ age: { $lt: 18 } })
// Delete all documents in collection (keeps collection + indexes)
db.users.deleteMany({})
// Drop entire collection (removes collection, indexes, metadata)
db.users.drop()
Aggregation pipeline
11. What is the aggregation pipeline?
The aggregation pipeline processes documents through a series of stages, each transforming the data:
Collection → [$stage1] → [$stage2] → ... → result
Common stages:
| Stage | Purpose |
|---|---|
$match |
Filter documents (like find) |
$group |
Group and accumulate values |
$project |
Reshape documents (include/exclude/compute fields) |
$sort |
Sort results |
$limit / $skip |
Paginate |
$lookup |
Left outer join with another collection |
$unwind |
Deconstruct array field into separate documents |
$addFields |
Add computed fields |
$count |
Count documents |
$facet |
Multiple sub-pipelines on same input |
$bucket |
Range-based grouping |
$out / $merge |
Write results to a collection |
12. Write an aggregation to find the total sales per category.
db.orders.aggregate([
{ $match: { status: "completed" } }, // filter first (use indexes)
{ $group: {
_id: "$category", // group by category
totalSales: { $sum: "$amount" },
orderCount: { $sum: 1 },
avgAmount: { $avg: "$amount" }
}},
{ $sort: { totalSales: -1 } }, // highest first
{ $limit: 10 }
])
Always put $match early to reduce the document set before expensive stages.
13. How does $lookup work?
$lookup performs a left outer join between two collections:
db.orders.aggregate([
{
$lookup: {
from: "products", // collection to join
localField: "productId", // field in orders
foreignField: "_id", // field in products
as: "productInfo" // output array field
}
},
{ $unwind: "$productInfo" } // flatten the array (converts to object)
])
For correlated sub-queries use the pipeline form:
{
$lookup: {
from: "inventory",
let: { ordProd: "$productId" },
pipeline: [
{ $match: { $expr: { $eq: ["$_id", "$$ordProd"] } } },
{ $project: { stock: 1 } }
],
as: "stock"
}
}
14. What does $unwind do and when do you use it?
$unwind deconstructs an array field — it outputs one document per array element:
// Input: { _id: 1, tags: ["a", "b", "c"] }
// $unwind: "$tags" produces:
// { _id: 1, tags: "a" }
// { _id: 1, tags: "b" }
// { _id: 1, tags: "c" }
Use cases:
- Group by individual array elements (
$groupafter$unwind) - Join array elements to another collection via
$lookup
Options: preserveNullAndEmptyArrays: true keeps documents where the field is missing/empty.
15. How do you use $expr in a query?
$expr lets you use aggregation expressions inside $match or find:
// Find documents where spent > budget (comparing two fields)
db.campaigns.find({
$expr: { $gt: ["$spent", "$budget"] }
})
// In aggregation
db.orders.aggregate([
{ $match: { $expr: { $gte: ["$amount", "$minAmount"] } } }
])
Without $expr, you can only compare a field to a literal value, not to another field.
Indexes
16. What types of indexes does MongoDB support?
| Index type | Use case |
|---|---|
| Single field | { age: 1 } — ascending; { age: -1 } — descending |
| Compound | { lastName: 1, firstName: 1 } — multi-field queries |
| Multikey | Automatic when field is an array |
| Text | Full-text search ({ content: "text" }) |
| Geospatial | 2dsphere for GeoJSON, 2d for flat coordinates |
| Hashed | { _id: "hashed" } for hash-based sharding |
| Wildcard | { "$**": 1 } — indexes all fields dynamically |
| TTL | Expires documents automatically (expireAfterSeconds) |
| Sparse | Only indexes documents that have the field |
| Partial | Only indexes documents matching a filter expression |
17. What is the ESR rule for compound indexes?
When building compound indexes, order fields as Equality → Sort → Range:
// Query: find users in London aged 25-35, sorted by name
// ESR index:
db.users.createIndex({ city: 1, name: 1, age: 1 })
// ^Equality ^Sort ^Range
This order allows MongoDB to:
- Jump directly to the equality value (city)
- Read documents in sort order (no in-memory sort needed)
- Apply the range filter on the remaining documents
18. What is a covered query?
A query is covered when all fields the query needs are in the index — MongoDB never touches the actual documents:
db.users.createIndex({ email: 1, name: 1 })
// This query is covered (only email and name accessed, _id excluded)
db.users.find({ email: "a@b.com" }, { name: 1, _id: 0 })
Covered queries are the fastest possible — they read only the index B-tree.
19. How do you analyse a query's performance?
db.users.find({ age: { $gt: 25 } }).explain("executionStats")
Key fields in the output:
| Field | What to look for |
|---|---|
winningPlan.stage |
IXSCAN (index) is good; COLLSCAN (full scan) is bad |
executionStats.totalDocsExamined |
Should equal totalDocsReturned ideally |
executionStats.executionTimeMillis |
Actual execution time |
indexBounds |
Which part of the index was used |
If you see COLLSCAN on a high-volume collection → add an index.
20. What is a TTL index?
A TTL (Time-To-Live) index automatically deletes documents after a specified time:
// Delete documents 30 days after their createdAt date
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 2592000 })
Requirements:
- Field must be a BSON
Datetype - A background thread checks and deletes expired documents every 60 seconds (approximate)
- Does not work on capped collections
- Only one TTL index per collection
Schema design
21. When should you embed vs reference documents?
| Criterion | Embed | Reference |
|---|---|---|
| Access pattern | Always loaded together | Loaded independently |
| Relationship | 1:few, 1:1 | 1:many, M:N |
| Document size concern | Small sub-documents | Avoids 16 MB limit |
| Write pattern | Update atomically | Update independently |
| Data duplication | Acceptable for reads | Avoid duplication |
Rule of thumb: "Embed if you can, reference if you must."
22. How do you model a one-to-many relationship?
Option 1 — Embed (one-to-few): blog post with comments (small, bounded list)
{
_id: ObjectId("..."),
title: "MongoDB tips",
comments: [
{ author: "Alice", text: "Great post!", date: ISODate("...") },
{ author: "Bob", text: "Very helpful", date: ISODate("...") }
]
}
Option 2 — Reference (one-to-many): user with thousands of orders
// users collection
{ _id: ObjectId("u1"), name: "Alice" }
// orders collection
{ _id: ObjectId("o1"), userId: ObjectId("u1"), amount: 99 }
Option 3 — Bucket pattern (time-series / IoT): group readings into buckets of N measurements to avoid unbounded arrays.
23. How do you model a many-to-many relationship?
// students and courses
// students collection
{ _id: ObjectId("s1"), name: "Alice", courseIds: [ObjectId("c1"), ObjectId("c2")] }
// courses collection
{ _id: ObjectId("c1"), title: "MongoDB 101", studentIds: [ObjectId("s1"), ObjectId("s3")] }
For very large M:N sets, store the relationship in a junction collection instead of arrays:
// enrollments collection
{ studentId: ObjectId("s1"), courseId: ObjectId("c1"), enrolledAt: ISODate("...") }
24. What is the Bucket pattern?
The Bucket pattern groups time-series or sequential data into documents that hold N measurements:
// Instead of one document per sensor reading:
{ deviceId: "sensor1", timestamp: ISODate("..."), temp: 22.5 }
// Bucket into hourly batches:
{
deviceId: "sensor1",
date: ISODate("2026-07-15T14:00:00Z"),
count: 60,
readings: [
{ t: ISODate("2026-07-15T14:00:10Z"), temp: 22.5 },
// ... up to 59 more
],
minTemp: 22.1,
maxTemp: 23.0
}
Benefits: fewer documents → smaller index → faster range queries. MongoDB's time series collections (v5.0+) implement this automatically.
25. What is the Outlier pattern?
When most documents have small arrays but a few have very large ones:
// Normal document
{ _id: ObjectId("b1"), title: "Normal book", reviewIds: [ObjectId("r1"), ObjectId("r2")] }
// Outlier (thousands of reviews — add overflow flag)
{ _id: ObjectId("b2"), title: "Bestseller", reviewIds: [...first 1000...], hasOverflow: true }
// Overflow documents
{ _id: ObjectId("bk_b2_1"), bookId: ObjectId("b2"), reviewIds: [...next 1000...] }
Application code checks hasOverflow and fetches additional pages if needed.
Replication
26. What is a replica set?
A replica set is a group of MongoDB instances that maintain the same dataset:
- Primary — receives all write operations
- Secondaries (1+) — replicate from the primary's oplog asynchronously
- Arbiter (optional) — participates in elections but holds no data
Primary ──(oplog)──► Secondary 1
└──(oplog)──► Secondary 2
If the primary fails, an election chooses a new primary from the secondaries. Minimum recommended size: 3 members (odd number avoids split-brain).
27. What is the oplog?
The oplog (operations log) is a capped collection (local.oplog.rs) on each replica set member that records every write operation in an idempotent, replayable format. Secondaries continuously tail the oplog to stay in sync.
Key points:
- Capped — oldest entries are overwritten (configurable size)
- Oplog window matters: if a secondary falls too far behind, it needs a resync
- Change Streams are built on top of the oplog
28. What are read preferences?
Read preference controls which replica set member satisfies read operations:
| Mode | Description |
|---|---|
primary (default) |
All reads from primary (strongly consistent) |
primaryPreferred |
Primary if available, else secondary |
secondary |
Always from a secondary (may read stale data) |
secondaryPreferred |
Secondary if available, else primary |
nearest |
Lowest network latency member |
db.users.find().readPref("secondaryPreferred")
Use secondary read preference for reporting/analytics to offload the primary.
29. What is write concern?
Write concern specifies how many replica set members must acknowledge a write before MongoDB considers it successful:
db.orders.insertOne(
{ item: "widget", qty: 10 },
{ writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
)
| Option | Description |
|---|---|
w: 0 |
Fire and forget (no acknowledgement) |
w: 1 |
Primary only |
w: "majority" |
Majority of voting members (safe for most production use) |
j: true |
Wait for the journal to be written to disk |
wtimeout |
Max milliseconds to wait |
w: "majority" + j: true is the safest combination.
30. What causes a replica set election and how long does it take?
Elections occur when:
- Primary becomes unreachable (heartbeat fails after ~10 s)
rs.stepDown()is called explicitly- Priority change forces re-election
Election duration: typically 10–30 seconds during which no writes are accepted. The node with the highest priority (and most up-to-date oplog) wins.
Sharding
31. What is sharding and why is it used?
Sharding horizontally partitions data across multiple servers (shards) to:
- Scale writes beyond a single server's capacity
- Store datasets larger than a single machine's disk
Architecture:
Client → mongos (query router) → Config servers (metadata)
↓
Shard 1 | Shard 2 | Shard 3 (each is a replica set)
32. What is a shard key and how do you choose one?
The shard key determines how data is distributed across shards. Criteria for a good shard key:
| Criterion | Why it matters |
|---|---|
| High cardinality | Allows many distinct chunks |
| Even write distribution | Avoids hot shards |
| Query isolation | Most queries include the shard key → single shard |
| Not monotonically increasing | Avoids write hot spot on last chunk |
Bad: { createdAt: 1 } — all new inserts go to the last chunk (monotonic hot spot).
Good: { userId: "hashed" } — hashed key distributes writes evenly.
Best for range queries: compound key { region: 1, userId: 1 } — isolates queries by region.
33. What are ranged vs hashed sharding?
| Strategy | How it works | Best for |
|---|---|---|
| Ranged | Documents with adjacent shard key values are on the same chunk/shard | Range queries on shard key |
| Hashed | Shard key is hashed; documents distributed uniformly | High write throughput; avoids hot spots |
// Ranged sharding
sh.shardCollection("mydb.orders", { customerId: 1 })
// Hashed sharding
sh.shardCollection("mydb.orders", { customerId: "hashed" })
Transactions
34. Does MongoDB support ACID transactions?
Yes — since v4.0 for replica sets and v4.2 for sharded clusters, MongoDB supports multi-document ACID transactions:
const session = client.startSession()
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
})
try {
db.accounts.updateOne(
{ _id: "A" }, { $inc: { balance: -100 } }, { session }
)
db.accounts.updateOne(
{ _id: "B" }, { $inc: { balance: 100 } }, { session }
)
await session.commitTransaction()
} catch (err) {
await session.abortTransaction()
} finally {
session.endSession()
}
Design preference: MongoDB's document model lets you atomically update a single document (including arrays) — avoid multi-document transactions when possible for better performance.
35. What is the difference between read concern local, majority, and snapshot?
| Read concern | Guarantee |
|---|---|
local (default) |
Returns the most recent data on the node — may be rolled back |
available |
Like local but does not guarantee causal consistency on shards |
majority |
Returns data acknowledged by a majority — won't be rolled back |
snapshot |
Consistent snapshot of data at a point in time (required for transactions) |
linearizable |
Most recent majority-committed data, waits for in-flight writes (single node) |
Performance
36. What is the MongoDB profiler?
The database profiler logs slow operations to system.profile:
// Enable: log operations slower than 100 ms
db.setProfilingLevel(1, { slowms: 100 })
// Enable: log all operations (heavy — for debugging only)
db.setProfilingLevel(2)
// Query the profile collection
db.system.profile.find().sort({ ts: -1 }).limit(5).pretty()
// Disable
db.setProfilingLevel(0)
Key fields: op, ns, millis, keysExamined, docsExamined, planSummary.
37. How do you force MongoDB to use a specific index?
db.users.find({ age: { $gt: 25 } }).hint({ age: 1 })
// Use natural order (no index — full collection scan)
db.users.find({ age: { $gt: 25 } }).hint({ $natural: 1 })
hint() overrides the query planner. Use it when the planner picks a suboptimal index, or when benchmarking.
38. What is the WiredTiger cache and why does it matter?
WiredTiger (default storage engine since v3.2) uses an in-memory cache:
- Default: 50% of available RAM - 1 GB (min 256 MB)
- Hot data (frequently accessed documents + index pages) stays in cache
- Reads from cache are orders of magnitude faster than disk reads
Tune with --wiredTigerCacheSizeGB or storage.wiredTiger.engineConfig.cacheSizeGB in mongod.conf. For dedicated MongoDB servers, set to ~60–70% of RAM.
Security
39. What authentication mechanisms does MongoDB support?
| Mechanism | Description |
|---|---|
| SCRAM-SHA-256 (default) | Challenge-response; recommended |
| SCRAM-SHA-1 | Legacy; avoid for new deployments |
| x.509 certificates | Client and member authentication |
| LDAP / Kerberos | Enterprise feature |
| AWS IAM | Atlas / Enterprise on AWS |
Always enable authentication (security.authorization: enabled in mongod.conf). By default MongoDB binds to localhost only — change bindIp carefully.
40. How does MongoDB RBAC work?
MongoDB uses Role-Based Access Control. A user is assigned roles; roles grant privileges on resources:
// Create a read-only user on one database
db.createUser({
user: "reportUser",
pwd: "securePassword",
roles: [{ role: "read", db: "analytics" }]
})
// Built-in roles:
// read, readWrite, dbAdmin, userAdmin, clusterAdmin, root
Custom roles:
db.createRole({
role: "reportRole",
privileges: [
{ resource: { db: "analytics", collection: "" }, actions: ["find"] }
],
roles: []
})
Advanced topics
41. What are Change Streams?
Change Streams allow applications to subscribe to real-time notifications of data changes:
const changeStream = db.orders.watch([
{ $match: { operationType: { $in: ["insert", "update"] } } }
])
changeStream.on("change", (change) => {
console.log("Changed:", change.fullDocument)
})
Built on the oplog — requires a replica set or sharded cluster. Use cases: event-driven architecture, cache invalidation, audit logs, real-time dashboards.
42. What is the difference between $match early vs late in a pipeline?
| Position | Effect |
|---|---|
Early $match (before $group, $lookup) |
Reduces input document count; can use indexes |
Late $match (after $group) |
Filters computed/grouped results; cannot use collection indexes |
Always place $match as early as possible to limit data flowing through subsequent stages.
// Good: index on status is used
db.orders.aggregate([
{ $match: { status: "shipped" } }, // ← early
{ $group: { _id: "$customerId", total: { $sum: "$amount" } } }
])
// Bad: full collection scanned before grouping
db.orders.aggregate([
{ $group: { _id: "$customerId", total: { $sum: "$amount" } } },
{ $match: { total: { $gt: 1000 } } } // ← this is fine (on computed field)
])
43. What are capped collections?
Capped collections are fixed-size, circular collections — oldest documents are overwritten when the size limit is reached:
db.createCollection("logs", { capped: true, size: 10485760, max: 10000 })
// size: bytes; max: maximum document count (both limits enforced)
- Guaranteed insertion order
- Very fast writes (no index on
_idby default) - Cannot delete or resize individual documents
- Ideal for: log rotation, audit trails, oplog-style ring buffers
44. What are Atlas Search and text indexes?
Text indexes provide basic full-text search:
db.articles.createIndex({ title: "text", body: "text" })
db.articles.find({ $text: { $search: "mongodb performance" } },
{ score: { $meta: "textScore" } })
.sort({ score: { $meta: "textScore" } })
Atlas Search (MongoDB Atlas) is a richer, Lucene-based full-text search:
- Fuzzy matching, autocomplete, synonyms, facets, highlighting
- Separate search nodes — does not impact primary cluster performance
- Defined with
$searchaggregation stage
45. How does the MongoDB aggregation pipeline handle memory limits?
By default a single aggregation pipeline stage cannot exceed 100 MB of RAM. For larger datasets:
db.bigCollection.aggregate(pipeline, { allowDiskUse: true })
allowDiskUse: true spills intermediate data to disk (slower but avoids memory errors).
In MongoDB 5.0+ the 100 MB limit was raised to 100 MB per stage with disk use allowed automatically in some contexts; Atlas has configurable limits.
46. What is the $facet stage?
$facet runs multiple sub-pipelines on the same input in a single pass — ideal for faceted search results:
db.products.aggregate([
{ $match: { category: "electronics" } },
{
$facet: {
byBrand: [{ $group: { _id: "$brand", count: { $sum: 1 } } }],
priceRanges: [{ $bucket: { groupBy: "$price", boundaries: [0, 100, 500, 1000], default: "1000+" } }],
totalCount: [{ $count: "total" }]
}
}
])
Returns a single document with one field per sub-pipeline.
47. How do you paginate efficiently in MongoDB?
Skip/limit (simple but slow for large offsets):
db.posts.find().sort({ _id: 1 }).skip(page * pageSize).limit(pageSize)
// Skip scans all preceding documents — O(n) cost
Keyset / cursor pagination (recommended):
// First page
const firstPage = db.posts.find().sort({ _id: 1 }).limit(10).toArray()
const lastId = firstPage[firstPage.length - 1]._id
// Next page — no skip needed
db.posts.find({ _id: { $gt: lastId } }).sort({ _id: 1 }).limit(10)
Keyset pagination is O(log n) and consistent regardless of offset depth.
48. What is MongoDB Atlas?
MongoDB Atlas is MongoDB's fully managed cloud service:
| Feature | Description |
|---|---|
| Managed clusters | Automated provisioning, backups, patching |
| Multi-cloud | AWS, GCP, Azure |
| Atlas Search | Lucene-based full-text search |
| Atlas Vector Search | Approximate nearest-neighbour for AI/ML |
| Data API / GraphQL | HTTP API without drivers |
| Charts | Built-in visualisation |
| Atlas App Services | Serverless functions, triggers, sync |
| Free tier | M0 — 512 MB storage forever |
49. What is the difference between MongoDB and Mongoose?
| MongoDB (driver) | Mongoose | |
|---|---|---|
| What it is | Official Node.js driver | ODM (Object-Document Mapper) for Node.js |
| Schema | None (schemaless) | Defines schemas, validation, type casting |
| Models | Raw collection access | Model classes with methods |
| Middleware | None | Pre/post hooks on save, find, remove |
| Overhead | Low | Slightly higher (schema validation, casting) |
| When to use | Microservices, low latency, flexible schema | Applications needing structure and validation |
50. What are common MongoDB anti-patterns?
| Anti-pattern | Problem | Solution |
|---|---|---|
| Unbounded arrays | Document grows past 16 MB; slow updates | Reference pattern or Bucket pattern |
| Bloated documents | Loading unneeded fields on every query | Projection; separate collections |
$where / eval |
Server-side JS execution; slow, insecure | Use native MQL operators |
| Index on every field | Write overhead; RAM pressure | Index only queried fields |
| No index on query fields | COLLSCAN on large collections | Add indexes; use explain() |
| Wrong shard key (monotonic) | Write hot spot on one shard | Hashed shard key |
| Multi-document txn for everything | Higher latency, coordination overhead | Embed related data; single-document atomicity |
| Ignoring write concern | Silent data loss on failure | Use w: "majority" in production |
Common mistakes
| Mistake | What goes wrong | Fix |
|---|---|---|
Using _id as ObjectId string vs ObjectId type |
Query { _id: "abc" } misses { _id: ObjectId("abc") } |
Always cast to correct BSON type |
| Querying array as scalar | { tags: ["a","b"] } matches exact array, not elements |
Use { tags: "a" } or $elemMatch |
Missing $ in update |
{ age: 31 } replaces document instead of setting field |
Use { $set: { age: 31 } } |
$match too late in pipeline |
Misses index; scans entire collection | Place $match first |
Forgetting new: true in findOneAndUpdate |
Returns old document | Pass { returnDocument: "after" } |
Case-sensitive regex without i flag |
Misses differently-cased values | /pattern/i or $regex with $options: "i" |
| Large in-memory sort without index | Hits 32 MB sort limit, aborts pipeline | Index sort fields |
deleteMany({}) without thinking |
Deletes every document | Always add a filter |
MongoDB vs other databases
| Feature | MongoDB | PostgreSQL | DynamoDB | Redis |
|---|---|---|---|---|
| Data model | Document | Relational | Key-Value/Document | Key-Value/Structures |
| Schema | Flexible | Strict | Flexible | None |
| ACID transactions | Yes (v4.0+) | Yes | Yes (limited) | Limited |
| Full-text search | Text index / Atlas Search | tsvector |
No (external) | RediSearch |
| Horizontal scaling | Sharding (native) | Citus / partitioning | Native | Redis Cluster |
| Best for | Flexible docs, hierarchical data | Complex relations, reporting | Massive scale key-value | Caching, sessions, pub/sub |
FAQ
Q: Is MongoDB suitable for financial applications?
A: Yes — with w: "majority" write concern, j: true journaling, and multi-document transactions. Use Decimal128 type for monetary values to avoid floating-point errors.
Q: How do I handle schema migrations in MongoDB?
A: Three strategies: (1) Lazy migration — update documents when they're next read/written; (2) Background migration script to update all documents in batches; (3) Schema versioning — add a schemaVersion field and handle multiple versions in application code.
Q: What is the $elemMatch operator?
A: Matches documents where an array field contains at least one element that satisfies all specified conditions — important when filtering on multiple fields of the same array element:
// Find products with a review where rating >= 4 AND author = "Alice"
db.products.find({ reviews: { $elemMatch: { rating: { $gte: 4 }, author: "Alice" } } })
Q: When should I use MongoDB time series collections?
A: When ingesting sensor data, metrics, or any time-stamped measurements. Time series collections (v5.0+) automatically use the Bucket pattern internally, providing better compression and faster range queries than regular collections.
Q: How does MongoDB handle null vs missing fields?
A: { field: null } matches documents where the field is null or where the field does not exist. To match only documents where the field explicitly exists, use { field: { $exists: true, $eq: null } }.
Q: What is the difference between find() cursor and aggregate() cursor?
A: Both return cursors, but find() can only filter and project — no computed fields, grouping, or joins. The aggregation pipeline is more powerful but slightly higher overhead. Prefer find() for simple queries; aggregate() for anything involving $group, $lookup, $unwind, or computed fields.