Index Strategies

Single & Compound Indexes

// Single field index
db.users.createIndex({ email: 1 });           // ascending
db.users.createIndex({ createdAt: -1 });      // descending

// Unique index
db.users.createIndex({ email: 1 }, { unique: true });

// Compound index (field order matters for queries)
db.orders.createIndex({ userId: 1, createdAt: -1 });
// Supports: { userId }  and  { userId, createdAt }
// Does NOT support: { createdAt } alone

// Covered index: include all projected fields
db.products.createIndex({ category: 1, price: 1, name: 1 });
// Query: db.products.find({ category: "tech" }, { price: 1, name: 1, _id: 0 })
// Result: IXSCAN only — no FETCH stage needed

// List indexes
db.users.getIndexes();

// Drop index
db.users.dropIndex({ email: 1 });
db.users.dropIndex("email_1");

Text Indexes

// Create text index on one or more fields
db.articles.createIndex({ title: "text", body: "text" });

// Weighted text index
db.articles.createIndex(
  { title: "text", body: "text" },
  { weights: { title: 10, body: 1 }, name: "article_text_idx" }
);

// Full-text search query
db.articles.find(
  { $text: { $search: "mongodb performance" } },
  { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } });

// Phrase search (wrap in quotes)
db.articles.find({ $text: { $search: "\"query planner\"" } });

// Negate a word
db.articles.find({ $text: { $search: "mongodb -slow" } });

// Only one text index per collection allowed

TTL & Partial Indexes

// TTL index: auto-delete documents after N seconds
db.sessions.createIndex(
  { expiresAt: 1 },
  { expireAfterSeconds: 0 }  // delete when expiresAt is reached
);

// Or set a fixed TTL from document creation
db.logs.createIndex(
  { createdAt: 1 },
  { expireAfterSeconds: 86400 }  // 24 hours
);

// Partial index: only index documents matching a filter
db.orders.createIndex(
  { userId: 1 },
  { partialFilterExpression: { status: "active" } }
);

// Sparse index: skip documents where field is null/missing
db.users.createIndex({ phone: 1 }, { sparse: true });

// Partial index saves space and speeds writes when only
// a subset of documents needs indexing

2dsphere & Geospatial

// 2dsphere index for GeoJSON geometry
db.places.createIndex({ location: "2dsphere" });

// Insert GeoJSON point
db.places.insertOne({
  name: "Coffee Shop",
  location: { type: "Point", coordinates: [116.4, 39.9] }
});

// Find places within 1km radius
db.places.find({
  location: {
    $near: {
      $geometry: { type: "Point", coordinates: [116.4, 39.9] },
      $maxDistance: 1000   // meters
    }
  }
});

// $geoWithin a polygon
db.places.find({
  location: {
    $geoWithin: {
      $geometry: {
        type: "Polygon",
        coordinates: [[[116.3,39.8],[116.5,39.8],[116.5,40.0],[116.3,40.0],[116.3,39.8]]]
      }
    }
  }
});

explain() Analysis

// Run explain to inspect query plan
db.orders.find({ userId: 42 }).explain("executionStats");

// Key fields to check:
// winningPlan.stage:
//   COLLSCAN = full collection scan (bad without index)
//   IXSCAN   = index scan (good)
//   FETCH    = fetch document from disk after IXSCAN
//   COVERED  = served entirely from index (best)

// executionStats:
//   nReturned:          documents returned
//   totalKeysExamined:  index keys scanned
//   totalDocsExamined:  documents fetched from disk
// Ideal: nReturned ≈ totalKeysExamined, totalDocsExamined ≈ 0

// Force an index hint
db.orders.find({ userId: 42 }).hint({ userId: 1 }).explain();

// Background index build (non-blocking)
db.large_collection.createIndex({ field: 1 }, { background: true });