skip to content

What does the filter { "items.price": { $gt: 100 } } match when items is an array of subdocuments?

level: middleimportance: should knowfreq 58%

answer

  1. A dot walks into nested structure
  2. Crossing an array fans out to every element
  3. Any element satisfying it is enough
  4. Numbers in a path address a position
  5. Whole-document equality is not the same as a dotted path

basics

~20 s

It matches any document where at least one element of the items array has a price greater than 100. A dotted path traverses embedded documents and, when it crosses an array, applies the rest of the path to every element.

solid answer

~50 s

A dotted path names a route through nested structure, and it must be quoted because of the dot. `"items.price"` first resolves `items`; if that value is an embedded document, the path continues into its `price` field, and if it is an **array**, the remainder of the path is applied to each element and the predicate succeeds if any element satisfies it. So `{ "items.price": { $gt: 100 } }` matches a document with a single expensive item and one with fifty items of which one is expensive. A numeric path component is positional: `"items.0.price"` tests only the first element, and `"items.0"` matches the whole first element. That numeric component is ambiguous — MongoDB will also match a literal field named `"0"` inside an embedded document, which is one reason numeric keys are a bad idea. Remember that two separate dotted predicates need not be satisfied by the same element; that is what `$elemMatch` is for.

code

javascript · 10 lines
javascript
db.orders.insertOne({
  _id: 1,
  address: { city: "Berlin", zip: "10115" },
  items: [ { sku: "A", price: 200 }, { sku: "B", price: 5 } ]
})

db.orders.find({ "address.city": "Berlin" })      // matches
db.orders.find({ address: { city: "Berlin" } })   // no match: exact-shape equality
db.orders.find({ "items.price": { $gt: 100 } })   // matches via element A
db.orders.find({ "items.0.sku": "A" })            // matches the first element only

go deeper

for a junior

Know that a quoted dotted key reaches into nested documents, and that when the path crosses an array the condition is checked against every element, matching if any one of them qualifies.

for a middle

Explain the fan-out rule precisely, contrast a dotted path with whole-embedded-document equality, and describe what a numeric path component does and why it is ambiguous.

for a senior

Demonstrate the operational consequences: multi-condition dotted filters that silently match across different elements, negative predicates that match missing paths, and the multikey restriction that limits which dotted paths can share a compound index.

for a principal

Own the nesting policy — how deep arrays of subdocuments may go, given that each array path a query needs makes indexing more constrained, and how that shapes the read patterns the model can serve.

## What a dotted path means MongoDB stores nested structure, and a query reaches into it with a dotted path written as a quoted key: `{ "address.city": "Berlin" }`. The quoting is a syntax requirement of the shell and of JSON generally — a dot is not legal in a bare key. The path is resolved left to right against each candidate document. For plain embedded documents this is unsurprising: ``` { _id: 1, address: { city: "Berlin", zip: "10115" } } ``` `{ "address.city": "Berlin" }` matches. Note the contrast with `{ address: { city: "Berlin" } }`, which is whole-document equality: it requires `address` to be exactly that document, with exactly those fields in that order, so the example above would **not** match because `zip` is also present. Dotted paths are what you almost always want; bare embedded-document literals are exact-shape comparisons. ## Crossing an array The interesting rule is what happens when a path component lands on an array. The engine applies the remainder of the path to every element and succeeds if any element satisfies the predicate. Given: ``` { _id: 1, items: [ { sku: "A", price: 200 }, { sku: "B", price: 5 } ] } { _id: 2, items: [ { sku: "C", price: 50 } ] } ``` `{ "items.price": { $gt: 100 } }` matches document 1 (element A qualifies) and not document 2. The predicate never tells you *which* element matched, and it does not restrict the returned document — the whole document comes back, all elements included. Shaping the output is a projection concern, not a filter concern. This implicit fan-out works at any depth and across more than one array level, so `"orders.items.sku"` resolves through an array of orders, each containing an array of items. ## Positional components A numeric path component addresses a specific index. `{ "items.0.price": { $gt: 100 } }` tests only the first element. `{ "items.0": { sku: "A", price: 200 } }` compares the whole first element for equality. This is how you write "the newest entry" when the array is maintained in a known order, and it is the basis of the length trick `{ "tags.2": { $exists: true } }`, which is true only when a third element exists. The ambiguity is real: if an embedded document literally has a field named `"0"`, the same path matches that field too. MongoDB cannot distinguish "array index 0" from "key named 0" in a dotted path, so avoid numeric keys in document fields entirely. ## Independent predicates, again The most consequential interaction is with multiple conditions. `{ "items.price": { $gt: 100 }, "items.qty": { $lt: 5 } }` is a conjunction of two independent predicates, each free to find its own witness element. A document whose first item is expensive and whose second item is nearly out of stock satisfies it. If the intent is "one item that is both", the filter must be `{ items: { $elemMatch: { price: { $gt: 100 }, qty: { $lt: 5 } } } }`. Reviewing dotted-path filters for this is worth doing routinely, because the wrong form usually passes tests built on single-element arrays. ## Missing intermediate levels If any component of the path is absent, the path simply does not resolve and the document does not match a positive predicate. A negative predicate behaves differently: `{ "address.city": { $ne: "Berlin" } }` matches documents with no `address` at all, because "no value" is not equal to "Berlin". Combine with `$exists` when you mean "has an address, in a different city". ## Indexing You index a dotted path exactly as written: `createIndex({ "items.price": 1 })`. Because the path crosses an array, that becomes a multikey index with one entry per element, which is what lets element-level predicates be answered from the index. A single compound index cannot be multikey on two different array paths, so indexing `"items.price"` and `"orders.total"` together is only possible when at most one of them traverses an array — a constraint that should influence how deeply you nest arrays in the first place. ## Practical guidance Prefer dotted paths to embedded-document literals unless you truly want exact-shape equality. Quote every dotted key. Avoid numeric field names. And every time a dotted path crosses an array and the filter has more than one condition on that array, decide explicitly whether the conditions must share an element.

  • How does { address: { city: "Berlin" } } differ from { "address.city": "Berlin" }?
    The first is whole-document equality: address must be exactly the document { city: "Berlin" } — same fields, same values, same order — so a document that also stores a zip field will not match. The second walks into address and tests only the city field, ignoring whatever else the embedded document holds. Dotted paths are the form you almost always want; embedded-document literals are for exact-shape comparisons.
  • What does the filter { "items.0.sku": "A" } match?
    Documents whose items array has "A" as the sku of its first element, and also documents where items is an embedded document containing a field literally named "0" whose sku is "A". MongoDB cannot tell an array index from a numeric key in a dotted path, which is why numeric field names should be avoided. Positional paths are useful when the array has a maintained order, such as newest-first.
  • Why does { "address.city": { $ne: "Berlin" } } return documents with no address field?
    A missing path resolves to no value, and no value is not equal to "Berlin", so the negative predicate is satisfied. The same holds for $nin and $not. If you mean "has an address, in a different city", write { "address.city": { $exists: true, $ne: "Berlin" } } so the presence requirement is explicit.

saying these in an interview costs you the question

  • Thinks a dotted path only works on embedded documents, not arrays
  • Believes a matching dotted path filters the returned array elements
  • Uses { address: { city: 'x' } } expecting a partial match
  • Assumes two dotted predicates must hit the same array element
  • Forgets to quote the dotted key

context