You're sitting across from a senior engineer, and they ask: How do indexes actually work in MongoDB? A moment of silence, and you're already behind. We've collected the MongoDB interview questions that come up repeatedly in production environments, paired with answers that show you understand not just the what, but the why.
How do indexes work? — B-tree indexes, compound order and the ESR rule
Indexes in MongoDB use B-tree structures, just like relational databases. When you create an index on a field, MongoDB builds a balanced tree that allows logarithmic lookups instead of full collection scans. The interviewer wants to know you understand this is not free—indexes consume memory, slow down writes, and must be maintained.
For a single-field index on a user collection:
db.users.createIndex({ email: 1 });MongoDB will now find a user by email in O(log n) time instead of scanning every document. But compound indexes—indexes on multiple fields—are where your answer separates you from mid-level candidates.
The order of fields in a compound index matters enormously. Consider this scenario: you query users by status, then by created date, and you filter out documents updated after a certain time.
db.users.find({
status: "active",
createdAt: { $gte: new Date("2024-01-01") },
updatedAt: { $lt: new Date("2024-12-31") }
});A naive engineer might create:
db.users.createIndex({ status: 1, createdAt: 1, updatedAt: 1 });The right answer uses the ESR rule: Equality, Sort, Range. Fields used in equality conditions come first, then sort fields, then range filters:
db.users.createIndex({ status: 1, createdAt: 1, updatedAt: 1 });In this case, status is equality, neither createdAt nor updatedAt is a sort (the query just filters), so ESR becomes equality, then range. Both fields are ranges, so either order works, but createdAt typically has higher selectivity. The ESR rule ensures MongoDB can use the index to its fullest: narrow down candidates as much as possible before applying expensive range comparisons.
What is a replica set? — Primary, secondaries, elections and write concerns
A replica set is MongoDB's answer to high availability. One node is primary (accepts reads and writes), others are secondaries (replicate from primary, serve reads if configured). If the primary dies, an election happens within seconds, and a new primary takes over. This is table stakes for production.
The follow-up question almost always comes: What about write concern? Write concern controls how many nodes must acknowledge a write before the client gets confirmation.
db.orders.insertOne(
{ customerId: 123, total: 99.99 },
{ writeConcern: { w: 1 } }
);w: 1 means the write is acknowledged by the primary only. Fast, but risky—if the primary crashes before replicating to secondaries, that write is lost. For financial transactions, you'd use:
db.orders.insertOne(
{ customerId: 123, total: 99.99 },
{ writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
);w: "majority" waits for the primary plus a majority of secondaries to acknowledge. j: true means the write must be journaled (durable on disk). wtimeout prevents hanging forever if nodes are down. This is slower but safe for critical data.
Elections happen automatically if the primary fails. A secondary that sees the primary is gone triggers an election; secondaries vote, and the one with the highest priority and most recent oplog entry becomes primary. You can influence this with priority settings in your replica set config:
rs.reconfig({
_id: "mySet",
members: [
{ _id: 0, host: "primary:27017", priority: 10 },
{ _id: 1, host: "secondary1:27017", priority: 5 },
{ _id: 2, host: "secondary2:27017", priority: 1 }
]
});Higher priority nodes are preferred in elections. A junior dev might think elections are unpredictable; you know they're deterministic and tunable.
When to use transactions? — Multi-document consistency when modeling cannot avoid it
This is where many interviews separate real production experience from theory. Transactions in MongoDB (added in version 4.0) allow you to update multiple documents atomically, but they come with real costs: latency, memory, and orchestration complexity.
The key insight: first, fix your data model to avoid transactions. A transaction is a band-aid for poor schema design.
Example: you're building an e-commerce system. A customer buys an item. You need to:
- Deduct from inventory
- Create an order
- Update the customer's purchase history
A junior engineer writes a transaction. A senior engineer redesigns: embed the inventory snapshot inside the order document itself. Now you're not modifying two separate documents; you're only writing the order. Consistency is guaranteed by your model, not by transactional overhead.
But sometimes transactions are unavoidable. If you're operating two truly independent systems (say, deducting from a payments system and reserving a room in a booking system), and both require atomicity across multiple documents, use a transaction:
const session = db.getMongo().startSession();
try {
session.startTransaction();
db.payments.updateOne(
{ accountId: "cust123" },
{ $inc: { balance: -100 } },
{ session }
);
db.reservations.insertOne(
{ roomId: "101", customerId: "cust123", paid: true },
{ session }
);
session.commitTransaction();
} catch (error) {
session.abortTransaction();
throw error;
} finally {
session.endSession();
}
Transactions lock resources, so keep them short. Don't do network calls or external API calls inside a transaction. The interviewer wants to hear: transactions are the last resort, not the first choice.
How do you optimize a slow query? — explain(), indexes, projections and schema changes
A query is slow. You have five minutes to diagnose. The methodology matters more than the answer.
Start with explain():
db.orders.find({ customerId: "c123", status: "pending" }).explain("executionStats");Look at the output: executionStats.executionStages.stage tells you the query plan. COLLSCAN means a full collection scan—bad. IXSCAN means it used an index—good. totalDocsExamined vs. nReturned` should be close; if you examined 100,000 docs but returned 10, your index is wrong or you're missing a filter.
The first fix is always indexing. Add an index matching your query pattern:
db.orders.createIndex({ customerId: 1, status: 1 });Run explain() again. If stage is now IXSCAN and totalDocsExamined ≈ nReturned, you're done.
The second optimization is projection. Only fetch the fields you need:
db.orders.find(
{ customerId: "c123", status: "pending" },
{ _id: 1, total: 1, createdAt: 1 }
).explain("executionStats");This reduces network bandwidth and CPU. If you're building a UI list view and only need three fields, don't fetch thirty.
The third, rare but powerful fix: schema redesign. If you're querying across an embedded array frequently, consider denormalization:
// Before: slow because it scans the array
db.orders.find({ "items.sku": "SKU-456" });
// After: embed a denormalized skus array at root for fast querying
db.orders.find({ skus: "SKU-456" });
Trade-off: larger documents, but faster queries. Production systems often make this trade.
Common mistakes
- Confusing index order with query order. The order of fields in a compound index determines efficiency, not the order you write them in your query. Use ESR.
- Using transactions to replace modeling. If your schema requires transactions for every write, your schema is broken. Fix the model first.
- Ignoring write concern on critical data. Defaulting to
w: 1for financial or inventory operations is a production incident waiting to happen. Setw: "majority"andj: true. - Not running explain() before optimizing. Guessing at indexes wastes time. Use explain() to see what MongoDB actually does.
- Creating too many indexes. Each index slows writes. Index only your hot queries; measure write performance impact.
Key takeaways
- B-tree indexes enable logarithmic lookups; compound index order and the ESR rule (Equality, Sort, Range) determine real-world performance.
- Replica sets provide high availability through primary-secondary replication and automatic elections; write concern tuning (w, j, wtimeout) balances speed and durability.
- Transactions are powerful but expensive; exhaust data modeling options before reaching for multi-document ACID.
- Optimize slow queries methodically: run explain(), add indexes, project only needed fields, and redesign the schema if necessary.
- Interview success comes from showing you've run into these problems in production and learned from them, not from memorizing definitions.






Comments
Loading comments…