🚀 MongoDB Indexing Mastery
Transform your queries from slow to lightning-fast! Master indexes with visual animations, real-world scenarios, and hands-on examples
📖 The Million-User Speed Crisis
Meet Alex, a backend developer at TechStart, a rapidly growing social media app. Everything was great when they had 10,000 users. Their MongoDB queries returned results in milliseconds. Life was good! 😊
❌ The Crisis Hits - 1 Million Users!
One Monday morning, Alex's phone explodes with alerts. The app is CRAWLING! 🐌
The Problem: MongoDB was scanning through 1 MILLION documents for every query! Like reading every single book in a library to find one specific book! 📚💀
Then Alex discovered MongoDB Indexes... 🎉
✅ After Adding Indexes
Alex added just 3 strategic indexes. The results were MAGICAL! ✨
The Magic: Instead of scanning 1 million documents, MongoDB now uses indexes to jump DIRECTLY to the exact documents needed! Like using a book's index to find page numbers instantly! 🎯
Let's learn how Alex did it, and how YOU can speed up your queries by 1000x! 🚀
🎯 What Are Indexes?
An index is a special data structure that stores a small portion of your data in an easy-to-traverse form. Think of it like a book's index - instead of reading every page to find "MongoDB", you check the index and jump directly to page 247! 📖
💡 Real-World Analogy
❌ WITHOUT Index (Collection Scan)
Scenario: Finding "John Smith" in a phone book with NO alphabetical order.
- Read EVERY SINGLE PAGE
- Check EVERY SINGLE NAME
- 1 million names = 1 million checks!
- Time: Hours!
MongoDB scans EVERY document 😰
✅ WITH Index (Index Scan)
Scenario: Finding "John Smith" in a phone book WITH alphabetical order.
- Open to section "S"
- Find "Smith"
- Find "John Smith"
- Time: Seconds!
MongoDB jumps DIRECTLY to data! 🚀
📊 Key Benefits of Indexes
Lightning-Fast Queries
Transform 30-second queries into 10ms responses. 1000x+ speed improvements are common!
Efficient Sorting
Pre-sorted index data makes ORDER BY operations instant instead of expensive in-memory sorts.
Reduced RAM Usage
Only load needed documents into memory instead of scanning entire collections.
Better Scalability
Performance stays consistent as your data grows from thousands to millions of documents.
🔬 How Indexes Work: B-Tree Structure
MongoDB uses B-Tree (Balanced Tree) data structures for indexes. Let's visualize how they work!
📊 B-Tree Index Structure (Age Field)
🎯 Key Points About B-Trees:
- Balanced: All leaf nodes are at the same depth - ensures consistent performance
- Sorted: Data is stored in sorted order - enables binary search (fast!)
- Logarithmic Time: Finding data takes O(log n) time instead of O(n)
- Example: 1 million documents = only ~20 comparisons needed! vs 1 million without index!
- Leaf Nodes: Contain pointers to actual documents in collection
⚡ Query Performance: With vs Without Index
Let's see the DRAMATIC difference indexes make! 🚀
📊 Query: db.users.find({ age: 28 })
💡 The Mathematics of Speed
Without Index
Linear Search O(n):
• 1,000 docs = 1,000 checks
• 1,000,000 docs = 1,000,000 checks
• 10x data = 10x slower! 😰
With Index
Binary Search O(log n):
• 1,000 docs = ~10 checks
• 1,000,000 docs = ~20 checks
• 10x data = +3 checks! 🚀
🗂️ Types of Indexes in MongoDB
Single Field Index
Index on one field. Most common and simple.
Use: Queries on single field
Compound Index
Index on multiple fields. Field order matters!
city: 1, age: 1
})
Use: Queries on multiple fields
Multikey Index
Index on array fields. Creates index entry per array element.
Use: Queries on array elements
Text Index
Full-text search. Tokenizes and stems words.
content: "text"
})
Use: Text search queries
Geospatial Index
Index for geographic data. 2d or 2dsphere.
location: "2dsphere"
})
Use: Location-based queries
Hashed Index
Hashes field value. For sharding and equality.
userId: "hashed"
})
Use: Shard keys, equality only
🖥️ Live Console Demo
Experience the MASSIVE speed difference! Click the buttons to simulate real MongoDB operations.
Connected to: localhost:27017
💡 Try these commands to see index magic in action!
📊 What You'll See
Watch how the same query goes from 25 seconds (COLLSCAN) without an index to 15 milliseconds (IXSCAN) with an index! This demonstrates a 1,666x speedup - the real power of indexing!
🛠️ Creating and Managing Indexes
📝 Basic Index Creation
// Create ascending index on age field
db.users.createIndex({ age: 1 })
// Create descending index
db.users.createIndex({ age: -1 })
// 1 = ascending order (18, 22, 25, 28...)
// -1 = descending order (89, 82, 78, 75...)
// MongoDB automatically creates an index on _id field!
⚡ Index Options
// Ensure email is unique across all documents
db.users.createIndex(
{ email: 1 },
{ unique: true }
)
// Prevents duplicate emails!
// Insert with duplicate email will fail with error
// Only index documents that have the field
db.users.createIndex(
{ phoneNumber: 1 },
{ sparse: true }
)
// Documents without phoneNumber are NOT in index
// Saves space if field is optional!
// Auto-delete documents after expiration
db.sessions.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 3600 } // Delete after 1 hour
)
// Perfect for sessions, logs, temporary data!
// MongoDB deletes expired documents automatically
// Index only documents matching a condition
db.orders.createIndex(
{ customerId: 1, orderDate: 1 },
{
partialFilterExpression: {
status: "active"
}
}
)
// Only indexes active orders!
// Saves space, faster for common queries
🔍 Viewing and Managing Indexes
// List all indexes on collection
db.users.getIndexes()
// Drop specific index
db.users.dropIndex("age_1")
// Drop index by specification
db.users.dropIndex({ age: 1 })
// Drop ALL indexes (except _id)
db.users.dropIndexes()
// Get index stats
db.users.stats()
// Check if query uses index
db.users.find({ age: 28 }).explain("executionStats")
🔗 Compound Indexes: Multiple Fields
Compound Indexes contain references to multiple fields. They're MORE POWERFUL than multiple single-field indexes! The order of fields matters!
// Create compound index: city THEN age
db.users.createIndex({ city: 1, age: 1 })
// This index can efficiently support:
// ✅ { city: "New York" }
// ✅ { city: "New York", age: 28 }
// ✅ { city: "New York", age: { $gt: 25 } }
// But NOT efficiently:
// ❌ { age: 28 } // Doesn't use city (first field)
// Field order matters! Think "phone book":
// Last name THEN first name works
// But finding by first name alone is slow!
📊 Compound Index Structure: { city: 1, age: 1 }
🎯 Compound Index Rules:
- Left-to-Right Rule: Index can be used if query starts with the FIRST field(s) in index
- Index { a: 1, b: 1, c: 1 } supports: {a}, {a,b}, {a,b,c} - but NOT {b}, {c}, {b,c}
- Order matters for sorting: Sort order must match index order for optimal performance
- ESR Rule: Equality fields first, Sort fields next, Range fields last
- Max 32 fields: But keep it practical - usually 2-4 fields max!
💡 Compound Index Best Practices
// Query: Find active users in NYC, sorted by age
db.users.find({
city: "New York", // Equality
status: "active", // Equality
age: { $gt: 25 } // Range
}).sort({ age: 1 }) // Sort
// BEST index follows ESR rule:
// E (Equality) → S (Sort) → R (Range)
db.users.createIndex({
city: 1, // E: Equality first
status: 1, // E: Equality second
age: 1 // S & R: Sort/Range last
})
// This index handles ALL parts of the query efficiently!
🎮 Interactive: Test Query Coverage
Index: { city: 1, age: 1, status: 1 }
Query Examples:
Result:
🔄 Query Execution Flow: Step-by-Step
Let's visualize how MongoDB executes a query WITH and WITHOUT an index!
🎯 Index Selection Decision Flow
How does MongoDB choose which index to use? Follow the decision tree!
🧠 How Query Planner Chooses:
- Selectivity: Index that narrows down results most (fewest docs examined)
- Index size: Smaller indexes are faster to traverse
- Sort compatibility: Index that matches sort order avoids in-memory sort
- Covered query: Index that contains all fields needed (no doc fetch)
- Historical performance: MongoDB learns from past query execution!
📊 Real-Time Performance Comparison
❌ WITHOUT Index
✅ WITH Index
🔢 Multikey Indexes: Indexing Arrays
Multikey Indexes automatically index array elements. MongoDB creates an index entry for EACH array element! Perfect for tags, categories, skills arrays.
// Sample document with array
{
"_id": 1,
"name": "John",
"tags": ["mongodb", "database", "nosql"],
"skills": ["javascript", "python", "react"]
}
// Create index on array field
db.users.createIndex({ tags: 1 })
// MongoDB automatically detects array and creates multikey index!
// Creates index entries:
// - "mongodb" → doc 1
// - "database" → doc 1
// - "nosql" → doc 1
// Now queries are FAST:
db.users.find({ tags: "mongodb" }) // Uses index!
db.users.find({ tags: { $in: ["mongodb", "database"] } }) // Uses index!
📊 How Multikey Index Works
⚠️ Multikey Index Limitations:
- One multikey field per compound index: Can't have { arr1: 1, arr2: 1 } if both are arrays
- Larger index size: More array elements = more index entries = more storage
- Slower writes: Updating array updates multiple index entries
- Best for: Tags, categories, skills - arrays with reasonable size (< 100 elements)
🔤 Text Indexes: Full-Text Search
Text Indexes enable powerful full-text search! They tokenize words, remove stop words, stem words, and support multiple languages. Think "Google search" for your MongoDB data!
// Create text index on single field
db.articles.createIndex({ content: "text" })
// Create text index on multiple fields
db.articles.createIndex({
title: "text",
content: "text",
tags: "text"
})
// Search using text index
db.articles.find({ $text: { $search: "mongodb database" } })
// Search with phrases
db.articles.find({ $text: { $search: "\"full text search\"" } })
// Exclude words with minus
db.articles.find({ $text: { $search: "mongodb -sql" } })
// Sort by text score (relevance)
db.articles.find(
{ $text: { $search: "mongodb" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
✨ Text Index Features:
- Tokenization: "Hello World" → ["hello", "world"]
- Stop Words: Removes "the", "is", "at", etc.
- Stemming: "running", "runs" → "run"
- Case-insensitive: "MongoDB" = "mongodb" = "MONGODB"
- Relevance Scoring: Returns most relevant results first
- Multi-language: Supports 15+ languages!
- Only one per collection: Can have only ONE text index
🎯 Index Strategy & Best Practices
✅ DO's - Follow These
- Index query filters: Fields in WHERE clauses
- Index sort fields: Fields in ORDER BY
- Use compound indexes: Better than multiple single indexes
- Follow ESR rule: Equality, Sort, Range
- Monitor with explain(): Check if index is used
- Index selectivity: High-cardinality fields first
- Analyze slow queries: Profile and optimize
❌ DON'Ts - Avoid These
- Don't over-index: Too many = slow writes
- Don't index low-cardinality: e.g., gender (M/F)
- Don't ignore RAM: Indexes must fit in RAM
- Don't create duplicate: Remove unused indexes
- Don't index everything: Balance reads vs writes
- Don't forget maintenance: Rebuild fragmented indexes
- Don't ignore order: Wrong field order = wasted index
📊 Query Analysis with explain()
// Check if query uses index
db.users.find({ age: 28 }).explain("executionStats")
// Key fields to check in output:
// {
// "executionStats": {
// "executionSuccess": true,
// "nReturned": 5, // Documents returned
// "executionTimeMillis": 2, // Query time
// "totalDocsExamined": 5, // Docs scanned
// "totalKeysExamined": 5, // Index entries scanned
// "executionStages": {
// "stage": "FETCH",
// "inputStage": {
// "stage": "IXSCAN", // ✅ Using index!
// "indexName": "age_1"
// }
// }
// }
// }
// BAD signs:
// - "stage": "COLLSCAN" → Full collection scan!
// - totalDocsExamined >> nReturned → Scanning too many docs
// - High executionTimeMillis → Slow query
// GOOD signs:
// - "stage": "IXSCAN" → Using index!
// - totalDocsExamined ≈ nReturned → Efficient
// - Low executionTimeMillis → Fast query
⚡ Performance Optimization Tips
Keep Indexes in RAM
Indexes should fit entirely in RAM for best performance. Monitor index size with db.collection.stats().
Index Selectivity
Index high-cardinality fields (many unique values). Low cardinality (e.g., boolean) = inefficient.
Bad: gender, isActive
Read/Write Balance
More indexes = faster reads but slower writes. Find the right balance for your workload.
Write-heavy: Fewer indexes
Covered Queries
Query returns ONLY indexed fields = ultra-fast! No document fetch needed.
Index Intersection
MongoDB can use multiple indexes for one query (v2.6+), but compound index is usually faster.
Over: Multiple single indexes
Remove Unused Indexes
Regularly audit indexes. Remove those never used - they waste RAM and slow writes.
🎯 Golden Rules of Indexing
1. Index your queries: Every field in WHERE, ORDER BY, JOIN should be considered for indexing
2. Compound > Multiple: One compound index beats multiple single-field indexes
3. Monitor & Analyze: Use explain() religiously. Measure, don't guess!
4. Balance is Key: Too few = slow reads. Too many = slow writes. Find your sweet spot!
🔢 Multikey Indexes: Indexing Arrays
Multikey Indexes automatically index array elements. MongoDB creates an index entry for EACH array element! Perfect for tags, categories, skills arrays.
// Sample document with array
{
"_id": 1,
"name": "John",
"tags": ["mongodb", "database", "nosql"],
"skills": ["javascript", "python", "react"]
}
// Create index on array field
db.users.createIndex({ tags: 1 })
// MongoDB automatically detects array and creates multikey index!
// Creates index entries:
// - "mongodb" → doc 1
// - "database" → doc 1
// - "nosql" → doc 1
// Now queries are FAST:
db.users.find({ tags: "mongodb" }) // Uses index!
db.users.find({ tags: { $in: ["mongodb", "database"] } }) // Uses index!
📊 How Multikey Index Works
⚠️ Multikey Index Limitations:
- One multikey field per compound index: Can't have { arr1: 1, arr2: 1 } if both are arrays
- Larger index size: More array elements = more index entries = more storage
- Slower writes: Updating array updates multiple index entries
- Best for: Tags, categories, skills - arrays with reasonable size (< 100 elements)
🔤 Text Indexes: Full-Text Search
Text Indexes enable powerful full-text search! They tokenize words, remove stop words, stem words, and support multiple languages. Think "Google search" for your MongoDB data!
// Create text index on single field
db.articles.createIndex({ content: "text" })
// Create text index on multiple fields
db.articles.createIndex({
title: "text",
content: "text",
tags: "text"
})
// Search using text index
db.articles.find({ $text: { $search: "mongodb database" } })
// Search with phrases
db.articles.find({ $text: { $search: "\"full text search\"" } })
// Exclude words with minus
db.articles.find({ $text: { $search: "mongodb -sql" } })
// Sort by text score (relevance)
db.articles.find(
{ $text: { $search: "mongodb" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
✨ Text Index Features:
- Tokenization: "Hello World" → ["hello", "world"]
- Stop Words: Removes "the", "is", "at", etc.
- Stemming: "running", "runs" → "run"
- Case-insensitive: "MongoDB" = "mongodb" = "MONGODB"
- Relevance Scoring: Returns most relevant results first
- Multi-language: Supports 15+ languages!
- Only one per collection: Can have only ONE text index
🎯 Index Strategy & Best Practices
✅ DO's - Follow These
- Index query filters: Fields in WHERE clauses
- Index sort fields: Fields in ORDER BY
- Use compound indexes: Better than multiple single indexes
- Follow ESR rule: Equality, Sort, Range
- Monitor with explain(): Check if index is used
- Index selectivity: High-cardinality fields first
- Analyze slow queries: Profile and optimize
❌ DON'Ts - Avoid These
- Don't over-index: Too many = slow writes
- Don't index low-cardinality: e.g., gender (M/F)
- Don't ignore RAM: Indexes must fit in RAM
- Don't create duplicate: Remove unused indexes
- Don't index everything: Balance reads vs writes
- Don't forget maintenance: Rebuild fragmented indexes
- Don't ignore order: Wrong field order = wasted index
📊 Query Analysis with explain()
// Check if query uses index
db.users.find({ age: 28 }).explain("executionStats")
// Key fields to check in output:
// {
// "executionStats": {
// "executionSuccess": true,
// "nReturned": 5, // Documents returned
// "executionTimeMillis": 2, // Query time
// "totalDocsExamined": 5, // Docs scanned
// "totalKeysExamined": 5, // Index entries scanned
// "executionStages": {
// "stage": "FETCH",
// "inputStage": {
// "stage": "IXSCAN", // ✅ Using index!
// "indexName": "age_1"
// }
// }
// }
// }
// BAD signs:
// - "stage": "COLLSCAN" → Full collection scan!
// - totalDocsExamined >> nReturned → Scanning too many docs
// - High executionTimeMillis → Slow query
// GOOD signs:
// - "stage": "IXSCAN" → Using index!
// - totalDocsExamined ≈ nReturned → Efficient
// - Low executionTimeMillis → Fast query
⚡ Performance Optimization Tips
Keep Indexes in RAM
Indexes should fit entirely in RAM for best performance. Monitor index size with db.collection.stats().
Index Selectivity
Index high-cardinality fields (many unique values). Low cardinality (e.g., boolean) = inefficient.
Bad: gender, isActive
Read/Write Balance
More indexes = faster reads but slower writes. Find the right balance for your workload.
Write-heavy: Fewer indexes
Covered Queries
Query returns ONLY indexed fields = ultra-fast! No document fetch needed.
Index Intersection
MongoDB can use multiple indexes for one query (v2.6+), but compound index is usually faster.
Over: Multiple single indexes
Remove Unused Indexes
Regularly audit indexes. Remove those never used - they waste RAM and slow writes.
🎯 Golden Rules of Indexing
1. Index your queries: Every field in WHERE, ORDER BY, JOIN should be considered for indexing
2. Compound > Multiple: One compound index beats multiple single-field indexes
3. Monitor & Analyze: Use explain() religiously. Measure, don't guess!
4. Balance is Key: Too few = slow reads. Too many = slow writes. Find your sweet spot!
❓ Interview Questions & Answers
Answer:
What is an Index:
An index is a special data structure (B-Tree) that stores a small portion of your collection's data in an easy-to-traverse form. It's like a book's index - instead of reading every page to find "MongoDB", you check the index and jump to page 247!
How it Improves Performance:
- Reduces documents scanned: Without index = scan ALL documents (O(n)). With index = binary search (O(log n))
- Example: 1 million documents
- Without index: 1,000,000 checks → 25 seconds
- With index: ~20 checks → 15 milliseconds
- Result: 1,666x FASTER! 🚀
- Efficient sorting: Data already sorted in index = instant ORDER BY
- Reduced RAM usage: Only load needed documents, not entire collection
How it Works: MongoDB uses B-Tree structure. Each non-leaf node contains keys that divide the tree, leaf nodes contain pointers to actual documents. Searching involves traversing from root to leaf (logarithmic time).
Answer:
Compound Index: An index on multiple fields. Order of fields matters!
Example:
db.users.createIndex({ city: 1, age: 1 })
// This index can efficiently support:
// ✅ { city: "NYC" }
// ✅ { city: "NYC", age: 28 }
// ✅ { city: "NYC", age: { $gt: 25 } }
// But NOT:
// ❌ { age: 28 } // city not specified
When to Use:
- Queries filter on multiple fields: WHERE city AND age
- Queries filter AND sort: WHERE city ORDER BY age
- Better than multiple single indexes: One compound index is more efficient
Field Order Rules:
- Left-to-Right Rule: Index can be used if query starts with first field(s)
- ESR Rule: Equality fields first, Sort fields next, Range fields last
- Example: Index {a:1, b:1, c:1} supports {a}, {a,b}, {a,b,c} but NOT {b}, {c}, {b,c}
Answer:
These are execution stages you see in explain() output:
COLLSCAN (Collection Scan):
- What: Full collection scan - examines EVERY document
- Performance: O(n) - linear time
- Example: 1M documents = 1M checks
- RAM: High usage - loads many documents
- When: No suitable index, or small collections where index isn't worth it
- Sign: ❌ BAD for large collections!
IXSCAN (Index Scan):
- What: Uses index to find documents directly
- Performance: O(log n) - logarithmic time
- Example: 1M documents = ~20 checks
- RAM: Low usage - only loads matching documents
- When: Suitable index exists for query
- Sign: ✅ GOOD - this is what you want!
How to Check:
db.users.find({ age: 28 }).explain("executionStats")
// Look for "stage" field:
// "stage": "COLLSCAN" → ❌ Full scan
// "stage": "IXSCAN" → ✅ Using index
Answer:
Multikey Index: Automatically created when you index an array field. MongoDB creates one index entry for EACH array element.
Example:
// Document
{
name: "Alice",
tags: ["mongodb", "database", "nosql"]
}
// Create index
db.users.createIndex({ tags: 1 })
// MongoDB creates index entries:
// "mongodb" → Alice
// "database" → Alice
// "nosql" → Alice
// Query is fast!
db.users.find({ tags: "mongodb" }) // Uses index!
How it Works:
- For each document, MongoDB creates multiple index entries
- One entry per array element
- Array with 10 elements = 10 index entries for that document
Limitations:
- One multikey field per compound index: Can't index {arr1: 1, arr2: 1} if both are arrays
- Larger index size: More elements = more entries = more storage
- Slower writes: Updating array updates multiple index entries
Best For: Tags, categories, skills - arrays with reasonable size (< 100 elements per document)
Answer:
ESR Rule: Best practice for compound index field order.
- E = Equality fields first
- S = Sort fields next
- R = Range fields last
Why: This order maximizes index efficiency because:
- Equality narrows down fastest: city = "NYC" eliminates most documents
- Sort uses pre-sorted index: Data already sorted by sort fields
- Range scans remaining: Only scan range within narrowed dataset
Example:
// Query
db.users.find({
city: "NYC", // E: Equality
status: "active", // E: Equality
age: { $gt: 25 } // R: Range
}).sort({ name: 1 }) // S: Sort
// BEST index following ESR:
db.users.createIndex({
city: 1, // E: Equality
status: 1, // E: Equality
name: 1, // S: Sort
age: 1 // R: Range
})
Answer:
Covered Query: A query where ALL returned fields are in the index. MongoDB can answer the query entirely from the index without fetching actual documents!
Example:
// Create index
db.users.createIndex({ age: 1, name: 1 })
// ✅ COVERED query - Returns only indexed fields
db.users.find(
{ age: 28 },
{ age: 1, name: 1, _id: 0 } // Only age & name
)
// ❌ NOT COVERED - Returns non-indexed field
db.users.find(
{ age: 28 },
{ age: 1, name: 1, email: 1 } // email not in index
)
Why Ultra-Fast:
- No document fetch: Doesn't load documents from disk
- Index-only operation: All data in index (which is in RAM)
- Minimal I/O: No disk reads for documents
- Example speed: 2-10x faster than regular index scan!
Answer:
Negative Impacts of Too Many Indexes:
1. Slower Writes:
- Every INSERT/UPDATE/DELETE must update ALL indexes
- 10 indexes = 10x write overhead per operation
- Example: Insert with 10 indexes can be 5-10x slower than no indexes
2. Increased Storage:
- Each index consumes disk space
- Indexes can be 10-50% of collection size
- 10 indexes = potentially 100-500% storage overhead!
3. RAM Pressure:
- All indexes should fit in RAM for optimal performance
- Too many indexes = not all fit in RAM = disk I/O = slow
The Trade-off:
Indexes trade write performance for read performance.
- Read-heavy app: More indexes OK (90% reads, 10% writes)
- Write-heavy app: Fewer indexes better (30% reads, 70% writes)
- Typical sweet spot: 5-10 indexes per collection
Answer:
Step-by-Step Process:
1. Enable Profiling:
// Enable profiler for queries > 100ms
db.setProfilingLevel(1, { slowms: 100 })
// View slow queries
db.system.profile.find().sort({ ts: -1 }).limit(10)
2. Use explain() to Analyze:
db.users.find({ age: 28 }).explain("executionStats")
// Red flags:
// ❌ "stage": "COLLSCAN"
// ❌ totalDocsExamined >> nReturned
// ❌ executionTimeMillis > 1000
3. Common Solutions:
- COLLSCAN: Create index on query fields
- Wrong Index: Create better compound index following ESR rule
- Excessive Scanning: Improve selectivity with compound index