Skip to content

Index types

A plain ascending index covers most needs, but MongoDB ships several specialized index types for cases a plain index cannot handle gracefully: fields that hold arrays, full-text search, documents that should expire on their own, indexes that cover only a slice of a collection, and indexes that enforce uniqueness. Each is a small variation on createIndex, and knowing they exist saves you from awkward workarounds. We will take them one at a time, keeping our library members collection.

When you index a field that holds an array, MongoDB automatically makes it a multikey index — it indexes every element of the array separately, so a query for any one element seeks straight to the matching documents. You do not ask for multikey; it happens because the field’s values are arrays. Index the borrowed array of book titles:

db.members.createIndex({ borrowed: 1 })

A query like finding everyone who borrowed "Compilers" now seeks instead of scans, even though the value sits inside an array.

A text index tokenizes string fields so you can search for words with the $text operator instead of matching exact strings. Index the notes field of each member as text, then query it:

db.members.createIndex({ notes: "text" })
db.members.find({ $text: { $search: "overdue reminder" } })

The search matches either word and ranks results by relevance. A collection may hold only one text index, though it can span several string fields.

A TTL (time to live) index deletes documents automatically once a date field is older than a set number of seconds. It is built by indexing a date field with an expireAfterSeconds option. Here, member session records expire one hour after their createdAt timestamp:

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

A background task sweeps and removes expired documents. The sweep runs about once a minute, so deletion is prompt but not instant.

A partial index covers only the documents that match a filter expression, which keeps the index small when you query just one slice of a collection. Index only members who actually owe fines, by attaching a partialFilterExpression:

db.members.createIndex(
{ fines: 1 },
{ partialFilterExpression: { fines: { $gt: 0 } } }
)

Members with zero fines never enter the index, so it stays lean and serves the “who owes money” query without indexing everyone.

A unique index rejects any insert or update that would duplicate an indexed value — the way you guarantee, say, that no two members share a cardNumber:

db.members.createIndex({ cardNumber: 1 }, { unique: true })

A sparse index goes one step further by skipping documents that lack the indexed field entirely, rather than indexing them with a null. Pairing sparse with unique lets many documents omit cardNumber while still forbidding duplicates among those that have one:

db.members.createIndex({ cardNumber: 1 }, { unique: true, sparse: true })

In Compass: the Create Index dialog in the Indexes tab exposes these as options — you can tick Unique, set a TTL value, or supply a Partial Filter Expression without leaving the GUI.

  • Multikey indexes have a limit: a compound index can include at most one array field, because indexing two arrays together would multiply out into an explosion of index entries.
  • TTL deletion is approximate in timing. The background sweep runs roughly once a minute, so an expired document may linger for up to a minute past its deadline before it is removed.
  • A partial index serves a query only when the query is guaranteed to fall inside the index’s filter. A query that could match excluded documents cannot use the partial index, even for the part that overlaps.
  • A unique index counts a missing field as a null value, so without sparse it allows only one document with the field absent. Add sparse when many documents will lack the field.
When does MongoDB make an index multikey?
What does a TTL index do?
Why combine a unique index with sparse?