MongoDBIndex Types

Index Types

MongoDB supports many specialized index types beyond the basic single-field index. Each type is optimized for specific data patterns or query types — choosing the right one has a direct impact on both query performance and storage efficiency.

Single Field Index

The most common index type. Indexes a single field in ascending (1) or descending (-1) order. For single-field indexes, the direction matters only when sorting — for equality and range queries, either direction works equally well.

JS
// Ascending — good for range queries, low-to-high sorts
db.users.createIndex({ email: 1 })

// Descending — efficient for newest-first queries
db.posts.createIndex({ publishedAt: -1 })

// Unique single-field index
db.accounts.createIndex({ apiKey: 1 }, { unique: true })
Compound Index

A compound index spans multiple fields in a single index structure. It can serve queries on any prefix of the indexed fields — making one compound index far more versatile than several single-field indexes.

Compound index

JS
// Compound index on lastName, then firstName
db.users.createIndex({ lastName: 1, firstName: 1 })

// This ONE index can efficiently serve ALL of these queries:
db.users.find({ lastName: "Smith" })
db.users.find({ lastName: "Smith", firstName: "Alice" })
db.users.find({ lastName: "Smith" }).sort({ firstName: 1 })

// It CANNOT efficiently serve:
db.users.find({ firstName: "Alice" })  // firstName is not a prefix

// Another example: userId + status + createdAt
db.orders.createIndex({ userId: 1, status: 1, createdAt: -1 })
// Serves: { userId } queries, { userId, status } queries,
//         { userId, status, createdAt } queries, and sort by createdAt
// when filtered by userId (and optionally status)
Note
A compound index on { a, b, c } can serve queries on { a }, { a, b }, and { a, b, c }. It CANNOT efficiently serve queries on just { b } or { b, c } — those fields are not index prefixes.
Multikey Index — Arrays

When you create an index on a field that holds an array value, MongoDB automatically creates a multikey index — it stores one index entry per element in the array. This allows efficient queries that match any array element.

Multikey index

JS
// Documents in the articles collection look like:
// { _id: ..., title: "...", tags: ["mongodb", "database", "nosql"] }

// Create an index on the tags array field
db.articles.createIndex({ tags: 1 })
// MongoDB automatically creates a MULTIKEY index — one entry per tag

// Query: find all articles tagged "mongodb"
db.articles.find({ tags: "mongodb" })

// Query: find articles tagged with all of these
db.articles.find({ tags: { $all: ["mongodb", "database"] } })

// Query: find articles tagged with any of these
db.articles.find({ tags: { $in: ["mongodb", "nosql"] } })

// Compound multikey index (one array field allowed in compound)
db.articles.createIndex({ authorId: 1, tags: 1 })
Note
A compound index cannot have more than one multikey field. For example, createIndex({ tags: 1, categories: 1 }) will fail if both tags and categories contain arrays in the same document.
Text Index

Text indexes support full-text search queries using the $text operator. They tokenize string content, remove stop words, and apply stemming. A collection may have at most one text index, but it can span multiple fields. See the Text Search page for full coverage.

JS
// Multi-field text index with field weights
db.articles.createIndex(
  { title: "text", body: "text" },
  { weights: { title: 10, body: 1 } }
)

// Search
db.articles.find({ $text: { $search: "mongodb performance" } })
Geospatial Indexes — 2dsphere

2dsphere indexes support queries on GeoJSON data — points, lines, and polygons stored on a spherical model of the Earth. Use this for location-aware queries like "find all restaurants within 1 km of this point."

2dsphere geospatial index

JS
// Documents store GeoJSON location:
// { name: "Cafe Roma", location: { type: "Point", coordinates: [13.405, 52.52] } }
// coordinates are [longitude, latitude]

// Create a 2dsphere index
db.restaurants.createIndex({ location: "2dsphere" })

// $near — find restaurants within 1 km, sorted by distance
db.restaurants.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [13.405, 52.52]   // [lng, lat]
      },
      $maxDistance: 1000,   // 1000 metres = 1 km
      $minDistance: 0
    }
  }
})

// $geoWithin — all locations inside a polygon
db.restaurants.find({
  location: {
    $geoWithin: {
      $geometry: {
        type: "Polygon",
        coordinates: [[
          [13.38, 52.51],
          [13.45, 52.51],
          [13.45, 52.54],
          [13.38, 52.54],
          [13.38, 52.51]   // close the ring
        ]]
      }
    }
  }
})
2d Index (Legacy Flat Coordinates)

The legacy 2d index works with simple [x, y] flat coordinate pairs — not GeoJSON. Use 2dsphere for new applications. The 2d index only makes sense for data on a flat plane (e.g., a game map with pixel coordinates).

2d index for flat coordinates

JS
// Documents: { name: "spawn_point", coords: [120, 450] }

// Create 2d index
db.gameMap.createIndex({ coords: "2d" })

// $geoWithin $box — find all points within a rectangle
db.gameMap.find({
  coords: {
    $geoWithin: {
      $box: [
        [100, 400],   // bottom-left corner
        [200, 500]    // top-right corner
      ]
    }
  }
})

// $near on a flat grid
db.gameMap.find({
  coords: { $near: [120, 450], $maxDistance: 50 }
})
Hashed Index — Sharding

A hashed index computes a hash of the field value and stores the hash. They are used primarily as shard keys in sharded clusters to achieve an even distribution of data across shards — avoiding hotspots that occur with monotonically increasing values like timestamps or auto-incremented IDs.

Hashed index

JS
// Create a hashed index — typically used as a shard key
db.events.createIndex({ userId: "hashed" })

// Shard a collection using the hashed index as shard key
sh.shardCollection("mydb.events", { userId: "hashed" })

// Hashed indexes support only equality queries:
db.events.find({ userId: "user_abc123" })  // fast

// Range queries do NOT benefit from hashed indexes:
db.events.find({ userId: { $gt: "user_a" } })  // COLLSCAN
Note
Hashed indexes cannot serve range queries ($gt, $lt, $in with ranges). They are for equality lookups and even data distribution in sharded clusters only.
Sparse Index

A sparse index only includes documents that have the indexed field — it skips documents where the field is absent or null. This is useful for optional fields where you don't want null/missing values filling up the index, and it enables unique constraints on optional fields.

Sparse index

JS
// Unique sparse index on an optional email field
// Documents without an email field are NOT indexed (and not checked for uniqueness)
db.users.createIndex(
  { email: 1 },
  { unique: true, sparse: true }
)

// Without sparse: a unique index on an optional field would
// fail when a second document has no email (both have null)

// A sparse index may NOT be used if the query must return docs
// where the field is missing — MongoDB will fall back to COLLSCAN
db.users.find({ email: { $exists: false } })
// The sparse index on email won't help here; MongoDB scans the collection
Partial Index

A partial index indexes only the subset of documents matching a filter expression. This produces a smaller, more efficient index compared to indexing the full collection — and reduces write overhead since many documents won't need index updates.

Partial index

JS
// Only index active users — inactive users are excluded
db.users.createIndex(
  { email: 1 },
  {
    partialFilterExpression: { status: "active" },
    unique: true
  }
)

// Only index products that are in stock
db.products.createIndex(
  { price: 1, category: 1 },
  {
    partialFilterExpression: { inStock: true }
  }
)

// Only index high-value orders
db.orders.createIndex(
  { customerId: 1, createdAt: -1 },
  {
    partialFilterExpression: { total: { $gt: 1000 } }
  }
)

// IMPORTANT: the query must include the partial filter expression
// for MongoDB to use this index
db.users.find({ email: "alice@example.com", status: "active" }) // uses index
db.users.find({ email: "alice@example.com" })                   // COLLSCAN
TTL Index — Auto-Expiry

A TTL (Time To Live) index automatically deletes documents after a specified number of seconds have elapsed since the value of the indexed date field. MongoDB's background TTL monitor runs every 60 seconds, so actual deletion may lag by up to a minute.

TTL index

JS
// Auto-delete session documents 2 hours after createdAt
db.sessions.createIndex(
  { createdAt: 1 },
  { expireAfterSeconds: 7200 }   // 7200 seconds = 2 hours
)

// Auto-delete log entries 30 days after timestamp
db.logs.createIndex(
  { timestamp: 1 },
  { expireAfterSeconds: 2592000 }   // 30 days

)

// Expire at a specific time using an absolute expiry field
// Set expiresAt to a specific Date in each document
db.jobs.createIndex(
  { expiresAt: 1 },
  { expireAfterSeconds: 0 }   // expire AT the field value, not after it
)
// Document: { task: "cleanup", expiresAt: new Date("2025-12-31T23:59:59Z") }

// Modify the TTL duration after creation
db.runCommand({
  collMod: "sessions",
  index: { keyPattern: { createdAt: 1 }, expireAfterSeconds: 3600 }
})
Wildcard Index

Wildcard indexes index all fields in a document — or all fields under a subdocument path. They are designed for workloads with unpredictable or user-defined field names, such as storing arbitrary metadata, form responses, or IoT sensor readings where field names vary by device type.

Wildcard index

JS
// Index ALL fields in every document
db.products.createIndex({ "$**": 1 })

// Index only fields under the "metadata" subdocument
db.products.createIndex({ "metadata.$**": 1 })

// Documents can now have arbitrary metadata:
// { name: "Widget A", metadata: { color: "red", weight: 1.2 } }
// { name: "Widget B", metadata: { voltage: 5, frequency: 60 } }

// Both queries can use the wildcard index:
db.products.find({ "metadata.color": "red" })
db.products.find({ "metadata.voltage": { $gt: 3 } })

// Exclude specific fields from a wildcard index
db.products.createIndex(
  { "$**": 1 },
  {
    wildcardProjection: {
      largeTextField: 0,   // exclude this field
      binaryData: 0
    }
  }
)
Index Type Summary

Index Type

Best For

Limitation

Single Field

Simple equality/range queries on one field

Cannot cover multi-field query patterns efficiently

Compound

Multi-field queries, covering queries, ESR-ordered access patterns

Only prefix queries can use the index; field order matters

Multikey

Querying documents by array element values

Compound index cannot have two multikey fields

Text

Full-text keyword search with stop word removal

One per collection; no fuzzy match; no prefix match

2dsphere

GeoJSON location queries on a sphere (real-world coordinates)

GeoJSON only; no flat coordinate support

2d

Legacy flat [x,y] coordinate queries

Deprecated for new apps; no GeoJSON support

Hashed

Even data distribution for sharding shard keys

No range queries; equality only

Sparse

Optional fields where missing should not be indexed

Cannot be used for queries returning docs missing the field

Partial

Indexing a filtered subset of documents

Query must include the partial filter expression to use it

TTL

Automatic document expiry (sessions, logs, cache)

Only works on Date fields; ~60s deletion lag

Wildcard

Unpredictable / user-defined field names

Less efficient than targeted compound indexes for known fields

Tip
Choose compound indexes over multiple single-field indexes. One compound index on { userId: 1, createdAt: -1 } is more efficient than two separate indexes on userId and createdAt — it avoids index intersection, uses less memory, and can serve covered queries.