Blog

Create Index | MongoDB Indexing Tutorial

Key Takeaways

  • Indexes can reduce query work, but they consume storage and cache space and add write-maintenance overhead.
  • Choose indexes for complete query patterns: filters, selectivity, sort order, and returned fields—not just frequently queried field names.
  • Compound-index field order matters. Array fields become multikey only when you explicitly index them.
  • Verify the winning plan with explain("executionStats"), and validate production impact before building or removing indexes.

To create an index in MongoDB, call db.collection.createIndex() with the fields and index types you need. For example, db.users.createIndex({ age: 1 }) creates an ascending index on age; whether it improves a query depends on how much work that index eliminates.

Version context: this refresh uses the MongoDB 9.0 documentation and mongosh 2.13.0 release notes. The examples are mongosh commands, not language-driver APIs or Atlas UI instructions. Use them to explore index behavior, not as performance benchmarks.

Why Use Indexes in MongoDB?

Without a suitable index, MongoDB may scan a collection to find matching documents. Ordinary ascending and descending indexes use B-tree structures: they store indexed keys and references to documents in an order that supports lookups, ranges, and sorting. Specialized MongoDB index types, such as text and hashed indexes, have different key-generation and query-support rules.

The trade-off is maintenance. Inserts and deletes maintain the relevant index entries, while updates maintain indexes whose keys change. Index pages also share memory resources with your working data, so more indexes aren't automatically better.

Create a Single Field Index

Consider a users collection and the query { age: { $lt: 30 } }. An age index may help when relatively few users match. If most users are under 30, fetching their documents through an index may offer little benefit—or perform worse than scanning. That's a question to measure, not a guaranteed speedup.

Start with a fresh, disposable database named stackchief_index_tutorial. Run the examples in order, once; the synthetic ages deliberately make the initial query selective.

db = db.getSiblingDB("stackchief_index_tutorial");

db.users.insertMany(
  Array.from({ length: 10000 }, (_, i) => ({
    email: i === 0 ? "alex@gmail.com" : `user${i}@example.com`,
    name: i === 0 ? "Sam" : `User ${i}`,
    age: i % 100 === 0 ? 22 : 40 + (i % 50),
    status: i % 2 === 0 ? "active" : "inactive"
  }))
);

// Inspect the query before creating the age index.
db.users.find({ age: { $lt: 30 } }).explain("executionStats");

// 1 means ascending; name makes later inspection/removal explicit.
db.users.createIndex({ age: 1 }, { name: "age_1" });

// Inspect the same query after index creation.
db.users.find({ age: { $lt: 30 } }).explain("executionStats");

A single-field ascending index can also serve a descending sort by scanning backward. You don't need separate ascending and descending indexes just to support both sort directions on that field.

Check Whether MongoDB Uses the Index

Read the winning plan, not just the list of available indexes. In MongoDB's explain-plan walkthrough, COLLSCAN means a collection scan, IXSCAN means an index scan, and FETCH retrieves documents. Other plans can use optimized stages such as EXPRESS_IXSCAN; output structure varies by version and execution engine.

Collection scan: collection documents -> filter -> results
Indexed lookup:  index keys -> FETCH matching documents -> results
                 IXSCAN       FETCH
  • nReturned: documents returned by the plan.
  • totalKeysExamined: index entries examined.
  • totalDocsExamined: document examinations performed.
  • executionTimeMillis: server-side plan-selection and execution time, excluding transmission of results to the client.

Compare examined work against returned results. An index scan that examines many irrelevant keys isn't necessarily efficient. Treat a single timing cautiously: explain() ignores existing plan-cache entries, and its timing need not represent normal application latency. For workload-level analysis, see our MongoDB index performance guide.

Choose Indexes from Real Query Patterns

  1. Write down the complete query: equality filters, ranges, sort, projection, and limit.
  2. Estimate selectivity: how small a portion of the collection does it match?
  3. Propose an index that reduces scanning or supplies the required sort.
  4. Compare execution plans on representative data, then measure application latency and write throughput under load.
  5. Keep the index only if its benefit justifies its storage and maintenance costs.

Don't assume index intersection—combining separate indexes—will replace a purpose-built compound index for an important query. Check the actual winning plan rather than designing around an assumed combination.

Create a Compound Index

A compound index contains multiple fields, ordered first by the leading field and then by subsequent fields within equal leading values. This ordering determines which queries have efficient access paths.

db.users.createIndex(
  { email: 1, name: -1 },
  { name: "email_name" }
);

db.users.find({ email: "alex@gmail.com", name: "Sam" });
db.users.find({ email: "alex@gmail.com" });

// No efficient leading-prefix lookup through email_name alone:
db.users.find({ name: "Sam" });

An index prefix is a beginning portion of its field list. The index above supports the email prefix and the complete email, name key pattern. A name-only query doesn't have that leading-prefix lookup. The textual order of equality predicates in find() doesn't need to match the index.

Index: email ASC -> name DESC
       [email]                  leading prefix
       [email, name]            complete key pattern
              [name]            not a leading prefix

For a sort on both fields, this index supports { email: 1, name: -1 } and its complete reverse, { email: -1, name: 1 }. It doesn't supply { email: 1, name: 1 } as the index order.

Apply the Equality-Sort-Range Guideline

Start with equality fields, then sort fields, then range fields—ESR. This favors avoiding a separate sort. If a range is highly selective, equality-range-sort can be better despite requiring a sort; compare both against your workload.

db.users.createIndex(
  { status: 1, name: 1, age: 1 },
  { name: "status_name_age" }
);

db.users.find({
  status: "active",     // Equality
  age: { $lt: 30 }      // Range
}).sort({ name: 1 })    // Sort
  .explain("executionStats");

Here, equality on status lets the index supply name order while evaluating the age range. It doesn't guarantee a small key scan. Our compound-index tutorial explores field ordering further.

Create a Multikey Index

MongoDB doesn't create an index merely because a field contains an array. When you create an index on array values, MongoDB makes it a multikey index. That also applies to an indexed path inside an array of embedded documents.

db.documents.insertOne({
  _id: ObjectId("62577886b29616254381f6b9"),
  name: "Vincent",
  bio: "Database developer",
  altInfo: [
    { otherEmail: "foo@example.com" },
    { otherEmail: "bar@example.com" }
  ]
});

db.documents.createIndex({ "altInfo.otherEmail": 1 });
db.documents.find({ "altInfo.otherEmail": "foo@example.com" });

Understand Compound Multikey Restrictions

A compound multikey index cannot index two independent array-valued fields in the same document. For an index on { tags: 1, scores: 1 }, { tags: ["db"], scores: 10 } is valid, but { tags: ["db"], scores: [10] } violates the restriction. Different documents may have arrays in different indexed fields, provided no individual document violates it; existing data can prevent index creation, and later violating writes fail.

Create a Text Index

A collection can have one text index, but that index can include multiple string fields. Use $text to query it.

db.documents.createIndex(
  { name: "text", bio: "text" },
  { name: "name_bio_text", default_language: "english" }
);

db.documents.find({ $text: { $search: "Vincent" } });

Language settings affect tokenization, stemming, and stop words; English is the default. $text is word-oriented search, not general substring matching, and a text index doesn't accelerate ordinary sort operations.

On Atlas, consider MongoDB Search for richer features such as autocomplete, analyzers, and faceting. Its search indexes are separate from the collection text index shown here.

Create a Wildcard Index

Wildcard indexes suit dynamic schemas where field names vary or aren't known ahead of time. Prefer explicit indexes when your important query fields are predictable.

db.documents.createIndex({ "altInfo.$**": 1 });
db.documents.find({ "altInfo.otherEmail": "foo@example.com" });

This targets subfields beneath altInfo. Alternatively, a collection-wide wildcard pattern can use wildcardProjection to restrict its coverage:

// Alternative example on a separate collection.
db.products.createIndex(
  { "$**": 1 },
  { wildcardProjection: { attributes: 1 } }
);
db.products.find({ "attributes.color": "blue" });

Don't add wildcardProjection to the path-targeted altInfo.$** definition. A wildcard index isn't a replacement for arbitrary compound indexes: when its wildcard portion could support multiple query fields, it supports only one of those fields. Broader coverage also means more entries to store and maintain.

Other Index Types

Support Geospatial Queries with 2dsphere or 2d

Use 2dsphere for spherical geometry, typically Earth locations represented as GeoJSON. It also supports legacy coordinate pairs. A 2d index is for flat, two-dimensional geometry using legacy coordinate pairs; don't treat it as interchangeable with spherical indexing.

db.documents.insertOne({
  name: "Example cafe",
  location: {
    type: "Point",
    coordinates: [-73.9667, 40.78]
  }
});

db.documents.createIndex({ location: "2dsphere" });
db.documents.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [-73.9667, 40.78]
      },
      $maxDistance: 1000
    }
  }
});

GeoJSON coordinates are longitude first, then latitude. With GeoJSON, $maxDistance is measured in meters, and $near returns nearest results first. See the official $near reference for query restrictions.

Create a Hashed Index

db.accounts.insertOne({ accountId: "acct-123" });
db.accounts.createIndex({ accountId: "hashed" });
db.accounts.find({ accountId: "acct-123" });

Hashed indexes store hashes of field values and support equality lookups. They're commonly used for hashed shard keys to distribute data and writes, not as a general query-performance upgrade. The hashed field doesn't support range lookups through that index, and hashed indexes cannot be unique or multikey. Creating the index alone doesn't shard the collection.

Distinguish Index Types from Index Options

Options modify an index's constraints, coverage, or behavior. They aren't interchangeable index types, and not every combination is valid.

OptionPurposeImportant caveat
unique: trueReject duplicate indexed keys across documents.Existing duplicates can prevent creation.
partialFilterExpressionIndex only documents matching a filter.A query must imply that filter to use the index without omitting results.
sparse: trueFor a single-field index, omit documents missing that field.Explicit null values are still indexed; partial indexes offer more control.
expireAfterSecondsConfigure document expiration with a TTL index.TTL indexes are single-field, require date values for expiration, and don't delete immediately at the deadline.
collationSet language-aware string comparison rules.Query and index collations must match for indexed string comparisons.
hidden: trueExclude an index from query-plan selection.The index is still maintained.

The official references cover partial-index eligibility, sparse-index behavior, and TTL expiration. For uniqueness examples and edge cases, read our MongoDB unique-index tutorial.

List, Hide, and Drop Indexes

In mongosh, getIndexes() returns index definitions. The database command listIndexes returns a cursor; don't confuse it with a shell collection method named listIndexes(). Driver APIs may expose that method name. Our list-indexes tutorial explains inspection in more detail.

db.users.getIndexes();
db.runCommand({ listIndexes: "users" });

// Test removal impact before dropping an index.
db.users.hideIndex("age_1");
// Observe representative queries; restore it if they regress.
db.users.unhideIndex("age_1");

// Tutorial cleanup only; validate production removal separately.
db.users.dropIndex("age_1");

Hiding lets you test query behavior without rebuilding the index if you need it back. Dropping removes the named index, not every index; the default _id index cannot be dropped. dropIndex() takes an exclusive collection lock, so removal also deserves an operational plan.

Production Checklist

  • Budget for builds. Builds consume resources and take exclusive collection locks at their start and end. Replica-set members also participate in index builds; monitor replication lag, disk space, and application latency.
  • Skip obsolete build advice. MongoDB ignores the background index-build option.
  • Review overlap carefully. A compound index may make a prefix index redundant, but compare uniqueness, filters, collation, and workload needs before removing it.
  • Observe a full workload cycle. Include infrequent reports and scheduled jobs before declaring an index unused.

Follow the index-build documentation rather than treating index creation as a free metadata change.

Conclusion

A useful index eliminates enough query work to justify its storage, cache, and write-maintenance costs. Start with real query patterns, verify candidates with execution plans, and measure the workload after deployment. Keep the indexes that earn their place—not every index you can create.

FAQ

How do I create an index in MongoDB?

In mongosh, run db.users.createIndex({ age: 1 }) to create an ascending age index. Add an options document when you need a name, uniqueness, or a partial filter.

How can I tell whether MongoDB is using an index?

Run the query with explain("executionStats") and inspect its winning plan for an index scan, commonly IXSCAN. Compare totalKeysExamined and totalDocsExamined with nReturned; index use alone doesn't prove efficiency.

Does MongoDB automatically index array fields?

No. You must create an index on the array field or nested path; MongoDB then stores it as a multikey index when the indexed values involve arrays.

How do I drop an index safely?

Inspect its name with getIndexes(), consider hiding it while observing a representative workload, then use dropIndex() with that name. Check constraint and operational implications first; the default _id index cannot be dropped.

Do MongoDB indexes slow down writes?

Indexes add write-maintenance work: inserts and deletes maintain relevant entries, and updates maintain indexes affected by changed keys. Measure that overhead against the read benefit, and remove genuinely unnecessary indexes.

Further Reading

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. Author of Mastering the Tech Interview. More about me