skip to content

Index Types

The catalogue of index kinds and what each one can and cannot serve. Interviewers probe multikey indexes in particular, because arrays break the mental model people bring from SQL.

part ofMongoDBoverview, primer and where to startread it →
on this pageshow

questions

6

Which queries can the compound index { userId: 1, status: 1, createdAt: -1 } serve?

level: middleimportance: must knowfreq 78%

answer

  1. One index, one lexicographic ordering
  2. Usable from the left only
  3. Values of a later field are scattered across the index
  4. Field order in the query document is irrelevant
  5. Sorts need matching directions or the exact mirror

basics

~20 s

Only queries whose filter includes the leading field. The usable index prefixes are { userId }, { userId, status } and all three fields; a query on status or createdAt alone cannot use it. It also serves sorts matching the declared key directions or their exact inverse.

solid answer

~50 s

A compound index is a single ordered structure keyed on the concatenation of its fields, so it is usable only from the left. The prefixes here are `{ userId }`, `{ userId, status }`, and `{ userId, status, createdAt }` — a filter on `userId` alone, or `userId` plus `status`, is served; a filter on `status` alone or `createdAt` alone is not, because those values are scattered across the whole index. You can also skip a middle field only at a cost: `{ userId, createdAt }` uses the index for `userId` and then has to check `createdAt` less efficiently, since `createdAt` keys are only ordered within a `(userId, status)` group. For sorting, the index directions matter: it serves `sort({ userId: 1, status: 1, createdAt: -1 })` by walking forward and the exact inverse `sort({ userId: -1, status: -1, createdAt: 1 })` by walking backward, but not a mixed variant such as all-ascending.

code

javascript · 9 lines
javascript
db.orders.createIndex({ userId: 1, status: 1, createdAt: -1 })

// served: leading field present
db.orders.find({ userId: 42 })
db.orders.find({ userId: 42, status: "OPEN" }).sort({ createdAt: -1 })

// not served: no leading field
db.orders.find({ status: "OPEN" })
db.orders.find({ createdAt: { $gt: ISODate("2024-01-01") } })

go deeper

for a junior

Recall that a compound index is used from the left: the query must include the first field. Filtering only on a later field will not use it.

for a middle

Explain the single lexicographic ordering behind the prefix rule, list the usable prefixes for a given spec, and state the forward-or-exact-inverse rule for sorts.

for a senior

Show the design reasoning on a real query: equality fields first so the sorted field lands in one contiguous block, and identify redundant prefix indexes you would drop.

for a principal

Own index-set economics across a service: a small number of well-ordered compound indexes covering the real query shapes, with a rule for retiring prefixes rather than accumulating one index per report.

## What a compound index is `db.orders.createIndex({ userId: 1, status: 1, createdAt: -1 })` builds one index whose keys are ordered tuples: first by `userId`, then within equal `userId` by `status`, then within equal `(userId, status)` by `createdAt` descending. There is one index, not three. Everything about which queries it serves follows from that single ordering. ## The prefix rule Because the sort is lexicographic from the left, the keys for a given `status` value are spread all over the index — they sit inside every `userId` group. So the index is usable only when the query pins the leading field. The usable **index prefixes** are: - `{ userId: ... }` - `{ userId: ..., status: ... }` - `{ userId: ..., status: ..., createdAt: ... }` A query on `{ status: "OPEN" }` alone gets no useful seek from this index; the planner will normally choose a collection scan or a different index. A query on `{ createdAt: { $gt: ... } }` alone is in the same position. Note what the rule is *not* about: the order in which you write the fields in the query document is irrelevant. `{ status: "OPEN", userId: 42 }` and `{ userId: 42, status: "OPEN" }` are the same query and both use the index. What matters is which fields are present, not how they are typed. ## Skipping a middle field A filter on `userId` and `createdAt` but not `status` can still use the index, but only partially: MongoDB seeks on `userId`, and then `createdAt` cannot be used as a tight contiguous bound because within one `userId` the `createdAt` keys are grouped by `status` first. The index is still far better than a collection scan, but it is not as good as an index whose key order matches the query. ## Key order in design The order of the keys is the whole design decision. Put fields you match by equality before the field you sort or range-scan on, so that the sorted or ranged values sit in one contiguous run of the index. Given `find({ userId: 42, status: "OPEN" }).sort({ createdAt: -1 })`, this index is ideal: the two equality matches select one contiguous block, and within that block `createdAt` is already in descending order, so no separate sort step is needed. ## Direction and sorting Directions only ever matter relative to each other. This index can produce: - `sort({ userId: 1, status: 1, createdAt: -1 })` — walk the index forward. - `sort({ userId: -1, status: -1, createdAt: 1 })` — walk it backward; every key direction is flipped. It cannot produce `sort({ userId: 1, status: 1, createdAt: 1 })` from the index alone, because that ordering exists nowhere in the structure — no single traversal yields two fields forward and one backward. If a query pins the leading fields with equality, though, direction on those pinned fields stops mattering: with `userId` and `status` fixed to single values, a sort on `createdAt` alone can still be served, in either direction, by scanning that one contiguous block forward or backward. ## Consequences for the index set Because every prefix of a compound index is itself usable, an index on `{ userId: 1, status: 1 }` is redundant when `{ userId: 1, status: 1, createdAt: -1 }` exists — the wider index serves everything the narrower one does, at slightly larger key size. The reverse is not true: `{ status: 1, userId: 1 }` is a genuinely different index serving a different set of queries. Recognising the redundant-prefix case is how index sets get trimmed without losing coverage. ## Answering well List the three prefixes explicitly, say plainly that `status` alone gets nothing, then show the equality-before-sort design reasoning on a concrete query. Finish with the direction rule — forward or exactly inverted — because that is the part most candidates have never thought about.

  • Does writing the query as { status: "OPEN", userId: 42 } instead of { userId: 42, status: "OPEN" } change whether the index is used?
    No. The order of fields in the query document is meaningless to the planner; both are the same query. What matters is which fields the filter contains relative to the index's key order. Only the index's own key order determines which prefixes are usable.
  • With that index in place, is a separate index on { userId: 1, status: 1 } worth keeping?
    No, it is redundant. Every prefix of a compound index is usable on its own, so the three-field index already serves any query the two-field one would, at a slightly larger key. Dropping the narrower index removes write and storage cost without losing any query coverage.
  • Which sorts can that index satisfy without an in-memory sort step?
    sort({ userId: 1, status: 1, createdAt: -1 }) by walking forward, and its exact inverse sort({ userId: -1, status: -1, createdAt: 1 }) by walking backward. A mixed variant such as all three ascending cannot be produced by any single traversal. If userId and status are pinned by equality, a sort on createdAt alone works in either direction.

saying these in an interview costs you the question

  • Says a compound index works like three separate indexes
  • Thinks the field order in the query document matters
  • Claims any subset of the indexed fields can use the index
  • Believes any sort over the indexed fields is free
  • Keeps a two-field index that is a prefix of a three-field one

context

open as a page

What makes a MongoDB index multikey, and what does it store for an array field?

level: middleimportance: must knowfreq 76%

basics

~20 s

An index becomes multikey automatically the first time a document holds an array in an indexed field. MongoDB then stores one index key per array element, so a document with five tags contributes five entries pointing at that one document.

open as a page

Does the 1 or -1 direction matter when creating a single-field MongoDB index?

level: juniorimportance: should knowfreq 58%

basics

~20 s

No. For a single-field index MongoDB can walk the keys forward or backward, so createIndex({ createdAt: 1 }) and createIndex({ createdAt: -1 }) serve exactly the same queries and both sort directions. Direction only starts to matter in compound indexes.

open as a page

What can a MongoDB text index do, and what restriction applies per collection?

level: middleimportance: should knowfreq 40%

basics

~20 s

A text index tokenizes string fields into stemmed, case- and diacritic-insensitive terms so $text queries can match words in them. A collection may have at most one text index, though that single index can cover many fields.

open as a page

Why does MongoDB refuse to index two array fields in one compound index?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Because the index would have to store the cross product of both arrays: a document with 10 and 20 elements would generate 200 keys. MongoDB rejects such parallel arrays — the index build fails, or the offending write is rejected once the index exists.

open as a page

When is a MongoDB wildcard index the right choice over indexes on named fields?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

When field names are data rather than schema — user-defined attributes or unpredictable key sets — so you cannot enumerate them. A wildcard index indexes every path under a subtree, at the cost of size and weaker query support than a purpose-built index.

open as a page