MongoDBArray Operators

Array Operators

Arrays are first-class citizens in MongoDB. Array operators let you query arrays by element, match conditions across array elements, and manipulate array contents during updates.

Query Array Operators Overview

Operator

Category

Purpose

$all

query

Array contains ALL specified values

$elemMatch

query

At least one element matches ALL conditions

$size

query

Array has exactly N elements

$push

update

Append an element to an array

$pull

update

Remove elements matching a condition

$addToSet

update

Add element only if not already present

$pop

update

Remove the first or last element

$pullAll

update

Remove all instances of listed values

$each

update modifier

Apply $push or $addToSet to multiple values

$slice

update modifier / projection

Trim or cap array length

$sort

update modifier

Sort array elements after $push

$all — Array Contains All Values

Matches documents where the array field contains ALL of the specified elements.

$all — match all elements

JS
// Find posts tagged with BOTH 'mongodb' AND 'nosql'
db.posts.find({
  tags: { $all: ['mongodb', 'nosql'] }
})

// Equivalent: the array must contain every listed value (order doesn't matter)
// { tags: ['nosql', 'mongodb', 'database'] }  ← matches
// { tags: ['mongodb'] }                        ← does NOT match (missing 'nosql')
// { tags: ['nosql'] }                          ← does NOT match (missing 'mongodb')
Note
$all does not require the array to match exactly — it just requires those elements to be present.
$elemMatch — Element Matching Conditions

Matches documents where at least one array element satisfies ALL conditions in the $elemMatch expression. Essential for arrays of objects.

$elemMatch — object array conditions

JS
// Find orders where a SINGLE item has quantity > 5 AND price < 20
db.orders.find({
  items: {
    $elemMatch: {
      qty: { $gt: 5 },
      price: { $lt: 20 }
    }
  }
})

// Matches: { items: [{ qty: 8, price: 15 }, { qty: 2, price: 50 }] }
// Because the first item satisfies BOTH conditions

Without $elemMatch — the pitfall

JS
// WARNING: This does NOT guarantee the conditions apply to the SAME element
db.orders.find({
  'items.qty': { $gt: 5 },
  'items.price': { $lt: 20 }
})

// This matches a document where:
//   - ANY item has qty > 5  (could be item A)
//   - ANY item has price < 20  (could be item B — a different item!)
//
// Example that incorrectly matches:
// { items: [{ qty: 8, price: 100 }, { qty: 1, price: 10 }] }
// item[0] satisfies qty > 5, item[1] satisfies price < 20 — but they're different items!
// Use $elemMatch to enforce conditions on the SAME element.
$size — Array Length

$size — match by array length

JS
// Find users with exactly 3 tags
db.users.find({ tags: { $size: 3 } })

// Find documents where the array is empty
db.users.find({ tags: { $size: 0 } })

// Find documents where hobbies has exactly 1 element
db.users.find({ hobbies: { $size: 1 } })
Note
$size only works with exact equality. For greater-than/less-than array length, use $where or create a computed tagCount field and index it.
Querying by Array Index

Positional queries

JS
// Query by exact position using dot notation
// Find documents where the FIRST score is greater than 90
db.results.find({ 'scores.0': { $gt: 90 } })

// Find documents where the first address is in 'New York'
db.users.find({ 'addresses.0.city': 'New York' })

// Find documents where the second tag is 'mongodb'
db.posts.find({ 'tags.1': 'mongodb' })
$push — Add to Array

$push variants

JS
// Push a single item onto the array
db.posts.updateOne(
  { _id: postId },
  { $push: { comments: { user: 'alice', text: 'Great post!' } } }
)

// Push MULTIPLE items using $each
db.users.updateOne(
  { _id: userId },
  { $push: { tags: { $each: ['mongodb', 'nosql', 'database'] } } }
)

// Push, then SORT the array (sort by 'score' descending)
db.students.updateOne(
  { _id: studentId },
  {
    $push: {
      scores: {
        $each: [{ subject: 'Math', score: 95 }],
        $sort: { score: -1 }
      }
    }
  }
)

// Push and CAP array length to last 5 elements (ring buffer pattern)
db.events.updateOne(
  { _id: deviceId },
  {
    $push: {
      recentEvents: {
        $each: [{ type: 'click', ts: new Date() }],
        $slice: -5  // keep only the last 5 elements
      }
    }
  }
)
$pull — Remove from Array

$pull — remove matching elements

JS
// Pull by exact value — remove the tag 'deprecated'
db.posts.updateOne(
  { _id: postId },
  { $pull: { tags: 'deprecated' } }
)

// Pull by condition — remove all scores below 60
db.students.updateOne(
  { _id: studentId },
  { $pull: { scores: { $lt: 60 } } }
)

// Pull objects matching a condition — remove items with qty 0
db.orders.updateOne(
  { _id: orderId },
  { $pull: { items: { qty: 0 } } }
)

// Pull from ALL documents (updateMany)
db.posts.updateMany(
  {},
  { $pull: { tags: 'spam' } }
)
$addToSet — Unique Push

$addToSet — no duplicates

JS
// Add tag only if it doesn't already exist in the array
db.posts.updateOne(
  { _id: postId },
  { $addToSet: { tags: 'mongodb' } }
)
// If 'mongodb' is already in tags, this is a no-op

// $addToSet with $each — add multiple unique values at once
db.users.updateOne(
  { _id: userId },
  {
    $addToSet: {
      roles: { $each: ['editor', 'viewer', 'editor'] }
      // 'editor' appears twice — only ONE is added if not present
    }
  }
)
$pop — Remove First or Last

$pop — remove from ends

JS
// Remove the LAST element from the queue array
db.tasks.updateOne(
  { _id: taskId },
  { $pop: { queue: 1 } }
)

// Remove the FIRST element from the queue array
db.tasks.updateOne(
  { _id: taskId },
  { $pop: { queue: -1 } }
)

// $pop: 1  → removes last  (like Array.pop())
// $pop: -1 → removes first (like Array.shift())
The Positional $ Operator

The positional $ operator updates the first array element that matches the query condition.

Positional $ update

JS
// Update the score of a specific student in the 'grades' array
// The $ refers to the first matching element from the query
db.students.updateOne(
  { _id: studentId, 'grades.subject': 'Math' },
  { $set: { 'grades.$.score': 98 } }
)

// Mark a specific comment as edited
db.posts.updateOne(
  { _id: postId, 'comments.user': 'alice' },
  { $set: { 'comments.$.edited': true } }
)
The $[] and $[identifier] Operators

All-positional $[] update

JS
// $[] — update ALL elements in the array
// Add 5 bonus points to every score
db.students.updateOne(
  { _id: studentId },
  { $inc: { 'scores.$[]': 5 } }
)

// $[identifier] — update elements matching arrayFilters
// Boost all scores below 50 to 50 (curved grading)
db.students.updateMany(
  {},
  { $set: { 'scores.$[elem]': 50 } },
  { arrayFilters: [{ elem: { $lt: 50 } }] }
)

// Update nested array objects matching a condition
db.orders.updateMany(
  {},
  { $set: { 'items.$[item].discounted': true } },
  { arrayFilters: [{ 'item.price': { $gt: 100 } }] }
)
Tip
Use $elemMatch anytime you're querying an array of objects with multiple conditions — it's the only way to ensure conditions apply to the SAME element.
Warning
Using $push without $slice on a frequently-updated array will cause it to grow indefinitely, potentially approaching the 16 MB document limit.