Mongoose
JavaScript
static methods
instance methods
programming

Mongoose 'static' methods vs. 'instance' methods

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In Mongoose, both static and instance methods let you attach domain logic to a schema, but they solve different problems. Static methods operate at model scope, while instance methods operate on one document. Choosing the right method type keeps your data layer predictable and prevents awkward code that mixes query logic with record mutation.

Static Methods: Model-Level Operations

A static method belongs to the model, so you call it as User.someMethod(). Use statics for queries, aggregations, and factory-style operations that do not require a preloaded document.

javascript
1const mongoose = require("mongoose");
2
3const userSchema = new mongoose.Schema({
4  email: { type: String, required: true, unique: true },
5  name: { type: String, required: true },
6  active: { type: Boolean, default: true },
7  lastLoginAt: { type: Date, default: null }
8});
9
10userSchema.statics.findByEmail = function (email) {
11  return this.findOne({ email: email.toLowerCase().trim() });
12};
13
14userSchema.statics.activeUsers = function () {
15  return this.find({ active: true }).sort({ name: 1 });
16};
17
18const User = mongoose.model("User", userSchema);
19
20async function run() {
21  await mongoose.connect("mongodb://127.0.0.1:27017/demo");
22  const user = await User.findByEmail("[email protected]");
23  console.log(user);
24  await mongoose.disconnect();
25}
26
27run().catch(console.error);

This keeps query rules close to the model and avoids repeated filter snippets across services.

Instance Methods: Document-Level Behavior

An instance method runs on a document already loaded from the database. Use instance methods when logic depends on current document state, especially for updates and computed checks.

javascript
1const mongoose = require("mongoose");
2
3const userSchema = new mongoose.Schema({
4  email: String,
5  name: String,
6  active: { type: Boolean, default: true },
7  failedLogins: { type: Number, default: 0 }
8});
9
10userSchema.methods.markLoginFailure = async function () {
11  this.failedLogins += 1;
12  if (this.failedLogins >= 5) {
13    this.active = false;
14  }
15  return this.save();
16};
17
18userSchema.methods.displayName = function () {
19  return `${this.name} <${this.email}>`;
20};
21
22const User = mongoose.model("User2", userSchema);
23
24async function run() {
25  await mongoose.connect("mongodb://127.0.0.1:27017/demo");
26  const user = await User.findOne();
27  if (user) {
28    await user.markLoginFailure();
29    console.log(user.displayName());
30  }
31  await mongoose.disconnect();
32}
33
34run().catch(console.error);

A good rule is simple: if you need this document fields, use an instance method.

Choosing the Right Method Type

Use statics when input is external and you need a document or list of documents. Use instance methods when input is mostly the current document and action mutates or inspects it.

Typical static use cases:

  • Lookup by business key.
  • Collection-level reports.
  • Paginated list queries.

Typical instance use cases:

  • State transitions such as activate or deactivate.
  • Field normalization before save.
  • Behavior that naturally belongs to a single record.

Keeping this separation clear makes code review easier because call sites communicate intent.

Sessions and Transactions

Both method types can support transactions. Pass a session to static queries and instance saves to keep operations atomic.

javascript
1async function transferFlag(User, fromId, toId, session) {
2  const fromUser = await User.findById(fromId).session(session);
3  const toUser = await User.findById(toId).session(session);
4
5  if (!fromUser || !toUser) {
6    throw new Error("user not found");
7  }
8
9  fromUser.active = false;
10  toUser.active = true;
11
12  await fromUser.save({ session });
13  await toUser.save({ session });
14}

When method design is clean, adding session support later is straightforward.

Testing Strategy

Test statics with seeded collections and assert query behavior. Test instance methods with concrete documents and assert state changes after save.

javascript
1// Example assertion pattern
2const user = await User.create({ email: "[email protected]", name: "Ava", failedLogins: 4 });
3await user.markLoginFailure();
4const reloaded = await User.findById(user._id);
5console.log(reloaded.active); // false

Avoid testing only return values for instance methods. Always verify persisted state.

Common Pitfalls

  • Putting query-heavy logic in instance methods, which forces unnecessary document loading.
  • Using arrow functions for methods that rely on this, which breaks context.
  • Duplicating identical filters in controllers instead of centralizing them in statics.
  • Saving documents inside statics without clear transaction boundaries.
  • Mixing validation, side effects, and persistence in one oversized method.

Summary

  • Static methods are model-level and best for collection queries and lookups.
  • Instance methods are document-level and best for record-specific behavior.
  • A clear split between static and instance responsibilities improves maintainability.
  • Both method types can participate in sessions and transactions.
  • Test statics for query correctness and instance methods for persisted state changes.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.