Photo by chansu shin on Unsplash

On this page

This chapter follows Mongoose with MongoDB. Indexes are not free: they speed reads, slow writes, and consume RAM. Create them to match filters, sort, and projection in your real queries.

Single-field and compound indexes

A single-field index on { email: 1 } supports equality and range on email. A compound index { status: 1, createdAt: -1 } supports queries that filter on status and sort on createdAt when the filter uses a prefix of the index keys.

The ESR rule

For compound indexes, a useful mnemonic is Equality → Sort → Range:

// Query: { status: 'open' }, sort { priority: -1 }, filter { createdAt: { $gte: date } }
db.tickets.createIndex({ status: 1, priority: -1, createdAt: -1 })

Put equality fields first, then sort field, then range field.

Multikey, unique, partial, TTL, wildcard

  • Multikey: indexes on array fields—one index entry per array element.
  • Unique: { email: 1 }, { unique: true } rejects duplicates (null counts as a value unless sparse).
  • Partial: index only documents matching a filter—smaller index for archived flags.
  • TTL: { expiresAt: 1 }, { expireAfterSeconds: 0 } deletes documents when date passes.
  • Wildcard: { 'metrics.$**': 1 } for dynamic metric keys (use sparingly).

explain() and covered queries

db.orders.find({ customerId: id, status: 'shipped' })
.sort({ createdAt: -1 })
.explain('executionStats')

Look at totalDocsExamined versus nReturned. A covered query satisfies the query entirely from the index (totalDocsExamined: 0 with projection of indexed fields only).

Create indexes from TypeScript

src/migrations/indexes.ts
import { getDb } from '../db/client.js';
export async function ensureTicketIndexes() {
const db = await getDb();
await db.collection('tickets').createIndexes([
{ key: { status: 1, createdAt: -1 }, name: 'status_createdAt' },
{ key: { assigneeId: 1 }, name: 'assigneeId', sparse: true },
]);
}

Build indexes in production with { background: true } where supported, or during maintenance windows on large collections.

Capped and time series collections

Capped collections have fixed size and FIFO behavior—useful for bounded logs:

db.createCollection('logs', { capped: true, size: 10485760, max: 5000 })

Time series collections optimize storage and queries for timestamped measurements:

db.createCollection('sensor', {
timeseries: { timeField: 't', metaField: 'sensorId', granularity: 'minutes' },
})

Next: MongoDB Aggregation Framework.

Check your understanding

Question 1 of 5

0 of 5 checked

  1. 1. What does the ESR mnemonic stand for in compound index design?

Search articles

Type a word to search published articles.

Open the search page