DEV Community

Cover image for MongoDB Indexes Finally Clicked for Me: Understanding Indexes, Compound Indexes & the Prefix Rule šŸš€
aarthirs
aarthirs

Posted on

MongoDB Indexes Finally Clicked for Me: Understanding Indexes, Compound Indexes & the Prefix Rule šŸš€

While working on a MERN project, I came across these indexes:

transactionSchema.index({ user: 1, date: -1 });
transactionSchema.index({ user: 1, type: -1 });
transactionSchema.index({ user: 1, category: -1 });
Enter fullscreen mode Exit fullscreen mode

My first reaction was:

"Why are we creating 3 different indexes for the same schema? Isn't one index enough?"

At that time, my understanding was:

"Indexes help MongoDB find records faster."

Which is true, but it wasn't enough to explain why multiple indexes existed for the same collection.

That simple doubt led me down a rabbit hole of learning about indexes, compound indexes, how MongoDB stores them, and the famous Prefix Rule.

Here's what I learned.


What is an Index?

Imagine a collection with millions of transactions.

db.transactions.find({
  user: "Aarthi"
});
Enter fullscreen mode Exit fullscreen mode

Without an index, MongoDB may need to inspect every document until it finds the matching records.

This is called a Collection Scan.

Think of it like searching for a chapter in a book without a table of contents. You'd have to flip through page after page until you find it.

An index works like a book's table of contents.

Instead of scanning every document, MongoDB can jump directly to the relevant records.

Example:

db.transactions.createIndex({
  user: 1
});
Enter fullscreen mode Exit fullscreen mode

Now MongoDB can quickly locate all transactions belonging to a specific user.


What is a Compound Index?

A compound index contains multiple fields.

Example:

db.transactions.createIndex({
  user: 1,
  date: -1
});
Enter fullscreen mode Exit fullscreen mode

This means MongoDB organizes the index by:

user
  └── date
Enter fullscreen mode Exit fullscreen mode

Conceptually, it looks something like:

Aarthi
   2025-08-10
   2025-08-09
   2025-08-08

John
   2025-08-10
   2025-08-05
Enter fullscreen mode Exit fullscreen mode

The data is first grouped by user, and within each user, it is ordered by date.

Now queries like:

db.transactions.find({
  user: "Aarthi"
}).sort({
  date: -1
});
Enter fullscreen mode Exit fullscreen mode

become very efficient.

MongoDB can jump directly to Aarthi's records and retrieve them in date order.


The Prefix Rule: The Concept That Finally Made It Click

Consider this index:

{
  user: 1,
  date: -1
}
Enter fullscreen mode Exit fullscreen mode

MongoDB can efficiently use it for:

find({
  user: "Aarthi"
});
Enter fullscreen mode Exit fullscreen mode

āœ… Works

find({
  user: "Aarthi",
  date: "2025-08-10"
});
Enter fullscreen mode Exit fullscreen mode

āœ… Works

But:

find({
  date: "2025-08-10"
});
Enter fullscreen mode Exit fullscreen mode

āŒ Not efficient

Why?

Because the index is organized by user first and then by date.

MongoDB knows where each user's records start, but it doesn't know where a specific date begins without first navigating through the user groups.

This behavior is known as the Prefix Rule.

A compound index can efficiently support queries that start from the leftmost fields of the index.

For example:

{
  user: 1,
  date: -1,
  type: 1
}
Enter fullscreen mode Exit fullscreen mode

can efficiently support:

find({ user })

find({ user, date })

find({ user, date, type })
Enter fullscreen mode Exit fullscreen mode

But not:

find({ date })

find({ type })

find({ date, type })
Enter fullscreen mode Exit fullscreen mode

because those queries do not start from the leftmost field.


Back to My Original Doubt

I originally saw:

transactionSchema.index({ user: 1, date: -1 });
transactionSchema.index({ user: 1, type: -1 });
transactionSchema.index({ user: 1, category: -1 });
Enter fullscreen mode Exit fullscreen mode

Now it makes sense.

Recent Transactions

find({ user }).sort({ date: -1 });
Enter fullscreen mode Exit fullscreen mode

Uses:

{ user: 1, date: -1 }
Enter fullscreen mode Exit fullscreen mode

Filter By Transaction Type

find({
  user,
  type: "expense"
});
Enter fullscreen mode Exit fullscreen mode

Uses:

{ user: 1, type: -1 }
Enter fullscreen mode Exit fullscreen mode

Filter By Category

find({
  user,
  category: "food"
});
Enter fullscreen mode Exit fullscreen mode

Uses:

{ user: 1, category: -1 }
Enter fullscreen mode Exit fullscreen mode

Each index is optimized for a different query pattern.


Another Question I Had: Where Are Indexes Stored?

Initially, I thought indexes somehow reorganized the actual documents.

But that's not what happens.

MongoDB stores documents and indexes separately.

Conceptually:

Collection
-----------
Doc1
Doc2
Doc3
Doc4
Enter fullscreen mode Exit fullscreen mode

And separately:

Index(user,date)
----------------
Aarthi -> Doc5
Aarthi -> Doc2
Aarthi -> Doc1

Rosy -> Doc8
Enter fullscreen mode Exit fullscreen mode
Index(user,type)
----------------
Aarthi -> expense -> Doc1
Aarthi -> income  -> Doc2

Rosy -> expense -> Doc8
Enter fullscreen mode Exit fullscreen mode
Index(user,category)
--------------------
Aarthi -> food -> Doc1
Aarthi -> travel -> Doc2

Rosy -> food -> Doc8
Enter fullscreen mode Exit fullscreen mode

The actual documents remain unchanged.

Indexes are separate data structures that contain references to documents.


Then Why Do We Need .sort() If the Index Is Already Sorted?

This confused me too.

Suppose we have:

{
  user: 1,
  date: -1
}
Enter fullscreen mode Exit fullscreen mode

The index itself is sorted.

However, MongoDB does not guarantee that results should be returned in date order unless we explicitly request it.

For example:

db.transactions.find({
  user: "Aarthi"
});
Enter fullscreen mode Exit fullscreen mode

This may use the index to locate records quickly.

But:

db.transactions.find({
  user: "Aarthi"
}).sort({
  date: -1
});
Enter fullscreen mode Exit fullscreen mode

tells MongoDB:

"Return these records in descending date order."

Since the index is already sorted that way, MongoDB can use the index directly and avoid an expensive in-memory sort.

That's one of the biggest performance benefits of compound indexes.


How Does MongoDB Handle Multiple Indexes?

This was another question I had.

Suppose we have:

{ user: 1, date: -1 }

{ user: 1, type: 1 }

{ user: 1, category: 1 }
Enter fullscreen mode Exit fullscreen mode

MongoDB creates three completely separate index structures.

Think of them as three separate books:

Index 1

Aarthi
  2025-08-10
  2025-08-09

Rosy
  2025-08-10
Enter fullscreen mode Exit fullscreen mode

Index 2

Aarthi
  expense
  income

Rosy
  expense
Enter fullscreen mode Exit fullscreen mode

Index 3

Aarthi
  food
  travel

Rosy
  shopping
Enter fullscreen mode Exit fullscreen mode

When a query arrives, MongoDB's query planner decides which index can answer the query most efficiently.

Example:

find({
  user: "Aarthi",
  type: "expense"
});
Enter fullscreen mode Exit fullscreen mode

MongoDB sees:

{ user: 1, type: 1 }
Enter fullscreen mode Exit fullscreen mode

and chooses that index.

For:

find({
  user: "Aarthi"
}).sort({
  date: -1
});
Enter fullscreen mode Exit fullscreen mode

MongoDB chooses:

{ user: 1, date: -1 }
Enter fullscreen mode Exit fullscreen mode

because it perfectly matches the query.


Why Not Create One Huge Index?

I also wondered:

{
  user: 1,
  date: -1,
  type: 1,
  category: 1
}
Enter fullscreen mode Exit fullscreen mode

Wouldn't this solve everything?

Not really.

Because of the Prefix Rule.

This index efficiently supports:

find({ user })

find({ user, date })

find({ user, date, type })
Enter fullscreen mode Exit fullscreen mode

But:

find({
  user,
  category
});
Enter fullscreen mode Exit fullscreen mode

is not optimal because date and type appear before category in the index definition.

MongoDB cannot efficiently skip the middle fields.

That's why index design should follow actual query patterns rather than simply including every field.


The Trade-Off Most Beginners Miss

Indexes speed up reads.

But they are not free.

Every insert, update, or delete must also update all related indexes.

For example, when inserting:

{
  user: "Aarthi",
  date: "2025-08-10",
  type: "expense",
  category: "food"
}
Enter fullscreen mode Exit fullscreen mode

MongoDB must update:

Index(user,date)

Index(user,type)

Index(user,category)
Enter fullscreen mode Exit fullscreen mode

every single time.

So indexes improve read performance at the cost of:

  • Additional storage
  • Slower writes
  • Extra maintenance

This is the classic database trade-off.


My Biggest Takeaway

Before this, I thought:

"Indexes make queries faster."

Now I think:

"Indexes make specific query patterns faster."

Understanding compound indexes, how MongoDB stores them, and the Prefix Rule completely changed the way I think about database design.

The best index is not the one with the most fields.

The best index is the one that matches the queries your application runs most often.

Sometimes a simple question like:

"Why do we have 3 indexes for the same schema?"

can lead to understanding an entire database concept.

If you've had a similar "aha!" moment while learning databases, I'd love to hear it in the comments.

Top comments (1)

Collapse
 
aarthirs profile image
aarthirs

Did you find this post useful? Follow me for more developer-friendly content, share your thoughts in the comments, and help others discover it by sharing this post.