Indexes
⚡ Indexes
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
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
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
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.
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.
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.
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.