HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
70 Topics
Chapter•360 Minutes

70 Progressive Questions (Easy to Advanced + Scenarios)

70 comprehensive interview questions spanning database basics, Mongoose ODM abstractions, advanced aggregation pipelines, system architecture, performance tuning, and real-world scenario challenges.

Q51 [Scenario]: High concurrent orders cause item stock counts to drop below zero. How do you prevent inventory race conditions?

Avoid checking inventory client-side (if (item.stock > 0) item.save()). Instead, use an atomic update with a query filter condition: Item.updateOne({ _id: itemId, stock: { $gte: quantity } }, { $inc: { stock: -quantity } }). If modifiedCount === 0, inventory was insufficient, preventing negative stock levels atomically.

Atomic Inventory Reduction
const result = await Item.updateOne(
  { _id: itemId, stock: { $gte: orderQty } }, // Condition filter
  {$inc: { stock: -orderQty } } // Atomic reduction
);

if (result.modifiedCount === 0) {
  throw new Error('Insufficient inventory or item out of stock');
}
💡 Decrements stock atomically on the database server strictly if stock is greater than or equal to order quantity.
Key Takeaways
  • Combine range filters directly inside atomic update queries.
  • Eliminates client-side check-then-set race conditions.
Knowledge Check

Why does client-side stock checking (if stock > 0) fail under high concurrent write loads?

Q52 [Scenario]: An API endpoint fetching user activity logs takes 8 seconds. Walk through your step-by-step debugging workflow.

  1. Enable Database Profiler: db.setProfilingLevel(1, { slowms: 100 }) to log slow operations to system.profile.
  2. Extract Explain Plan: Run .explain('executionStats') on the offending query.
  3. Inspect Stages: Check if winningPlan shows COLLSCAN. Look at totalDocsExamined vs nReturned.
  4. Apply Indexing: Construct a compound index adhering to the ESR rule (Equality, Sort, Range).
  5. Verify: Re-run explain to verify IXSCAN stage execution.
Key Takeaways
  • Identify slow queries via system profiler or MongoDB Atlas Performance Advisor.
  • Apply ESR rule indexing to convert COLLSCAN into IXSCAN.
Knowledge Check

Which system collection stores slow operation entries when database profiling is enabled?

Q53 [Scenario]: Design a data schema for a Twitter-like follower system where users can have millions of followers.

Do NOT embed followers inside an array in the User document (followers: [ObjectId]), as it will breach the 16MB limit and cause massive document relocation. Instead, use a Bucket Pattern or a Separate Outlier Collection (Follow collection with indexed { followerId, followeeId }), or bucket 1000 follower references per document.

Key Takeaways
  • Avoid embedding unbounded arrays in single documents.
  • Use normalized target collections or the Bucket Pattern for massive 1-to-N relationships.
Knowledge Check

What issue arises if a celebrity user's 10 million followers are stored as ObjectIds in a single array field?

Q54 [Scenario]: A user registration endpoint throws MongoServerError E11000 duplicate key error. How should your application handle this?

Catch the E11000 error code inside Express error-handling middleware. Parse the error.keyPattern or error.keyValue object to identify which field caused the collision (e.g., email), and return a user-friendly HTTP 409 Conflict status response ({ message: 'Email address already registered' }).

Key Takeaways
  • E11000 is the MongoDB duplicate key error code.
  • Parse err.keyValue to return targeted validation messages.
Knowledge Check

What standard MongoDB error code signifies a unique index duplicate key constraint violation?

Q55 [Scenario]: How do you implement global Soft Deletes in Mongoose so deleted documents are preserved for compliance?

Add an isDeleted: { type: Boolean, default: false } and deletedAt: Date field to the schema. Implement a Mongoose Query Pre-Middleware hook targeting find, findOne, countDocuments, and update regex patterns (/^find/) to append this.where({ isDeleted: false }) automatically.

Global Soft Delete Query Middleware
schema.pre(/^find/, function(next) {
  // Automatically filter out soft-deleted documents from all find queries
  this.where({ isDeleted: { $ne: true } });
  next();
});
💡 Hooks into all find-variant queries to append soft-delete filtering globally.
Key Takeaways
  • Use query middleware regex (/^find/) to append soft-delete filters globally.
  • Ensure compound indexes include { isDeleted: 1 }.
Knowledge Check

Which Mongoose middleware pattern allows applying soft-delete filters across find, findOne, and findById queries simultaneously?

Q56 [Scenario]: You need to rename a field across 50 million documents in production without downtime. How do you execute this?

Do NOT execute a blocking $rename update on 50M documents at once (locks memory and replication).

  1. Update application code to handle both old and new field names via Mongoose virtuals or getters.
  2. Write new data using the new field name.
  3. Run a background script processing documents in small batched cursor chunks using $set and $unset during off-peak hours.
Key Takeaways
  • Avoid single monolithic updates on massive production collections.
  • Execute chunked background updates using cursor pagination.
Knowledge Check

Why is running db.collection.updateMany({}, { $rename: ... }) dangerous on a 50M document production table?

Q57 [Scenario]: An analytics dashboard calculates order metrics continuously, degrading database performance. How do you optimize this?

Apply the Pre-aggregation Pattern. Instead of running heavy $group pipeline calculations on raw transaction logs per dashboard load, maintain a separate DailyMetrics collection. Update summary counters incrementally in real time using atomic $inc updates during checkout events.

Key Takeaways
  • Use the Pre-aggregation Pattern to store pre-calculated metrics.
  • Update summary metrics incrementally using atomic $inc operations.
Knowledge Check

Which design pattern replaces expensive on-the-fly aggregation queries with incremental pre-calculated metric documents?

Q58 [Scenario]: Audit logs must be retained for exactly 90 days and deleted automatically. How do you implement this efficiently?

Create a TTL index on the createdAt timestamp field with expireAfterSeconds: 7776000 (90 days). MongoDB's background thread will purge expired documents automatically without requiring cron jobs or application intervention.

Key Takeaways
  • TTL indexes eliminate external cleanup cron scripts.
  • Calculated in seconds ($90 ext{ days} imes 24 imes 3600 = 7,776,000 ext{ seconds}$).
Knowledge Check

How many seconds must be configured in expireAfterSeconds for a 90-day TTL index?

Q59 [Scenario]: Design a multi-tenant SaaS architecture in MongoDB. Should you use single collections, separate databases, or separate collections?

  • Shared Collection (Discriminator/Tenant ID): Best for scale and operational simplicity. Filter all queries by tenantId (add tenantId as leading key in compound indexes).
  • Separate Databases per Tenant: Best for strict compliance, data isolation, and easy backup/restore per customer.
  • Separate Collections: Anti-pattern if tenant counts reach thousands (exceeds namespace limits).
Key Takeaways
  • Shared Collection + tenantId index prefix = Maximum scalability.
  • Separate DBs = Maximum isolation compliance.
Knowledge Check

In a shared collection multi-tenant architecture, where should the tenantId field reside in compound indexes?

Q60 [Scenario]: Fetching 500,000 documents via Model.find() causes Node.js Out-Of-Memory (OOM) crashes. How do you resolve this?

Model.find() loads all 500,000 documents into application RAM simultaneously as array objects. Resolve this by streaming documents using Mongoose Cursor Stream (Model.find().cursor()) or batching with .eachAsync() or cursor .next(), processing documents sequentially with low memory overhead.

Streaming Documents using Mongoose Cursor
const cursor = User.find({ status: 'active' }).cursor();

for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {
  await processDocument(doc); // Process one document at a time in memory
}
💡 Streams documents sequentially from the database, preventing Node.js RAM exhaustion.
Key Takeaways
  • Never load massive datasets into application RAM arrays.
  • Use streams (.cursor()) to process documents in small memory chunks.
Knowledge Check

Which method returns a Node.js stream for processing large query datasets sequentially without flooding application RAM?