0 / 15 read
All topics

MongoDB

MongoDB queries, aggregation, indexing, schema design, and Mongoose ODM

15 questions0 read9 interview prep

Questions6 shown

MongoDB is a NoSQL document database that stores data in flexible, JSON-like documents (called BSON) instead of rows and columns.

MongoDB vs SQL — A simple comparison:

| SQL (MySQL, PostgreSQL) | MongoDB |

|------------------------|---------|

| Tables | Collections |

| Rows | Documents |

| Columns | Fields |

| Fixed schema | Flexible schema |

| JOIN to combine tables | Embed or Reference |

| SQL query language | MQL (MongoDB Query Language) |

A MongoDB document looks like this:

Code
{
  "_id": "ObjectId('...')",
  "name": "Ravish",
  "email": "ravish@example.com",
  "skills": ["JavaScript", "React", "Node.js"],
  "address": {
    "city": "Delhi",
    "country": "India"
  }
}

Notice how you can have arrays and nested objects right inside the document — no need for separate tables and JOINs.

When to choose MongoDB:

Your data structure changes frequently

You need to store varied, complex data

You want fast development iterations

Horizontal scaling is important

When SQL might be better:

Your data has strict relationships (banking, accounting)

You need complex transactions across multiple tables

Data integrity is the top priority

Mongoose is an ODM (Object Data Modeling) library for MongoDB and Node.js. It adds structure and convenience on top of the raw MongoDB driver.

Think of it as a helpful layer between your code and MongoDB:

Without Mongoose (raw driver):

Code
await db.collection('users').insertOne({
  name: 123, // Oops, name should be a string!
  email: 'not-an-email' // No validation
});

With Mongoose:

Code
const userSchema = new Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, match: /.+@.+/ },
  age: { type: Number, min: 13 }
});

const User = mongoose.model('User', userSchema);

await User.create({ name: 123 }); // Error! Validation fails

What Mongoose gives you:

| Feature | Benefit |

|---------|--------|

| Schemas | Define the shape of your data |

| Validation | Auto-validate before saving |

| Type casting | Converts strings to numbers, etc. |

| Middleware/Hooks | Run code before/after save, update, delete |

| Population | Easily load related documents (like SQL JOINs) |

| Query helpers | Chainable, readable queries |

| Virtuals | Computed properties that don't get saved |

Mongoose makes working with MongoDB feel structured and safe while keeping all the flexibility of NoSQL.

Here's a complete CRUD reference using Mongoose:

CREATE:

Code
// Method 1: Create and save
const user = new User({ name: 'Ravish', email: 'r@test.com' });
await user.save();

// Method 2: Direct create
const user = await User.create({ name: 'Ravish', email: 'r@test.com' });

// Create many
await User.insertMany([{ name: 'A' }, { name: 'B' }]);

READ:

Code
// Find all
const users = await User.find();

// Find with filter
const devs = await User.find({ role: 'developer' });

// Find one
const user = await User.findOne({ email: 'r@test.com' });

// Find by ID
const user = await User.findById('64a...');

// Select specific fields
const names = await User.find().select('name email');

// Sort, limit, skip (pagination)
const page = await User.find().sort({ createdAt: -1 }).skip(20).limit(10);

UPDATE:

Code
// Update one
await User.updateOne({ _id: id }, { $set: { name: 'New Name' } });

// Find, update, return new doc
const updated = await User.findByIdAndUpdate(
  id,
  { name: 'New Name' },
  { new: true, runValidators: true }
);

DELETE:

Code
await User.deleteOne({ _id: id });
const deleted = await User.findByIdAndDelete(id);
await User.deleteMany({ status: 'inactive' });

Pro tip: Always use { new: true } with findByIdAndUpdate to get the updated document back.

Indexes are like the index at the back of a book — instead of reading every page to find a topic, you look up the index and jump directly to the right page.

Without indexes, MongoDB does a collection scan — checking every single document. With indexes, it jumps straight to the matches.

Creating indexes with Mongoose:

Code
const userSchema = new Schema({
  email: { type: String, unique: true, index: true },
  username: { type: String, index: true },
  age: Number,
  city: String
});

// Compound index (for queries that filter by both)
userSchema.index({ city: 1, age: -1 });

// Text index (for search)
userSchema.index({ name: 'text', bio: 'text' });

Performance difference:

Code
Without index: Scan 1,000,000 documents  500ms
With index:    Lookup index  2ms

Types of indexes:

| Type | Use case |

|------|----------|

| Single field | { email: 1 } — Most common |

| Compound | { city: 1, age: -1 } — Multi-field queries |

| Unique | Prevents duplicate values |

| Text | Full-text search |

| TTL | Auto-delete after time (sessions, logs) |

When to index:

Fields you frequently search/filter by

Fields used in .sort()

Fields used in .populate() references

When NOT to index:

Fields rarely queried

Collections with very few documents

Too many indexes slow down writes (each insert must update all indexes)

This is one of the most important decisions in MongoDB schema design.

Embedding — Put related data inside the same document:

Code
const orderSchema = new Schema({
  customer: String,
  items: [
    {
      name: String,
      price: Number,
      quantity: Number
    }
  ]
});

Referencing — Store a reference (ID) and look it up later:

Code
const orderSchema = new Schema({
  customer: { type: Schema.Types.ObjectId, ref: 'User' },
  products: [{ type: Schema.Types.ObjectId, ref: 'Product' }]
});

// To get the full data:
const order = await Order.findById(id).populate('customer products');

When to EMBED:

Data is always accessed together (user + address)

One-to-few relationships (a blog post + its comments)

Data doesn't change independently

You want fast reads (single query)

When to REFERENCE:

Data is accessed independently

One-to-many or many-to-many (users + orders)

Data is large or grows unboundedly

Data changes frequently and independently

Quick decision guide:

Code
Accessed together?  Embed
Grows without limit?  Reference
Needs to be updated independently?  Reference
Small and rarely changes?  Embed

The 16MB document limit in MongoDB also matters — if embedded data could grow very large, reference it instead.

MongoDB has powerful query operators that start with `$`. Here are the ones you'll use most: **Comparison:** [code block] **Logical:** [code block] **Array:** [code block] **Update operators:** [c...

Sign in to continue reading

Create a free account to unlock login-level content, or go premium for everything.

Interview Preparation

Premium

High-signal questions that come up most in real interviews. These are the ones worth spending extra time on.

The **Aggregation Pipeline** is MongoDB's way of processing and transforming data — like a conveyor belt where each stage transforms the documents. [code block] **Common stages:** | Stage | What it...

Sign in to continue reading

Create a free account to unlock login-level content, or go premium for everything.

Mongoose **middleware** (also called hooks) lets you run code **before or after** certain operations. They're like interceptors for your database operations. **Pre hooks** — Run BEFORE the operation:...

Sign in to continue reading

Create a free account to unlock login-level content, or go premium for everything.

**Population** is Mongoose's way of replacing an ObjectId reference with the actual document from another collection. It's the MongoDB equivalent of a SQL JOIN — but it happens at the application leve...

Sign in to continue reading

Create a free account to unlock login-level content, or go premium for everything.

Mongoose has a powerful built-in validation system that runs automatically before saving to the database. **Built-in validators:** [code block] **Custom validators:** [code block] **Handling valida...

Sign in to continue reading

Create a free account to unlock login-level content, or go premium for everything.

**Transactions** let you group multiple operations together — either they ALL succeed, or NONE of them do. This is essential for operations that must be atomic. **Example: Transferring money between ...

Sign in to continue reading

Create a free account to unlock login-level content, or go premium for everything.

**Virtuals** are properties that are computed on the fly — they don't get stored in the database. [code block] **Instance methods** — Add custom functions to documents: [code block] **Static method...

Sign in to continue reading

Create a free account to unlock login-level content, or go premium for everything.

This is one of the most practical questions you'll face in interviews and real projects. **Choose MongoDB when:** - Your data structure is **flexible or evolves** frequently - You're building a **pro...

Sign in to continue reading

Create a free account to unlock login-level content, or go premium for everything.

There are two main approaches to pagination: **1. Skip-Limit (Offset-based) — Simple but slower for large datasets:** [code block] **2. Cursor-based — Better for large datasets and infinite scroll:*...

Sign in to continue reading

Create a free account to unlock login-level content, or go premium for everything.

Good schema design is the foundation of a performant MongoDB application. **1. Design based on access patterns, not relationships:** [code block] **2. Embed data you read together:** [code block] *...

Sign in to continue reading

Create a free account to unlock login-level content, or go premium for everything.