Indexes

MongoDB Fundamentals — Indexes
MongoDB Fundamentals
Chapter 7 · Indexes

⚡ Indexes

Every chapter so far covered how to model and store data. This chapter covers how to make querying it fast — and the tool that shows you whether a query is actually using an index at all.

Why Indexes Exist: The Collection Scan Problem

Without an index, a query has to check every single document in a collection one by one to find matches — a collection scan. That's fine on a few hundred documents, and disastrous on a few million. An index is a separate, sorted data structure that lets MongoDB jump straight to matching documents instead of examining everything.

Creating a Single-Field Index

Indexing a Field

db.products.createIndex({ category: 1 }) // 1 = ascending, -1 = descending

Compound Indexes

A compound index spans more than one field at once — useful when queries regularly filter and sort on multiple fields together:

An Index Across Two Fields

db.products.createIndex({ category: 1, price: -1 }) // speeds up queries filtering by category AND sorting by price descending

Field Order Matters: Equality, Sort, Range

The order fields are listed in a compound index isn't arbitrary — a widely used heuristic is Equality, Sort, Range (ESR): put fields queried with an exact match first, fields used for sorting next, and fields queried with a range ($gt/$lt) last. A compound index built in the wrong order for how it's actually queried may not get used as efficiently as one built to match the query's real shape.

explain(): Seeing What the Database Actually Did

Appending .explain("executionStats") to a query reveals whether MongoDB used an index (IXSCAN) or fell back to scanning the whole collection (COLLSCAN), and how many documents were actually examined versus returned:

COLLSCAN vs. IXSCAN

// No index on category — full collection scan db.products.find({ category: "books" }).explain("executionStats") // stage: "COLLSCAN", totalDocsExamined: 100000, nReturned: 250 // With an index on category — direct lookup db.products.find({ category: "books" }).explain("executionStats") // stage: "IXSCAN", totalDocsExamined: 250, nReturned: 250

The second run examined exactly as many documents as it returned — the index let MongoDB go straight to the matches instead of checking all 100,000 documents to find 250 of them.

What a Missing Index Actually Costs

A query relying on a collection scan gets linearly slower as the collection grows — a query taking 10ms against 10,000 test documents might take several seconds against 10 million real ones, even though the query itself never changed. This is exactly why the problem is often invisible during development, where test collections are small, and only shows up once real data volume hits production.

💻 Coding Challenges

Challenge 1: Create a Compound Index

Queries on an orders collection regularly filter by status and sort by createdAt descending. Write the createIndex call that would speed this up.

Goal: Practice basic compound index syntax matching a real query pattern.

→ Solution

Challenge 2: Read an explain() Output

An explain("executionStats") result shows stage: "COLLSCAN", totalDocsExamined: 500000, nReturned: 12. What does this tell you, and what would you do about it?

Goal: Practice reading explain() output as an actionable diagnosis, not just numbers.

→ Solution

Challenge 3: Order a Compound Index Correctly

A query filters by status: "active" (exact match), sorts by priority, and filters createdAt with a range ($gt). Using the Equality-Sort-Range rule, what order should the compound index's fields be in?

Goal: Practice applying the ESR heuristic to a concrete multi-clause query.

→ Solution

⚠️ Gotcha: Indexes Aren't Free — They Slow Down Writes

It's tempting to index every field "just in case" once you've seen how much faster a query with an index can be. But every index has to be updated whenever a document is inserted, updated, or deleted in a way that touches an indexed field — more indexes means more work on every write, and more storage used to hold the index structures themselves. Indexes should be created deliberately, based on the query patterns your application actually runs (exactly what explain() helps you verify), not applied blanket across every field on the assumption that more indexing is always better.

🎯 What's Next

The next chapter connects everything to real application code: Working with MongoDB from Node.js — the native driver vs. the Mongoose ODM, and finally seeing this course's queries called from a real app instead of typed into mongosh by hand.