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.
Multikey: indexing arrays
Section titled “Multikey: indexing arrays”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 })await db.collection("members").createIndex({ borrowed: 1 });db.members.create_index([("borrowed", 1)])_, err := coll.Indexes().CreateOne( ctx, mongo.IndexModel{Keys: bson.D{{Key: "borrowed", Value: 1}}},)if err != nil { return err}coll.create_index( IndexModel::builder().keys(doc! { "borrowed": 1 }).build(),).await?;A query like finding everyone who borrowed "Compilers" now seeks instead of scans, even though the value sits inside an array.
Text: searching words
Section titled “Text: searching words”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" } })await db.collection("members").createIndex({ notes: "text" });await db.collection("members") .find({ $text: { $search: "overdue reminder" } }) .toArray();db.members.create_index([("notes", "text")])list(db.members.find({"$text": {"$search": "overdue reminder"}}))_, err := coll.Indexes().CreateOne( ctx, mongo.IndexModel{Keys: bson.D{{Key: "notes", Value: "text"}}},)if err != nil { return err}cur, err := coll.Find(ctx, bson.M{"$text": bson.M{"$search": "overdue reminder"}})coll.create_index( IndexModel::builder().keys(doc! { "notes": "text" }).build(),).await?;let cur = coll.find(doc! { "$text": { "$search": "overdue reminder" } }).await?;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.
TTL: documents that expire
Section titled “TTL: documents that expire”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 })await db.collection("sessions").createIndex( { createdAt: 1 }, { expireAfterSeconds: 3600 });db.sessions.create_index([("createdAt", 1)], expireAfterSeconds=3600)_, err := coll.Indexes().CreateOne( ctx, mongo.IndexModel{ Keys: bson.D{{Key: "createdAt", Value: 1}}, Options: options.Index().SetExpireAfterSeconds(3600), },)if err != nil { return err}coll.create_index( IndexModel::builder() .keys(doc! { "createdAt": 1 }) .options( IndexOptions::builder() .expire_after(Duration::from_secs(3600)) .build(), ) .build(),).await?;A background task sweeps and removes expired documents. The sweep runs about once a minute, so deletion is prompt but not instant.
Partial: indexing a subset
Section titled “Partial: indexing a subset”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 } } })await db.collection("members").createIndex( { fines: 1 }, { partialFilterExpression: { fines: { $gt: 0 } } });db.members.create_index( [("fines", 1)], partialFilterExpression={"fines": {"$gt": 0}},)_, err := coll.Indexes().CreateOne( ctx, mongo.IndexModel{ Keys: bson.D{{Key: "fines", Value: 1}}, Options: options.Index().SetPartialFilterExpression( bson.M{"fines": bson.M{"$gt": 0}}, ), },)if err != nil { return err}coll.create_index( IndexModel::builder() .keys(doc! { "fines": 1 }) .options( IndexOptions::builder() .partial_filter_expression(doc! { "fines": { "$gt": 0 } }) .build(), ) .build(),).await?;Members with zero fines never enter the index, so it stays lean and serves the “who owes money” query without indexing everyone.
Unique and sparse
Section titled “Unique and sparse”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 })await db.collection("members").createIndex({ cardNumber: 1 }, { unique: true });db.members.create_index([("cardNumber", 1)], unique=True)_, err := coll.Indexes().CreateOne( ctx, mongo.IndexModel{ Keys: bson.D{{Key: "cardNumber", Value: 1}}, Options: options.Index().SetUnique(true), },)if err != nil { return err}coll.create_index( IndexModel::builder() .keys(doc! { "cardNumber": 1 }) .options(IndexOptions::builder().unique(true).build()) .build(),).await?;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 })await db.collection("members").createIndex( { cardNumber: 1 }, { unique: true, sparse: true });db.members.create_index([("cardNumber", 1)], unique=True, sparse=True)_, err := coll.Indexes().CreateOne( ctx, mongo.IndexModel{ Keys: bson.D{{Key: "cardNumber", Value: 1}}, Options: options.Index().SetUnique(true).SetSparse(true), },)if err != nil { return err}coll.create_index( IndexModel::builder() .keys(doc! { "cardNumber": 1 }) .options( IndexOptions::builder().unique(true).sparse(true).build(), ) .build(),).await?;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.
Tips and gotchas
Section titled “Tips and gotchas”- 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
nullvalue, so withoutsparseit allows only one document with the field absent. Addsparsewhen many documents will lack the field.