Menu
CoddyTech

Mongoose Interview Questions

Mongoose, the Node.js ODM for MongoDB: schemas, models, validation, middleware, populate, lean and the traps around them.

12 questions
  1. What is Mongoose, and why use it instead of the MongoDB driver?

    Mongoose is an ODM (object document mapper) for Node.js, built on the official MongoDB driver. It adds schemas with types, defaults and validation, model classes with your own methods, middleware hooks, and populate for references. Use it when you want one enforced document shape in the application; use the driver directly for full control, less overhead or very dynamic data.

  2. What is the difference between a schema, a model and a document in Mongoose?

    A schema describes the documents: fields, types, defaults, validators and indexes. A model is a class compiled from a schema and bound to a collection (User uses users); its static methods query that collection. A document is one instance of a model, with methods such as save(). Note that unique: true is not a validator: it only builds a unique index.

    const userSchema = new mongoose.Schema({
      email: { type: String, required: true, unique: true, lowercase: true },
      age: { type: Number, min: 13 },
    }, { timestamps: true }); // adds createdAt and updatedAt
    
    const User = mongoose.model('User', userSchema);
    const user = await User.create({ email: 'Asha@Example.com', age: 21 });
    console.log(user.email); // asha@example.com
    
  3. How does validation work in Mongoose?

    Validators run before save() and create(): built-in ones such as required, min, max, enum, match and minLength, plus custom validate functions, which can be async. A failure rejects with a ValidationError that lists every invalid path, and nothing is written. Mongoose casts values first, so '21' becomes 21 for a Number field, and a value it cannot cast is reported as a cast error.

  4. Do Mongoose validators run on updateOne and findOneAndUpdate?

    Not by default. Validators run on save(), while query updates go straight to MongoDB, so User.updateOne({ _id: id }, { age: 5 }) stores an invalid age. Pass { runValidators: true } (or set it globally) to validate the updated paths. Even then only the fields in the update are checked, and inside a custom validator this is the query, not the document.

  5. What are Mongoose middleware hooks, and what are the common traps?

    Functions that run before (pre) or after (post) an operation. Document middleware (save, validate) gets the document as this; query middleware (find, updateOne, findOneAndUpdate) gets the query. Traps: an arrow function has no this, and findOneAndUpdate does not run save hooks, so password hashing in pre('save') is skipped on that path.

    userSchema.pre('save', async function () {
      if (!this.isModified('password')) return;
      this.password = await bcrypt.hash(this.password, 10);
    });
    
  6. How does populate work, and how is it different from $lookup?

    A field stores an ObjectId with ref: 'User', and populate('author') runs a second query for the referenced documents and puts them in place of the ids, in your application. $lookup joins on the server in one aggregation. populate is convenient, but it costs an extra query per populated path, and nested populates multiply them; for heavy reports, use $lookup or embed the fields you need.

  7. What does lean() do in Mongoose?

    It makes a query return plain JavaScript objects instead of Mongoose documents: no change tracking, getters, setters, virtuals, defaults or save(). That uses much less memory and CPU, so use it for read-only endpoints that just send JSON. Skip it when you will modify the result and save it.

    const users = await User.find({ active: true }).select('name email').lean();
    
  8. What are virtuals in Mongoose?

    Properties computed from other fields, defined on the schema and not stored in MongoDB, such as fullName built from first and last names. Because they are not in the database, you cannot query or sort by them, and toJSON() leaves them out unless you set toJSON: { virtuals: true }.

    userSchema.virtual('fullName').get(function () {
      return `${this.first} ${this.last}`;
    });
    
  9. What is the difference between instance methods and statics in Mongoose?

    Instance methods (schema.methods.name) are called on one document and use this as that document, such as user.checkPassword(pw). Statics (schema.statics.name) are called on the model and usually run queries, such as User.findByEmail(email). Define both before mongoose.model() compiles the schema, and with regular functions, not arrow functions.

  10. Is a Mongoose query a promise?

    No, it is a thenable: await User.find() works because a query has a then() method, and the query runs when awaited. .exec() returns a real promise and gives better stack traces. A query object runs only once: Mongoose 6 and later throw Query was already executed if you await the same object twice. Mongoose 7 removed callback support, so use await.

  11. Should Mongoose build indexes automatically in production?

    Usually not. With autoIndex on (the default), each app start calls createIndex for every index in the schemas, so adding an index to a schema starts a build on a large collection under production load. Mongoose's documentation recommends turning it off in production and building indexes in a deployment step, for example with Model.createIndexes().

  12. What are discriminators in Mongoose?

    Schema inheritance in one collection: a base model (Event) and child models (ClickEvent, PurchaseEvent) that add their own fields. Mongoose stores the type in a __t field and filters on it, so ClickEvent.find() returns only clicks while Event.find() returns every event. Use them when the types share most fields and are queried together.

Coddy programming languages illustration

Learn to code with Coddy

Get started