DocsHub
Indexes

Index Types

Learn about specialized MongoDB index types — text, sparse, partial, TTL, and wildcard indexes.

Index Types

In the previous file we covered the most common indexes — single field, compound, and multikey. MongoDB also has specialized index types built for specific use cases.

  • Text index — full-text search on string fields
  • Sparse index — only index documents where the field exists
  • Partial index — only index documents that match a filter condition
  • TTL index — automatically delete documents after a set time
  • Wildcard index — index all fields in a document dynamically

Text Index

A text index enables full-text search on string fields using the $text operator. We saw $text in the querying section — now let's look at how to set up the index properly.

Create a text index

// Text index on a single field
db.courses.createIndex({ title: "text" })

// Text index on multiple fields
db.courses.createIndex({
  title: "text",
  description: "text"
})

Notice the value is "text" — not 1 or -1 like other indexes.

Using the text index

// Search for courses containing "science"
db.courses.find({ $text: { $search: "science" } })

// Search across both title and description
db.courses.find({ $text: { $search: "computer programming" } })

When you create a text index on multiple fields, $text searches across all of them automatically with one query.

Text index with weights

You can give different fields different importance using weights. A higher weight means matches in that field rank higher in relevance:

db.courses.createIndex(
  {
    title: "text",
    description: "text"
  },
  {
    weights: {
      title: 10,
      description: 5
    },
    name: "courses_text_index"
  }
)

A match in title now counts twice as much as a match in description when sorting by relevance score.

One text index per collection

A collection can only have one text index. But that one index can cover multiple fields — so always combine all fields you want to search into a single text index.

// Wrong — you cannot create two text indexes
db.courses.createIndex({ title: "text" })
db.courses.createIndex({ description: "text" }) // ERROR

// Correct — one text index covering both fields
db.courses.createIndex({ title: "text", description: "text" })

In our school system, a text index on courses makes sense for a search bar where students can search for courses by keyword. We can also add a text index on students.name for an admin search feature.


Sparse Index

A sparse index only indexes documents where the indexed field exists. Documents that do not have the field are completely ignored by the index.

Why sparse indexes matter

Imagine only some students have a scholarshipId field — maybe 200 out of 5,000 students have a scholarship. If you create a regular index on scholarshipId, MongoDB creates index entries for all 5,000 students — with null entries for the 4,800 who do not have the field.

A sparse index only creates entries for the 200 students who actually have scholarshipId. The index is smaller, faster, and uses less memory.

Create a sparse index

db.students.createIndex(
  { scholarshipId: 1 },
  { sparse: true }
)

When to use sparse indexes

// Only some students have a phone number
db.students.createIndex({ phone: 1 }, { sparse: true })

// Only some teachers have a secondarySubject
db.teachers.createIndex({ secondarySubject: 1 }, { sparse: true })

// Only graduated students have a graduationDate
db.students.createIndex({ graduationDate: 1 }, { sparse: true })

Be careful when using a sparse index with sort(). If you sort by a sparse-indexed field, documents missing that field are excluded from the results entirely — not just sorted last. This can make results look incomplete. If you need all documents sorted, use a regular index instead.


Partial Index

A partial index only indexes documents that match a filter condition you define. It is more flexible than a sparse index — instead of just checking if a field exists, you can specify any filter.

Create a partial index

db.collection.createIndex(
  { field: 1 },
  { partialFilterExpression: { condition } }
)

Example — Index only enrolled students

In our school system, most queries are about enrolled students. There is no point indexing unenrolled students if we never query them:

db.students.createIndex(
  { name: 1 },
  { partialFilterExpression: { enrolled: true } }
)

This index only contains enrolled students. Queries that filter by { enrolled: true } will use this index:

// Uses the partial index — fast
db.students.find({ name: "Ali Hassan", enrolled: true })

But a query without the enrolled: true filter will not use this index:

// Does NOT use the partial index — falls back to collection scan
db.students.find({ name: "Ali Hassan" })

Example — Index only active teachers

db.teachers.createIndex(
  { subject: 1 },
  { partialFilterExpression: { active: true } }
)

Example — Unique email only for enrolled students

A partial index with unique: true enforces uniqueness only among documents that match the filter:

db.students.createIndex(
  { email: 1 },
  {
    unique: true,
    partialFilterExpression: { enrolled: true }
  }
)

Two unenrolled students can share an email — only enrolled students must have unique emails.

Partial vs Sparse

Sparse IndexPartial Index
Filters byField existenceAny condition
FlexibilityLowHigh
Use whenField is optionalYou query a subset of documents

TTL Index

A TTL (Time To Live) index automatically deletes documents after a specified number of seconds. MongoDB runs a background job every 60 seconds that removes expired documents.

This is perfect for temporary data — session tokens, password reset codes, notifications, logs.

Create a TTL index

db.collection.createIndex(
  { dateField: 1 },
  { expireAfterSeconds: number }
)

The field must be a Date type. MongoDB calculates expiry as: dateField value + expireAfterSeconds.

Example — Delete session tokens after 1 hour

db.sessions.createIndex(
  { createdAt: 1 },
  { expireAfterSeconds: 3600 } // 3600 seconds = 1 hour
)

Now when you insert a session:

db.sessions.insertOne({
  studentId: new ObjectId("64a1f2c3e4b0a1b2c3d4e5f6"),
  token: "abc123xyz",
  createdAt: new Date()
})

MongoDB automatically deletes this document 1 hour after createdAt.

Example — Delete password reset codes after 15 minutes

db.passwordResets.createIndex(
  { createdAt: 1 },
  { expireAfterSeconds: 900 } // 900 seconds = 15 minutes
)

Example — Delete at a specific time

If you want the document to expire at a specific time rather than after a duration, set expireAfterSeconds: 0 and store the exact expiry time in the date field:

db.notifications.createIndex(
  { expiresAt: 1 },
  { expireAfterSeconds: 0 }
)

// This document expires at exactly the time stored in expiresAt
db.notifications.insertOne({
  message: "Parent-teacher meeting tomorrow at 3pm",
  expiresAt: new Date("2024-09-15T15:00:00Z")
})

MongoDB's TTL cleanup runs every 60 seconds — so documents are not deleted at the exact second they expire. They are deleted within 60 seconds of expiry. Do not rely on TTL for precise second-level expiration.


Wildcard Index

A wildcard index indexes all fields in a document — or all fields within a sub-document — dynamically. It is useful when your documents have unpredictable or highly variable field names.

Create a wildcard index on all fields

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

Create a wildcard index on a specific sub-document

// Index all fields inside the 'metadata' object
db.collection.createIndex({ "metadata.$**": 1 })

Example — Variable student attributes

Imagine different students have completely different custom attributes:

// Student A
{ name: "Ali", attributes: { sport: "Cricket", club: "Science" } }

// Student B
{ name: "Sara", attributes: { instrument: "Piano", award: "Gold" } }

// Student C
{ name: "Umar", attributes: { language: "French", debate: true } }

Every student has different fields inside attributes. Instead of creating a separate index for every possible field, use a wildcard index:

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

Now any query on any field inside attributes can use this index:

db.students.find({ "attributes.sport": "Cricket" })
db.students.find({ "attributes.instrument": "Piano" })
db.students.find({ "attributes.language": "French" })

Wildcard indexes are powerful but expensive — they use more storage and memory than targeted indexes. Only use them when your field names are genuinely unpredictable. For most cases, a regular index on a known field is better.


School System Index Setup

Here is a complete, realistic index setup for our school system using all the types we covered:

// Text index — course search bar
db.courses.createIndex(
  { title: "text", description: "text" },
  { weights: { title: 10, description: 5 }, name: "courses_search" }
)

// Text index — student name search for admin
db.students.createIndex({ name: "text" })

// Sparse index — only some students have scholarships
db.students.createIndex(
  { scholarshipId: 1 },
  { sparse: true }
)

// Sparse index — only some students have phone numbers
db.students.createIndex(
  { phone: 1 },
  { sparse: true }
)

// Partial index — most queries are about enrolled students
db.students.createIndex(
  { grade: 1, name: 1 },
  { partialFilterExpression: { enrolled: true } }
)

// TTL index — auto-delete password reset tokens after 15 minutes
db.passwordResets.createIndex(
  { createdAt: 1 },
  { expireAfterSeconds: 900 }
)

// TTL index — auto-delete login sessions after 24 hours
db.sessions.createIndex(
  { createdAt: 1 },
  { expireAfterSeconds: 86400 }
)

Quick Reference

Index TypeSyntaxUse when
Text{ field: "text" }Full-text keyword search
Sparse{ field: 1 }, { sparse: true }Field is optional — only exists in some documents
Partial{ field: 1 }, { partialFilterExpression: {...} }You only query a subset of documents
TTL{ dateField: 1 }, { expireAfterSeconds: N }Auto-delete temporary data
Wildcard{ "$**": 1 }Dynamic or unpredictable field names

For most applications, you will use text indexes for search features and TTL indexes for temporary data like sessions and tokens. Sparse and partial indexes are great performance optimizations when only a subset of your documents are queried regularly. Wildcard indexes are rare — use them only when you genuinely have unpredictable fields.

On this page