Menu
CoddyTech

Advanced MongoDB Interview Questions

Running MongoDB in production: slow queries, query plans, write and read concerns, change streams, connections, memory, security and safe concurrent updates.

12 questions
  1. How do you find and fix a slow query in MongoDB?

    Find it first: operations slower than slowms (100 ms by default) are written to the server log, the database profiler (db.setProfilingLevel(1)) stores them in system.profile, and Atlas shows them in its Query Profiler. Then run the query with explain('executionStats') and look for COLLSCAN, an in-memory SORT stage, or far more documents examined than returned. Fix it with an index in ESR order, a smaller projection or a schema change.

  2. How does MongoDB choose a query plan?

    When several indexes could serve a query, the planner runs the candidate plans in a short trial, picks the one that produces results with the least work, and caches it for that query shape. The cached plan is reevaluated if it starts doing much more work, and the cache is cleared when the collection's indexes change or the server restarts. hint() forces an index, but the right index is better than a hint.

  3. What is write concern?

    How many members must acknowledge a write before it counts as successful. w: 1 waits for the primary only, and the write can be rolled back if the primary fails before replicating it. w: 'majority' waits for a majority of voting members, so the write survives failover; it is the default since MongoDB 5.0. j: true also waits for the on-disk journal, and wtimeout limits the wait.

  4. What is read concern?

    Which data a read may return. local (the default) returns the member's newest data, which may later be rolled back. majority returns only data acknowledged by a majority, so it can never be rolled back. linearizable also guarantees the read reflects every majority write completed before it, and snapshot reads one point in time, as transactions do. With majority reads and writes in a causally consistent session, a client always reads its own writes.

  5. What is read preference, and when should you read from secondaries?

    It decides which member serves reads: primary (the default), primaryPreferred, secondary, secondaryPreferred or nearest (lowest latency). Secondaries replicate asynchronously, so their data can be stale: use them for analytics, reports or users far from the primary, not for reading what the user just wrote. maxStalenessSeconds skips secondaries that lag too far behind.

  6. What are change streams?

    A way to subscribe to inserts, updates, deletes and other changes on a collection, a database or a whole deployment, with watch(). They read the oplog, so they need a replica set or sharded cluster. Each event carries a resume token: store it and pass it as resumeAfter after a restart to continue without missing events, as long as the oplog still holds them.

    const stream = orders.watch([{ $match: { operationType: 'insert' } }]);
    for await (const change of stream) {
      await saveToken(change._id); // the resume token
      console.log(change.fullDocument.total);
    }
    
  7. How should a Node.js app manage MongoDB connections?

    Create one MongoClient per process at startup and reuse it everywhere: it holds a connection pool (maxPoolSize, 100 by default) and monitors the cluster. A client per request adds a TCP and TLS handshake and authentication to every call, and can exhaust the server's connection limit. In serverless functions, create the client outside the handler so warm invocations reuse it, and keep the connection string in an environment variable.

  8. How does MongoDB use memory, and what is the working set?

    The WiredTiger storage engine keeps its own cache, by default the larger of 50% of (RAM minus 1 GB) or 256 MB, and the rest of memory serves the operating system's file cache. The working set is the data and indexes your application touches regularly. If it fits in RAM, reads come from memory; if not, they go to disk and latency jumps. Watch cache eviction and keep indexes selective.

  9. Can a MongoDB query be injected through JSON input?

    Node.js
    const { MongoClient } = require('mongodb');
    const client = new MongoClient('mongodb://localhost:27017');
    
    async function main() {
      const users = client.db('app').collection('users');
      await users.insertOne({ user: 'admin', password: 's3cret' });
      // a login request body sent as JSON by an attacker
      const body = JSON.parse('{"user":"admin","password":{"$ne":null}}');
      const found = await users.findOne({ user: body.user, password: body.password });
      console.log(found ? `logged in as ${found.user}` : 'denied');
    }
    main().finally(() => client.close());

    It prints logged in as admin. The attacker sent an object, { "$ne": null }, where a string was expected, and it became a query operator: any password that is not null matches. Check input types (typeof password === 'string') or reject keys that start with $, and never store or compare raw passwords. The SQL version of this bug is in preventing SQL injection.

  10. How do you stop two users from overwriting each other's edits?

    Node.js
    const { MongoClient } = require('mongodb');
    const client = new MongoClient('mongodb://localhost:27017');
    
    async function main() {
      const docs = client.db('app').collection('docs');
      const { insertedId } = await docs.insertOne({ text: 'v1', version: 1 });
      const a = await docs.updateOne(
        { _id: insertedId, version: 1 },
        { $set: { text: 'from A' }, $inc: { version: 1 } }
      );
      const b = await docs.updateOne(
        { _id: insertedId, version: 1 },
        { $set: { text: 'from B' }, $inc: { version: 1 } }
      );
      const doc = await docs.findOne({ _id: insertedId });
      console.log(a.modifiedCount, b.modifiedCount, doc.text, doc.version);
    }
    main().finally(() => client.close());

    It prints 1 0 from A 2. This is optimistic concurrency: each update puts the version it read in the filter and increments it. The first update matches and moves the version to 2, so the second, still expecting version 1, matches nothing, and the app can reload and retry instead of silently overwriting. Mongoose's optimisticConcurrency option does the same with __v.

  11. How do you back up a MongoDB deployment?

    mongodump and mongorestore export BSON and suit small databases; on a replica set, --oplog makes the dump a consistent point in time. For large deployments, use filesystem snapshots (with the journal on the same volume) or a managed backup such as Atlas cloud backups, which add point-in-time restore from the oplog. Restore regularly to test: an untested backup is a hope, not a plan.

  12. How do you change the schema of a large collection without downtime?

    In steps that the running code tolerates. Add a schemaVersion field, deploy code that reads both the old and new shapes and writes the new one, then migrate old documents in small throttled batches in the background (ranges of _id). When none are left, remove the old read path. For rarely touched data, a lazy migration also works: rewrite each document the next time it is read or written.

Coddy programming languages illustration

Learn to code with Coddy

Get started