MongoDB Index Types

Explore single and compound indexes in MongoDB: how they work, when to use each, and hands-on examples to speed up your queries.

Focus: MongoDB index types

Sponsored

Your MongoDB queries are starting to feel sluggish. You've added more documents, and now a simple find() on a field you search constantly takes hundreds of milliseconds. You're not alone — this is the classic moment when every developer realizes they need an index. In this lesson, you'll learn about MongoDB index types, focusing on the two most common: single-field and compound indexes. By the end, you'll know exactly how they work, when to use each, and how to create them with hands-on examples.

The Problem This Lesson Solves

Without an index, MongoDB performs a collection scan to answer every query. That means it reads every document in the collection to find the ones that match your filter. For a collection with a few thousand documents, this might be acceptable — but as your data grows into millions of documents, collection scans become unbearably slow and waste your server's I/O and CPU resources.

Consider a typical e-commerce collection: orders. A query like db.orders.find({status: "shipped"}) would scan all orders, checking each document's status field. With 10 million orders, that's 10 million document reads per query. Your application's response time balloons, and your database load skyrockets.

Indexes solve this by providing a sorted data structure (a B-tree) that MongoDB can traverse quickly, similar to the index at the back of a book. Instead of reading every page, you jump straight to the relevant entries. In this lesson, you'll learn the two foundational index types — single-field and compound — that address the majority of real-world performance problems.

Core Concept / Mental Model

Think of a single-field index as an alphabetized list of one field. If you have an index on status, MongoDB builds a sorted list of all status values, each pointing to the documents that contain it. When you query with {status: "shipped"}, MongoDB finds the entry "shipped" in the B-tree and retrieves only those documents — far faster than scanning everything.

A compound index is like a phone book sorted by last name, then first name. It contains multiple fields in a specified order. For example, an index on {status: 1, created_at: -1} sorts first by status (ascending), and within each status, by created_at (descending). This is ideal for queries that filter by status and then sort by created_at, because the index already provides the sorted order — no separate sort step is needed.

Key definitions: - Index key: The field(s) you index, plus the sort direction (1 for ascending, -1 for descending). - B-tree: The balanced tree structure MongoDB uses to store index entries. - Index selectivity: How unique the indexed values are; higher selectivity means better performance.

How It Works Step by Step

Step 1: Identify Query Patterns

Before creating an index, analyze your application's queries. Which fields appear in your filter and sort clauses? For example, if you frequently run db.orders.find({customer_id: 123}).sort({created_at: -1}), you need an index on customer_id and created_at.

Step 2: Create a Single-Field Index

Use the createIndex() method on a collection. Syntax: db.collection.createIndex({field: direction}). For instance, db.orders.createIndex({status: 1}) creates an ascending index on status.

Step 3: Create a Compound Index

The syntax extends to multiple fields: db.orders.createIndex({customer_id: 1, created_at: -1}). The order of fields matters because it defines how MongoDB sorts the index entries.

Step 4: Verify with explain()

Always check that your query actually uses the index. Use db.orders.find({status: "shipped"}).explain("executionStats") and look for the winningPlan — it should show IXSCAN for index scans instead of COLLSCAN.

Step 5: Monitor Performance

After creating indexes, monitor query performance using db.system.profile or MongoDB Atlas's performance advisor to ensure the indexes help.

Hands-On Walkthrough

Let's put this into practice with a sample orders collection. We'll create indexes and observe the performance difference. You'll need a running MongoDB instance (local or Atlas) and the mongosh shell.

First, insert some sample data:

// Insert sample orders
db.orders.insertMany([
  { customer_id: 101, status: "pending", created_at: new Date("2025-01-01") },
  { customer_id: 102, status: "shipped", created_at: new Date("2025-01-02") },
  { customer_id: 101, status: "shipped", created_at: new Date("2025-01-03") },
  { customer_id: 103, status: "canceled", created_at: new Date("2025-01-04") }
]);

Now, simulate a slow query and check its execution plan before creating an index:

// Check query plan before index
db.orders.find({ status: "shipped" }).explain("executionStats");

Look for "stage": "COLLSCAN" in the output — that's a collection scan. Note the executionTimeMillis value (likely a few milliseconds for this tiny dataset, but imagine millions of documents).

Next, create a single-field index and re-check:

// Create a single-field index on status
db.orders.createIndex({ status: 1 });

// Re-check the query plan
db.orders.find({ status: "shipped" }).explain("executionStats");

Now winningPlan.inputStage.stage should be IXSCAN — MongoDB used the index. The executionTimeMillis should be similar for small data, but the key is that MongoDB only reads index entries, not entire documents.

Now let's handle a more complex query with a filter and sort. Create a compound index:

// Create compound index on customer_id and created_at
// 1 = ascending, -1 = descending
db.orders.createIndex({ customer_id: 1, created_at: -1 });

// Query: find orders for customer 101, sorted by date descending
db.orders.find({ customer_id: 101 }).sort({ created_at: -1 }).explain("executionStats");

The explain output should show IXSCAN on the compound index, and MongoDB will not need an in-memory sort stage (look for the absence of SORT in the plan). This is a big win for performance.

Compare Options / When to Choose What

Index Type Best For Example Query Trade-offs
Single-field Simple equality filters on one field {status: "shipped"} Minimal storage, fast for one field; doesn't help multi-field filters
Compound Queries with multiple fields or mixed filter/sort {customer_id: 101, status: "shipped"} More storage and write overhead; field order matters, so you may need multiple compound indexes for different query patterns

When to Use Single-Field Index

Use a single-field index when your queries filter on one field only, or when you want a low-overhead way to speed up common lookups. For example, a users collection with a unique email field — a single-field index on email is perfect.

When to Use Compound Index

Choose a compound index when your queries involve multiple fields, especially if you filter on one field and sort on another. For instance, an e-commerce app that shows all orders for a user sorted by date would benefit from a compound index on {user_id: 1, created_at: -1}. The order of fields in the index should match the most common query pattern: equality fields first, then sort fields.

Troubleshooting & Edge Cases

Index Not Being Used

Your query might still use a COLLSCAN even if an index exists. Common causes: - Inequality operators ($ne, $nin) on the indexed field can prevent efficient use. - Regular expressions with leading wildcards (e.g., /^prefix/ is okay, but /contains/ is not). - Negation queries like {status: {$ne: "shipped"}} might ignore the index if they match a large portion of the collection.

Field Order in Compound Indexes

If you create {customer_id: 1, created_at: -1}, a query that filters only by created_at cannot use this index efficiently — it will have to scan all index entries. Always put equality fields before range/sort fields.

Write Performance Overhead

Every index adds overhead on writes because MongoDB must update the index on each insert, update, or delete. Too many indexes can slow down write-heavy workloads. Keep only the indexes your queries actually need.

Large Index Size

Indexes consume disk and memory (RAM for working set). A compound index with high-cardinality fields can become large, especially if you index long strings. Consider using hashed or partial indexes (covered in later lessons) for edge cases.

Verify with explain()

Always verify the query plan. If you see SORT stage in the plan, the index didn't provide the sort order. Adjust the index field directions to match your sort direction.

What You Learned & What's Next

You now understand the two foundational MongoDB index types: single-field and compound. You learned how to create them with createIndex(), how to verify their usage with explain(), and when to choose one over the other. You can now:

  • Explain how single-field and compound indexes differ.
  • Create a single-field index for simple equality queries.
  • Create a compound index for multi-field filters with sort.
  • Diagnose and fix common indexing pitfalls.

These skills directly improve your MongoDB performance and are essential for scaling applications.

Next in the MongoDB learning path, you'll explore multikey indexes for arrays, text indexes for search, and partial indexes to fine-tune performance further. Each builds on the same mental model of B-trees and sorted lists. Keep practicing — create indexes on your own sample data and use explain() to see the difference in action.

Practice recap

Create a new inventory collection with fields like category, price, and stock. Write queries that filter by category and sort by price, then create a compound index to optimize them. Use explain() to confirm the index is used, and compare execution time before and after.

Common mistakes

  • Creating a single-field index when your query has multiple filters — a compound index would be much faster.
  • Putting fields in the wrong order in a compound index (e.g., sort field before equality field), which prevents efficient index usage.
  • Ignoring explain() output — assuming an index is used without verifying IXSCAN vs COLLSCAN.
  • Over-indexing: adding too many indexes and slowing down writes with little read benefit.

Variations

  1. Multikey indexes: Automatically created when indexing an array field, allowing efficient queries on array elements.
  2. Partial indexes: Index only a subset of documents that match a filter expression, saving space and write overhead.
  3. Hashed indexes: Support equality queries on large fields (like long strings) but do not support range or sort operations.

Real-world use cases

  • E-commerce platform: speeding up order lookups by customer and status with a compound index on {customer_id, status}.
  • Social media feed: using a compound index on {user_id, created_at} to efficiently fetch posts sorted by date.
  • IoT telemetry data: indexing device IDs and timestamps as a compound index to quickly query sensor readings over time.

Key takeaways

  • A single-field index accelerates queries on one field by replacing full collection scans with B-tree lookups.
  • A compound index handles queries with multiple filters and sorts, but field order is critical — equality fields first, then sort fields.
  • Always verify index usage with explain("executionStats") — look for IXSCAN instead of COLLSCAN.
  • Indexes speed up reads but add write overhead, so create only the indexes your queries need.
  • The index direction (1 or -1) should match your sort order to avoid in-memory sorting.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.