← back

Array Query
Operators

FILE  11_array_operators
TOPIC  $elemMatch · $all · $size · Plain vs elemMatch Gotcha
LEVEL  Foundation
01
Array Query Basics
How MongoDB queries array fields
foundations

MongoDB treats array fields specially during queries. A query on an array field will match documents where at least one element satisfies the condition — you don't need any special syntax to query arrays.

Implicit Array Matching

// Sample documents
{ _id: 1, tags: ["mongodb", "nosql", "database"] }
{ _id: 2, tags: ["mysql", "sql", "database"] }
{ _id: 3, tags: ["mongodb", "aggregation"] }

// Find documents where tags contains "mongodb"
db.articles.find({ tags: "mongodb" })
// Returns docs 1 and 3 — no $in or special syntax needed
// MongoDB automatically checks if any element equals "mongodb"

// Find by exact full array match (order matters!)
db.articles.find({ tags: ["mongodb", "nosql", "database"] })
// Returns only doc 1 — exact array with that exact order
NOTE
There are two very different array query behaviors: querying for a value in the array (element match) vs querying for an exact array. Querying { tags: "mongodb" } checks membership; querying { tags: ["mongodb"] } checks for exact array equality including order.

Dot Notation on Array of Objects

// Documents with embedded arrays of objects
{ _id: 1, scores: [ { subject: "Math", val: 85 }, { subject: "Sci", val: 72 } ] }
{ _id: 2, scores: [ { subject: "Math", val: 40 }, { subject: "Sci", val: 90 } ] }

// Find docs where ANY score object has subject "Math"
db.students.find({ "scores.subject": "Math" })
// Returns both docs 1 and 2

// Find docs where ANY score has val > 80
db.students.find({ "scores.val": { $gt: 80 } })
// Returns doc 1 (85) and doc 2 (90) — checks across ALL elements
WARN
Dot notation on array-of-objects checks each field independently across all elements. { "scores.subject": "Math", "scores.val": { $gt: 80 } } matches ANY doc where some element has subject "Math" AND any element (possibly a different one) has val > 80. This is the classic gotcha — use $elemMatch to enforce same-element constraints.
02
$elemMatch
All conditions must be satisfied by the SAME element
same-element

$elemMatch matches documents where at least one array element satisfies all the specified criteria simultaneously. It is the solution to the multi-condition same-element problem.

Syntax

{ arrayField: { $elemMatch: { condition1, condition2, ... } } }

$elemMatch on Array of Objects

// Find students with a Math score GREATER THAN 80
// (both conditions must hold on the SAME score object)
db.students.find({
  scores: {
    $elemMatch: { subject: "Math", val: { $gt: 80 } }
  }
})
// Only returns docs where ONE element has subject:"Math" AND val > 80 simultaneously

// Compare: plain dot-notation is misleading
db.students.find({ "scores.subject": "Math", "scores.val": { $gt: 80 } })
// Returns docs where SOME element has subject:"Math"
// AND SOME element (possibly different!) has val > 80
// This can return false positives!

$elemMatch on Array of Primitives

// Sample: { _id: 1, ratings: [70, 45, 90, 30] }

// Find docs with at least one rating between 40 and 60 (same element)
db.col.find({ ratings: { $elemMatch: { $gte: 40, $lte: 60 } } })
// Returns doc 1 because ratings has 45 (satisfies both $gte:40 AND $lte:60)
// Without $elemMatch: { ratings: { $gte:40, $lte:60 } } has DIFFERENT semantics

// Plain range on a primitive array — subtle difference
db.col.find({ ratings: { $gte: 40, $lte: 60 } })
// Matches docs where SOME element >= 40 AND SOME element (possibly different) <= 60
// { ratings: [10, 90] } would match because 90 >= 40 and 10 <= 60!
// { ratings: [10, 90] } would NOT match $elemMatch because no single element qualifies
CRITICAL
This is the most misunderstood array query behavior in MongoDB. Without $elemMatch, multiple conditions on an array field are checked across elements independently. { arr: { $gte: 40, $lte: 60 } } can match [10, 90] because 90 ≥ 40 and 10 ≤ 60. Use $elemMatch whenever you need a single element to satisfy multiple conditions simultaneously.

$elemMatch in Projection

// In a projection, $elemMatch returns only the FIRST matching element
db.students.find(
  { "scores.subject": "Math" },
  { scores: { $elemMatch: { subject: "Math" } } }
)
// Output includes only the first scores element where subject === "Math"
// Other elements are excluded from the projection output
// _id is always returned unless explicitly excluded
03
Plain vs $elemMatch Gotcha
The most common array query mistake
gotcha

Understanding this distinction is critical for writing correct array queries.

Side-by-Side Comparison

// Test dataset
db.students.insertMany([
  { name: "Alice", scores: [ { subject: "Math", val: 85 }, { subject: "Sci", val: 40 } ] },
  { name: "Bob",   scores: [ { subject: "Math", val: 35 }, { subject: "Sci", val: 95 } ] },
  { name: "Carol", scores: [ { subject: "Math", val: 92 }, { subject: "Sci", val: 88 } ] }
])

Plain Query

db.students.find({
  "scores.subject": "Math",
  "scores.val": { $gt: 80 }
})

Returns Alice, Bob, Carol — Bob is included because his Sci score of 95 satisfies $gt: 80, even though his Math score is only 35.

$elemMatch Query

db.students.find({
  scores: { $elemMatch: {
    subject: "Math",
    val: { $gt: 80 }
  }}
})

Returns Alice, Carol only — both conditions must be in the same element. Bob's Math score (35) is not > 80.

Decision Guide

ScenarioUse
Check if array contains a specific value { arr: "value" } — plain equality
Single condition on array elements { arr: { $gt: 5 } } — plain operator (one condition, same result as $elemMatch)
Multiple conditions, must be in SAME element { arr: { $elemMatch: { ... } } } — required
Conditions CAN span different elements { "arr.field1": ..., "arr.field2": ... } — plain dot notation
Primitive range that must hold for one value { arr: { $elemMatch: { $gte: 40, $lte: 60 } } }
TIP
When querying with a single condition on an array field ({ arr: { $gt: 5 } }), plain and $elemMatch produce identical results. The difference only matters when you have multiple conditions that should apply to the same element.
04
$all
Array must contain ALL specified values
contains-all

$all matches documents where the array field contains all of the specified values (order doesn't matter, extra elements are allowed).

Syntax and Basic Usage

{ arrayField: { $all: [value1, value2, ...] } }

// Find articles that have BOTH "mongodb" AND "nosql" tags
db.articles.find({ tags: { $all: ["mongodb", "nosql"] } })
// Matches if tags contains at least these two — extra tags are fine
// Order of values in $all does not matter
// Order of elements in the document array does not matter

$all vs $in

// $all — array must contain ALL listed values (intersection = all)
db.articles.find({ tags: { $all: ["mongodb", "nosql"] } })
// Only returns docs with BOTH "mongodb" AND "nosql"

// $in — array contains AT LEAST ONE of the listed values (union)
db.articles.find({ tags: { $in: ["mongodb", "nosql"] } })
// Returns docs with "mongodb" OR "nosql" (or both)
$all$in
Semantics Contains every listed value (AND) Contains any listed value (OR)
Target field Array fields Arrays and scalar fields
Extra values in array? allowed allowed
Order matters? no no

$all with $elemMatch — AND conditions on embedded docs

// Find docs where the array has BOTH:
//   - an element with qty >= 5, AND
//   - an element with qty <= 10 AND price < 1 (same element!)
db.inventory.find({
  items: {
    $all: [
      { $elemMatch: { qty: { $gte: 5 } } },
      { $elemMatch: { qty: { $lte: 10 }, price: { $lt: 1 } } }
    ]
  }
})
// Each $elemMatch in the $all array defines one required element
// Useful for requiring multiple distinct elements each satisfying their own conditions
05
$size
Match array by exact element count
length

$size matches documents where the array field has exactly the specified number of elements.

Basic Usage

// Find documents where tags array has exactly 3 elements
db.articles.find({ tags: { $size: 3 } })

// Find documents where items array has exactly 1 element
db.orders.find({ items: { $size: 1 } })

// Find documents where the comments array is empty
db.posts.find({ comments: { $size: 0 } })
WARN
$size only accepts an exact integer. You cannot use comparison operators with it: { tags: { $size: { $gt: 2 } } } is invalid and throws an error. See Section 06 for workarounds.

$size Cannot Use Operators

// INVALID — throws an error
db.articles.find({ tags: { $size: { $gt: 2 } } })
// Error: $size needs a number as argument

// VALID — only exact integer
db.articles.find({ tags: { $size: 3 } })

$size in Aggregation

// In aggregation, use the $size expression operator (different from query $size)
db.articles.aggregate([
  { $project: { tagCount: { $size: "$tags" } } }
])
// Returns the count as a field — works with $match and $filter too

// Use $size expression with $match for range-based filtering in aggregation
db.articles.aggregate([
  { $match: { $expr: { $gt: [ { $size: "$tags" }, 2 ] } } }
])
// Finds docs where tags array has MORE THAN 2 elements
// $expr allows aggregation expressions inside query context
TIP
In the aggregation pipeline, $size is an expression operator that returns the array's count as a value. In a regular find query, $size is a query operator that matches documents by exact count. They share the same name but are used differently.
06
$size Workarounds
Range-based array length queries without $size limitations
patterns

Because $size only supports exact integers, range-based length queries (e.g., "arrays with more than 3 elements") require alternative approaches.

Index Existence Trick

// Arrays are zero-indexed. If index N exists, the array has at least N+1 elements.
// Find docs where tags array has AT LEAST 3 elements (index 2 must exist)
db.articles.find({ "tags.2": { $exists: true } })
// "tags.2" is the 3rd element (0-indexed). If it exists, arr.length >= 3

// Find docs where tags has AT MOST 2 elements (index 2 must NOT exist)
db.articles.find({ "tags.2": { $exists: false } })
// If index 2 doesn't exist, array has 0, 1, or 2 elements

// Combine for exact range — between 2 and 4 elements
db.articles.find({
  "tags.1": { $exists: true },  // at least 2 elements
  "tags.4": { $exists: false }  // fewer than 5 elements
})
// Result: docs where tags has exactly 2, 3, or 4 elements

$expr + $size Aggregation Expression

// Use $expr to bridge aggregation expressions into find() queries
// Find docs where tags has MORE THAN 2 elements
db.articles.find({
  $expr: { $gt: [ { $size: "$tags" }, 2 ] }
})

// Find docs where tags has BETWEEN 2 and 5 elements (inclusive)
db.articles.find({
  $expr: {
    $and: [
      { $gte: [ { $size: "$tags" }, 2 ] },
      { $lte: [ { $size: "$tags" }, 5 ] }
    ]
  }
})

Workaround Comparison

ApproachSyntaxIndex-Friendly?Best For
Index existence trick { "arr.N": { $exists: true/false } } yes (multikey) Simple >/< N thresholds
$expr + $size { $expr: { $gt: [{$size:"$arr"}, N] } } no (collection scan) Complex ranges, aggregation pipelines
Stored count field Keep a tagCount field, index it yes (scalar index) Frequent range queries on array length
TIP
For frequent array-length range queries at scale, consider maintaining a separate numeric field (e.g., tagCount) that you increment/decrement with $inc alongside push/pull operations. A regular index on that field performs far better than any of the workarounds above.
07
Quick Reference
All array query operators at a glance
reference
OperatorPurposeKey Constraint
{ arr: value } Array contains the value Plain equality — implicit membership check
{ arr: [v1, v2] } Array is exactly this array Exact match including order — rarely intended
$elemMatch At least one element satisfies ALL conditions Same-element enforcement — use whenever multi-condition
$all Array contains ALL listed values Order irrelevant; extra elements OK; cannot use range operators directly
$size Array has exactly N elements Exact integer only — no comparison operators
"arr.N": { $exists: true } Array has at least N+1 elements $size workaround for >/< N

Common Patterns Cheat Sheet

// Array contains a value
db.col.find({ tags: "mongodb" })

// Array contains any of these values
db.col.find({ tags: { $in: ["mongodb", "nosql"] } })

// Array contains ALL of these values
db.col.find({ tags: { $all: ["mongodb", "nosql"] } })

// At least one element satisfies multiple conditions simultaneously
db.col.find({ scores: { $elemMatch: { subject: "Math", val: { $gte: 80 } } } })

// Exact array length
db.col.find({ tags: { $size: 3 } })

// Array length > 2 (index existence workaround)
db.col.find({ "tags.2": { $exists: true } })

// Array length > 2 ($expr workaround)
db.col.find({ $expr: { $gt: [{ $size: "$tags" }, 2] } })

// Nested array range — same element constraint
db.col.find({ ratings: { $elemMatch: { $gte: 40, $lte: 60 } } })