5.4 Specialized Index Types: TTL, Partial, Unique & Text
Key Takeaways
- TTL (Time-To-Live) indexes automatically delete documents after a specified duration using a background thread running every 60 seconds; they must be single-field Date indexes and cannot be compound or built on '_id'.
- Partial indexes index only documents matching a specified 'partialFilterExpression', drastically reducing index memory footprint and write overhead.
- Unique indexes reject duplicate key values; combining unique constraints with partialFilterExpression allows enforcing uniqueness on populated fields while ignoring missing or null fields.
- A collection can have at most ONE Text index, which supports full-text search, tokenization, language stemming, and relevance sorting via '{ score: { $meta: "textScore" } }'.
- 2dsphere geospatial indexes index GeoJSON geometry on an Earth-like sphere using [longitude, latitude] coordinate pairs, enabling proximity queries with $near and containment queries with $geoWithin.
Specialized Index Types: TTL, Partial, Unique & Text
Exam Focus: The MongoDB Certified Associate Developer Exam tests the exact configuration parameters, mechanics, and limitations of specialized index types: TTL index date requirements and 60-second background thread purge cycles, Partial Index
partialFilterExpressionquery matching rules, Unique Index null-handling and Partial Unique patterns, Text Index single-index limits and$text/textScoresyntax, and 2dsphere GeoJSON coordinate ordering ([longitude, latitude]).
1. Time-To-Live (TTL) Indexes
A TTL (Time-To-Live) Index is a specialized single-field index that automatically purges documents from a collection after a specified amount of time. TTL indexes are ideal for transient operational data such as user authentication sessions, temporary verification tokens, machine telemetry caches, and ephemeral event logs.
Syntax & Configuration
// Expire documents 3600 seconds (1 hour) after the 'created_at' timestamp
db.user_sessions.createIndex(
{ created_at: 1 },
{ expireAfterSeconds: 3600 }
);
Fixed-Interval Expiration vs. Dynamic Custom Expiration
- Fixed-Interval Expiration: As shown above, every document expires when
current_time >= created_at + expireAfterSeconds. - Dynamic Custom Expiration (Per-Document TTL): Set
expireAfterSeconds: 0and store the explicit future expiration date in each document's Date field:
// Create dynamic TTL index
db.coupons.createIndex({ expires_at: 1 }, { expireAfterSeconds: 0 });
// Document with custom future expiration date
db.coupons.insertOne({
code: "SPRING2026",
expires_at: ISODate("2026-09-15T00:00:00Z") // Purged after this exact instant
});
Critical TTL Rules & Limitations (High-Yield Exam Points)
- BSON Date Type Requirement: The indexed field must contain a BSON Date (
ISODate) or an array of BSON Dates. If the field contains a string, timestamp, integer, or null, the document will never be expired. - Single-Field Only: TTL indexes cannot be compound indexes.
- Forbidden on
_id: You cannot build a TTL index on the primary key_id. - 60-Second Background Thread: Document deletion is managed by an internal background thread that executes once every 60 seconds. Therefore, document expiration is not instantaneous; a document may remain visible for up to 60 seconds after its expiration threshold.
- Capped Collections Incompatibility: TTL indexes cannot be created on capped collections because capped collections prohibit individual document deletions.
- Modifying TTL Thresholds: You can modify
expireAfterSecondson an existing index using thecollModcommand without dropping and rebuilding the index:
db.runCommand({
collMod: "user_sessions",
index: {
keyPattern: { created_at: 1 },
expireAfterSeconds: 7200 // Extended to 2 hours
}
});
2. Partial Indexes
A Partial Index indexes only documents in a collection that satisfy a specified filter expression (partialFilterExpression). By omitting documents that are never queried, partial indexes reduce index size in RAM, minimize disk usage, and cut write maintenance overhead.
Syntax
// Index 'email' ONLY for documents where status is 'ACTIVE'
db.users.createIndex(
{ email: 1 },
{ partialFilterExpression: { status: "ACTIVE" } }
);
Query Utilization Rules for Partial Indexes
For MongoDB to use a partial index during query execution, the query filter must be a guaranteed subset of the partialFilterExpression:
// Uses the partial index (filter explicitly includes status: "ACTIVE"):
db.users.find({ email: "alex@example.com", status: "ACTIVE" });
// CANNOT use the partial index (filter does not guarantee status is "ACTIVE"):
db.users.find({ email: "alex@example.com" }); // Triggers COLLSCAN
Allowed Operators in partialFilterExpression
The partialFilterExpression supports: $eq, $exists: true, $gt, $gte, $lt, $lte, $type, and $and at the top level. Operators such as $expr, $where, $text, and $regex are not permitted.
3. Unique Indexes & The Partial Unique Pattern
A Unique Index guarantees that the indexed field does not store duplicate values across the collection, rejecting any write operation that violates uniqueness with an E11000 duplicate key error.
Syntax
db.accounts.createIndex({ username: 1 }, { unique: true });
The Missing/Null Field Problem in Unique Indexes
In MongoDB, if a document does not contain an indexed field, MongoDB stores null as the index entry. Consequently, in a standard unique index, you can insert at most ONE document that lacks the indexed field. A second insert with a missing or null field throws an E11000 duplicate key error on { field: null }!
The Solution: Partial Unique Indexes (Modern Best Practice)
To enforce uniqueness on a field when present while allowing multiple documents to omit the field, combine unique: true with a partialFilterExpression:
// Allow multiple users without phone numbers, but enforce uniqueness for all existing phones
db.users.createIndex(
{ phone_number: 1 },
{
unique: true,
partialFilterExpression: { phone_number: { $type: "string" } }
}
);
[!TIP] Partial Unique vs Sparse Indexes: While legacy MongoDB supported
sparse: trueindexes, Partial Unique indexes are strictly preferred becausesparseindexes only check field existence ($exists), whereaspartialFilterExpressionallows fine-grained type and value constraints.
4. Text Indexes & Full-Text Search
MongoDB provides Text Indexes to support text search queries on string content, featuring natural language tokenization, stemming (e.g., matching "running" to "run"), and case-insensitive matching.
Key Rules & Syntax
// Create a text index on multiple string fields
db.articles.createIndex(
{ title: "text", body: "text" },
{ weights: { title: 10, body: 1 }, name: "idx_article_text_search" }
);
- Single Text Index Limit: A collection can have at most ONE text index. However, a single text index can compound multiple fields (compound text index) or index all string fields using wildcard syntax (
{ "$**": "text" }). - Executing Queries with
$textand$search:
// Search for articles containing 'mongodb' AND 'performance', but NOT 'legacy'
db.articles.find({
$text: { $search: "mongodb performance -legacy" }
});
- Relevance Sorting via
textScore: MongoDB computes a numerical relevance score for each matching document. You can project and sort by this score using$meta: "textScore":
db.articles.find(
{ $text: { $search: "wiredtiger optimization" } },
{ score: { $meta: "textScore" } }
).sort({
score: { $meta: "textScore" }
});
5. Geospatial 2dsphere Indexes
A 2dsphere Index supports queries that calculate geometries on an Earth-like sphere using WGS84 coordinates.
Coordinate Order: [ Longitude, Latitude ]
[!WARNING] Critical Exam Rule: GeoJSON coordinate arrays in MongoDB strictly follow the order:
[ Longitude, Latitude ](X before Y). Reversing the coordinates ([ Lat, Long ]) will store invalid coordinates or cause queries to fail.
// Document containing GeoJSON Point
db.places.insertOne({
name: "Central Park",
location: {
type: "Point",
coordinates: [ -73.9654, 40.7829 ] // [ Longitude, Latitude ]
}
});
// Create 2dsphere index
db.places.createIndex({ location: "2dsphere" });
// Proximity search: Find places within 5,000 meters
db.places.find({
location: {
$near: {
$geometry: { type: "Point", coordinates: [ -73.9654, 40.7829 ] },
$maxDistance: 5000
}
}
});
Specialized Index Comparison Matrix
| Index Type | Primary Use Case | Key Configuration Option | Critical Restrictions |
|---|---|---|---|
| TTL | Auto-purging transient data | expireAfterSeconds: <N> | Single Date field only; 60s purge loop; no capped/_id |
| Partial | Indexing selective subsets | partialFilterExpression: <query> | Queries must guarantee filter subset to utilize index |
| Unique | Enforcing uniqueness | unique: true | Multiple nulls conflict without partial filter |
| Text | Full-text tokenized search | { field: "text" } | Maximum 1 text index per collection |
| 2dsphere | Geospatial spherical search | { field: "2dsphere" } | Strict [ Longitude, Latitude ] coordinate ordering |
An architect creates a TTL index on db.tokens.createIndex({ expiration_time: 1 }, { expireAfterSeconds: 0 }). An application inserts a document with { token: 'xyz', expiration_time: '2026-09-02T12:00:00Z' } where expiration_time is stored as a UTF-8 String. What will happen to this document?
A collection has a partial index defined with db.orders.createIndex({ order_number: 1 }, { partialFilterExpression: { status: 'COMPLETE' } }). Which of the following queries will be able to utilize this partial index?
What is the maximum number of Text indexes that can be created on a single MongoDB collection?
When storing geospatial GeoJSON Point data for use with a 2dsphere index in MongoDB, what is the mandatory ordering of coordinates inside the coordinates array?