MongoDB Query Operators

Write richer MongoDB queries with operators: compare with $gt, $gte, $lt, $lte and $ne, match lists with $in and $nin, combine conditions with $and, $or and $not, check fields with $exists and $type, match patterns with $regex, and query arrays with $all, $size and $elemMatch.

Lesson 4 of 43Beginner19 min read

Exact matches only get you so far. Real questions sound like "products under £30", "orders that are pending or on hold", "users without a phone number", or "names starting with A". MongoDB answers these with query operators: special keys that start with a dollar sign, such as $gt for "greater than" and $in for "any of these values".

Operators sit inside the filter, next to the field they apply to: { price: { $lt: 30 } } reads as "price is less than 30". Several operators on the same field combine into a range, such as { price: { $gte: 10, $lte: 50 } }. Logical operators like $or combine whole conditions, and array operators let you ask precise questions about the elements of an array.

In this lesson you'll use comparison, logical, element, pattern and array operators, and $expr to compare two fields of the same document. The examples use the products collection from the previous lesson, plus a few orders documents introduced where they're needed.

Comparison operators

OperatorMeaningExample
$eqEqual{ price: { $eq: 24 } } (same as { price: 24 })
$neNot equal{ category: { $ne: 'office' } }
$gt, $gteGreater than, or equal{ price: { $gt: 20 } }
$lt, $lteLess than, or equal{ 'stock.qty': { $lte: 2 } }
$inAny of a list{ category: { $in: ['home', 'office'] } }
$ninNone of a list{ category: { $nin: ['home'] } }

Comparison operators work on numbers, strings (alphabetically) and dates. $ne and $nin also match documents where the field is missing.

Logical, element and pattern operators

MongoDB Shell
db.products.find({ $or: [{ price: { $lt: 5 } }, { tags: 'gift' }] })
db.products.find({ price: { $not: { $gt: 20 } } })
db.products.find({ stock: { $exists: false } })
db.products.find({ name: { $regex: '^k', $options: 'i' } })
  • $or takes an array of conditions and matches if any is true; $nor matches if none are.
  • $and is implicit when you list fields, but needed when two conditions use the same operator, such as two $or groups.
  • $not negates an operator expression.
  • $exists: true/false checks whether a field is present; $type checks its BSON type, such as 'string' or 'number'.
  • $regex matches a pattern; $options: 'i' ignores case. A pattern starting with ^ can use an index; other patterns scan every document.

Array operators and $expr

  • $all: the array contains all these values, in any order: { tags: { $all: ['gift', 'ceramic'] } }.
  • $size: the array has exactly this many elements: { tags: { $size: 0 } }.
  • $elemMatch: one element meets several conditions at once, which matters for arrays of documents: { items: { $elemMatch: { sku: 'MUG', qty: { $gte: 2 } } } }.
  • $expr lets a query use aggregation expressions, for example to compare two fields of the same document: { $expr: { $gt: ['$spent', '$budget'] } }.

Comparisons, types and missing fields

Comparison operators only compare values of the same kind. MongoDB calls this type bracketing: { price: { $gt: 20 } } looks at numbers, so a document where price was stored by mistake as the string '50' is simply skipped, and { price: { $gt: '1' } } would find only string prices. If a query mysteriously misses documents, check the stored types with { price: { $type: 'string' } }.

Missing fields need the same care, and negation is where they catch people out. These two queries look equivalent, but they aren't:

MongoDB Shell
db.products.find({ price: { $lte: 20 } })          // only documents that have a price of 20 or less
db.products.find({ price: { $not: { $gt: 20 } } }) // also documents with no price at all

$not matches every document that fails the inner condition, and a document without a price field fails $gt: 20, so it's included. The same applies to $ne and $nin. When you mean "has a price, and it's 20 or less", use the positive form, or add $exists: true alongside $not.

A few more rules worth knowing:

  • Ranges on one field combine in one object: { 'stock.qty': { $gt: 1, $lt: 10 } }.
  • $in accepts regular expressions as well as values: { name: { $in: [/^k/i, 'Pen'] } }.
  • $size takes an exact number only. { tags: { $size: { $gt: 0 } } } is an error; use { 'tags.0': { $exists: true } } to find non-empty arrays.

MongoDB CRUD Syntax

Operator cheat sheet

{ price: { $gte: 10, $lte: 50 } }
{ status: { $in: ['paid', 'shipped'] } }
{ status: { $ne: 'cancelled' } }
{ $or: [{ qty: 0 }, { discontinued: true }] }
{ phone: { $exists: false } }
{ age: { $type: 'number' } }
{ name: { $regex: '^an', $options: 'i' } }
{ tags: { $all: ['a', 'b'] } }
{ items: { $elemMatch: { sku: 'X', qty: { $gt: 1 } } } }
{ $expr: { $lt: ['$stock', '$reorderLevel'] } }

MongoDB CRUD Summary

  • Operators start with $ and go inside the filter next to the field: { price: { $lt: 30 } }.
  • $gt, $gte, $lt, $lte, $ne, $in and $nin compare values; combine them for ranges.
  • $or, $and, $nor and $not combine conditions logically.
  • $exists, $type and $regex check presence, types and patterns.
  • $all, $size and $elemMatch query arrays; $expr compares fields within a document.

MongoDB CRUD Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Comparisons and ranges

MongoDB Shell
db.products.find({ price: { $lt: 10 } }, { name: 1, _id: 0 })
db.products.find({ price: { $gte: 20, $lte: 80 } }, { name: 1, price: 1, _id: 0 })
db.products.find({ category: { $in: ['home', 'office'] } }, { name: 1, _id: 0 })
db.products.countDocuments({ category: { $ne: 'kitchen' } })
db.products.find({ 'stock.qty': { $lte: 2 } }, { name: 1, _id: 0 })
Output
[ { name: 'Mug' }, { name: 'Pen' } ]
[
  { name: 'Lamp', price: 24 },
  { name: 'Kettle', price: 29.99 },
  { name: 'Rug', price: 79 }
]
[ { name: 'Lamp' }, { name: 'Rug' }, { name: 'Pen' } ]
3
[ { name: 'Lamp' }, { name: 'Rug' } ]
Line-by-line explanation
  1. Line 1$lt matches prices below 10.
  2. Line 2Two operators on one field make an inclusive range.
  3. Line 3$in matches any value in the list.
  4. Line 4$ne excludes kitchen items, leaving Lamp, Rug and Pen.
  5. Line 5The Pen has no stock field, so a comparison on stock.qty doesn't match it.
Example 2

$or, $exists and $regex

MongoDB Shell
db.products.find({ $or: [{ price: { $lt: 5 } }, { tags: 'gift' }] }, { name: 1, _id: 0 })
db.products.find({ stock: { $exists: false } }, { name: 1, _id: 0 })
db.products.find({ name: { $regex: '^k', $options: 'i' } }, { name: 1, _id: 0 })
db.products.find({ name: { $regex: 'u' } }, { name: 1, _id: 0 })
db.products.find({ price: { $not: { $gt: 20 } } }, { name: 1, _id: 0 })
Output
[ { name: 'Mug' }, { name: 'Rug' }, { name: 'Pen' } ]
[ { name: 'Pen' } ]
[ { name: 'Kettle' } ]
[ { name: 'Mug' }, { name: 'Rug' } ]
[ { name: 'Mug' }, { name: 'Pen' } ]
Line-by-line explanation
  1. Line 1$or matches documents meeting either condition: cheap items or gifts.
  2. Line 2$exists: false finds documents that don't have a stock field at all.
  3. Line 3^ anchors the pattern at the start, and the i option ignores case.
  4. Line 4Without an anchor, the pattern can appear anywhere in the value.
  5. Line 5$not reverses the condition: prices that are not greater than 20.
Example 3

Arrays of documents and $elemMatch

MongoDB Shell
db.orders.insertMany([
  { _id: 101, items: [{ sku: 'MUG', qty: 1 }, { sku: 'LAMP', qty: 3 }], budget: 50, spent: 60 },
  { _id: 102, items: [{ sku: 'MUG', qty: 4 }], budget: 50, spent: 34 },
  { _id: 103, items: [{ sku: 'PEN', qty: 10 }, { sku: 'RUG', qty: 1 }], budget: 100, spent: 91 }
])

db.orders.find({ 'items.sku': 'MUG', 'items.qty': { $gte: 2 } }, { _id: 1 })
db.orders.find({ items: { $elemMatch: { sku: 'MUG', qty: { $gte: 2 } } } }, { _id: 1 })
db.orders.find({ items: { $size: 2 } }, { _id: 1 })
db.orders.find({ $expr: { $gt: ['$spent', '$budget'] } }, { _id: 1 })
Output
{ acknowledged: true, insertedIds: { '0': 101, '1': 102, '2': 103 } }
[ { _id: 101 }, { _id: 102 } ]
[ { _id: 102 } ]
[ { _id: 101 }, { _id: 103 } ]
[ { _id: 101 } ]
Line-by-line explanation
  1. Line 7Without $elemMatch, order 101 matches because one item is a mug and a different item has qty 3.
  2. Line 8$elemMatch requires a single item to be a mug with qty of at least 2.
  3. Line 9$size matches arrays with exactly two elements.
  4. Line 10$expr compares two fields of the same document: spending above the budget.

Common MongoDB CRUD Mistakes

Writing an operator outside the field

Don't
db.products.find({ $gt: { price: 20 } })        // error: unknown top level operator: $gt
Do
db.products.find({ price: { $gt: 20 } })

Repeating a key instead of combining operators

Don't
db.products.find({ price: { $gt: 10 }, price: { $lt: 50 } })   // the second key replaces the first
Do
db.products.find({ price: { $gt: 10, $lt: 50 } })

Separate conditions on an array of documents

Don't
db.orders.find({ 'items.sku': 'MUG', 'items.qty': 4 })          // may match different elements
Do
db.orders.find({ items: { $elemMatch: { sku: 'MUG', qty: 4 } } })

MongoDB CRUD in Real Projects

Operators in real queries

Filter panels on shopping sites become operator queries: a price slider is $gte and $lte, a set of checkboxes is $in, and "in stock only" is { 'stock.qty': { $gt: 0 } }. Background jobs find work with queries like { status: 'pending', createdAt: { $lt: oneHourAgo } }. Search boxes often start with an anchored, case-insensitive $regex before moving to a full text search index.

MongoDB CRUD FAQ

What is the difference between $in and $or in MongoDB?

$in checks one field against a list of values: { status: { $in: ['pending', 'on-hold'] } }. $or combines whole conditions, which can involve different fields: { $or: [{ status: 'pending' }, { priority: 'high' }] }. When every condition tests the same field for equality, $in is shorter and usually the better choice.

How do I write a SQL LIKE query in MongoDB?

Use $regex or a regular expression literal. LIKE 'Ke%' becomes { name: /^Ke/ }, and LIKE '%ett%' becomes { name: /ett/ }. Prefix patterns starting with ^ can use an index, but patterns that can match anywhere in the string have to check every value, so for real search features use a text index or Atlas Search.

How do I find documents where a field is not null and exists?

Use { field: { $ne: null } }. It excludes documents where the field is null and also those where it's missing, because MongoDB treats a missing field as equal to null in this comparison. { field: { $exists: true } } is different: it matches any document that has the field, even when its value is null.

How do I find documents where an array is not empty?

Check whether the first element exists: { tags: { $exists: true, $ne: [] } } or { 'tags.0': { $exists: true } }. The second form reads as "there is an element at position 0". $size can't do this, because it only accepts an exact number and not a range like { $gt: 0 }.

Why does my $gt query not return documents I can see in the collection?

The field is probably stored with a different type in those documents, such as the string '50' instead of the number 50. Comparison operators only compare like with like, so strings are skipped by a numeric range. Find the culprits with { price: { $type: 'string' } }, fix the data, and add schema validation to stop it happening again.

MongoDB CRUD Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the filter to find products cheaper than 10.

db.products.find({ price: { : 10 } })
Multiple choiceeasy

Which operator matches any value in a list?

Predict the outputmedium

How many products does this match in the products collection?

db.products.countDocuments({ category: { $in: ['kitchen', 'office'] } })
Multiple choicemedium

How do you find documents that don't have a phone field?

Multiple choicehard

When do you need $elemMatch?

MongoDB CRUD Quiz

5 questions to check what you learned.

  1. What does $gte mean?

    • Greater than or equal to
    • Greater than
    • Not equal
    • Less than or equal to
  2. What does $or take?

    • A string
    • A number
    • An array of conditions
    • A field name
  3. Which option makes $regex ignore case?

    • $options: 'i'
    • $options: 'g'
    • $case: false
    • $ignoreCase: true
  4. What does { tags: { $size: 0 } } match?

    • Missing tags
    • Documents whose tags array is empty
    • Every document
    • Documents with null tags
  5. What is $expr used for in a find filter?

    • Regular expressions
    • Exporting results
    • Explaining queries
    • Using aggregation expressions, such as comparing two fields of the same document