August 3, 2026 · Varun Sharma

MongoDB Indexes: Every Type, What It's For, and When to Actually Use It

Slow queries are almost never MongoDB's fault. They're usually a missing — or wrong — index. The problem is that MongoDB offers nearly a dozen index types, and most developers only ever learn one (a single-field index) and stop there, missing out on tools that could make their queries dramatically faster or solve problems they're currently working around in application code.

Here's a practical tour of every major index type in MongoDB, what it's actually for, and a real example of when to reach for it.

1. Single Field Index

The default, most common index — speeds up queries that filter or sort on one field.

javascript

db.users.createIndex({ email: 1 })

When to use it: Any field you frequently query on its own — a lookup field like email, username, or sku. The 1 means ascending order; -1 means descending. For single-field indexes it rarely matters which you choose, but it matters more once you combine fields.

javascript

// Now this is fast instead of a full collection scan
db.users.findOne({ email: "jane@example.com" })

2. Compound Index

Indexes multiple fields together, in a specific order. This is where most of the real performance wins live, and where most people get it wrong by not thinking about field order.

javascript

db.orders.createIndex({ userId: 1, status: 1, createdAt: -1 })

When to use it: Queries that filter on multiple fields together, especially combined with sorting.

javascript

// Uses the compound index efficiently
db.orders.find({ userId: "abc123", status: "shipped" }).sort({ createdAt: -1 })

The rule that trips people up: MongoDB compound indexes follow the ESR rule — Equality fields first, Sort fields next, Range fields last. Put fields you filter on with exact matches first, fields you sort by second, and fields you filter with ranges ($gt, $lt, etc.) last. Get the order wrong and MongoDB may not use the index at all, or use it far less efficiently.

3. Multikey Index (Arrays)

Automatically created when you index a field that holds an array — MongoDB indexes every element in the array individually.

javascript

// If "tags" is an array field like ["electronics", "sale", "featured"]
db.products.createIndex({ tags: 1 })

When to use it: Anytime you filter on an array field — tags, categories, permission lists.

javascript

// Finds any product where "sale" appears anywhere in the tags array
db.products.find({ tags: "sale" })

Watch out for: you can't create a compound index with two array fields in it — MongoDB will reject it. Only one array field is allowed per compound index.

4. Text Index

Enables full-text search across string fields — word-based matching, not just exact substring matches.

javascript

db.articles.createIndex({ title: "text", body: "text" })

When to use it: Basic search functionality — a blog search bar, product search, or searching support tickets — without standing up a separate search engine like Elasticsearch.

javascript

db.articles.find({ $text: { $search: "mongodb performance tuning" } })

Limitation to know upfront: a collection can only have one text index. If you need more advanced search (fuzzy matching, relevance tuning, faceted search), consider MongoDB Atlas Search instead.

5. Hashed Index

Indexes the hash of a field's value instead of the value itself. Mainly used for one specific purpose: sharding.

javascript

db.sessions.createIndex({ userId: "hashed" })

When to use it: As a shard key when you want writes evenly distributed across shards. A normal ascending index on something like a timestamp or auto-incrementing ID tends to funnel all new writes onto a single shard (a "hot shard"); hashing spreads them out randomly.

Not for: range queries. A hashed index can't efficiently support $gt / $lt — only equality lookups.

6. Geospatial Index (2dsphere / 2d)

Enables location-based queries — "find things near this point," "find things within this area."

javascript

db.restaurants.createIndex({ location: "2dsphere" })

When to use it: Any app with maps or location features — store locators, delivery radius checks, ride-hailing driver matching.

javascript

// Find restaurants within 2km of a point
db.restaurants.find({
  location: {
    $near: {
      $geometry: { type: "Point", coordinates: [-73.9857, 40.7484] },
      $maxDistance: 2000
    }
  }
})

Use 2dsphere for real-world (spherical, lat/long) coordinates — which is almost always what you want. 2d is a legacy option for flat, planar coordinate systems.

7. TTL Index (Time-To-Live)

Automatically deletes documents after a set amount of time. This one is criminally underused — a lot of developers write cron jobs to manually clean up expired data when MongoDB can just do it natively.

javascript

db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })

When to use it: Session tokens, password reset codes, temporary verification links, cache entries, log records you only need to retain for a limited window.

javascript

// This document will be automatically deleted 1 hour after insertion
db.sessions.insertOne({ userId: "abc123", createdAt: new Date() })

8. Partial Index

Indexes only the documents that match a filter condition, instead of the entire collection — smaller, faster, and cheaper to maintain.

javascript

db.orders.createIndex(
  { customerId: 1 },
  { partialFilterExpression: { status: "pending" } }
)

When to use it: When you only ever query a subset of documents — for example, if 95% of your orders are "completed" and you almost always query for "pending" ones. Indexing the whole collection would waste space and slow down writes for documents your queries never touch.

9. Unique Index

Enforces that no two documents can have the same value for a field — a database-level guarantee, not just an application-level check.

javascript

db.users.createIndex({ email: 1 }, { unique: true })

When to use it: Anywhere duplicate values would corrupt your data — emails, usernames, SKUs, API keys. Don't rely solely on application code to prevent duplicates; race conditions between simultaneous requests can slip through app-level checks, but the database will always enforce a unique index.

10. Wildcard Index

Indexes fields dynamically based on their names, without you having to know the schema ahead of time.

javascript

db.products.createIndex({ "attributes.$**": 1 })

When to use it: Collections with highly variable or user-defined schemas — think a multi-tenant SaaS product catalog where every tenant's product can have wildly different custom attributes (color, size for clothing; voltage, wattage for electronics). You can't predict every field in advance, so you let MongoDB index whatever shows up under attributes.

How to Decide What You Actually Need

  • Start by looking at your slowest, most frequent queries — run .explain("executionStats") on them and check whether they're doing a COLLSCAN (full collection scan, bad) instead of an IXSCAN (index scan, good).

  • Don't index everything "just in case" — every index speeds up reads but slows down writes (since MongoDB has to update every index on every insert/update) and takes up disk and memory. Index what you actually query.

  • For queries filtering on multiple fields, reach for a compound index before creating several single-field indexes — it's almost always more efficient.

  • Revisit your indexes periodically. Query patterns change as your app grows, and an index that made sense a year ago might be dead weight today.

The Takeaway

Most "MongoDB is slow" complaints aren't a MongoDB problem — they're a missing-index problem, or the wrong type of index for the query pattern. Once you know the full toolbox — compound indexes with proper field ordering, TTL for automatic cleanup, partial indexes for narrow query patterns, geospatial for location features — a huge number of "we need to add caching" or "we need to migrate to a different database" conversations turn out to be a five-minute fix.

The rule that saves you: don't guess which index you need — run .explain() on your real queries, find the COLLSCANs, and index based on evidence, not assumption.