MongoDB Cheatsheet

Query Operators

Use this MongoDB reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Comparison

A query document is a set of field conditions. Every operator is a key inside the value, so { qty: { $gt: 5 } } and never { $gt: { qty: 5 } }.

OperatorMatchesExample
$eqEqual. Implied when you write a bare value{ qty: { $eq: 5 } }
$neNot equal. Also matches documents missing the field{ status: { $ne: "paid" } }
$gt $gteGreater than, greater or equal{ qty: { $gte: 10 } }
$lt $lteLess than, less or equal{ placed_at: { $lt: ISODate("2026-01-01") } }
$inEqual to any value in the array{ status: { $in: ["paid", "shipped"] } }
$ninEqual to none of them, missing field included{ status: { $nin: ["cancelled"] } }

Two conditions on one field combine, no $and needed:

db.orders.find({ qty: { $gte: 5, $lt: 20 } })
db.orders.find({ placed_at: { $gte: ISODate("2026-01-01"),
                              $lt:  ISODate("2026-02-01") } })

Comparisons respect the BSON type order, and they only match within a type bracket. A string "5" is never $gt: 4, and numeric types (int, long, double, decimal) do compare against each other. $ne and $nin are negations, so the planner cannot seek with them and usually scans the whole index.

Logical

Top-level keys are an implicit and. Reach for $and only when the same operator key would repeat, which in practice means two $or blocks.

OperatorFormNotes
$and{ $and: [ {...}, {...} ] }Rarely needed. Required to combine two $ors
$or{ $or: [ {...}, {...} ] }Each branch should have its own index, or the whole thing scans
$not{ f: { $not: { $gt: 5 } } }Wraps an operator expression, not a field condition
$nor{ $nor: [ {...}, {...} ] }Matches documents failing every clause
// Implicit and
db.orders.find({ status: "paid", "items.sku": "A-1" })

// Two independent or-groups need $and
db.orders.find({
  $and: [
    { $or: [ { status: "paid" }, { status: "shipped" } ] },
    { $or: [ { region: "EU" }, { region: "UK" } ] }
  ]
})

$not also negates a missing field, so { qty: { $not: { $gt: 5 } } } matches documents with no qty at all. If you mean "present and not greater than 5", add $exists: true.

Element and evaluation

OperatorMatches
$exists: trueField is present, even when its value is null
$exists: falseField is absent
$type: "string"BSON type by alias or number. Aliases: double string object array binData objectId bool date null regex int long decimal
$type: "number"Alias covering every numeric type
$type: ["string", "int"]Any of several types
$regexPattern match, with $options i m x s
$exprUse aggregation expressions in a query, so you can compare two fields
$mod: [4, 0]Value modulo 4 equals 0
$text: { $search: "..." }Full text, requires a text index
$jsonSchemaMatch a JSON Schema, the same operator schema validation uses
$sampleRate: 0.05Random ~5% of documents (4.4+)
$whereServer-side JavaScript. Do not use it
db.users.find({ email: { $regex: "^admin@", $options: "i" } })
db.users.find({ email: /^admin@/i })              // identical, native regex

// Compare two fields in the same document
db.budgets.find({ $expr: { $gt: ["$spent", "$limit"] } })

// Field present but explicitly null
db.users.find({ deleted_at: { $type: "null" } })

An anchored, case-sensitive prefix regex (/^abc/) can use a normal index. Add i and it cannot, unless the index was built with a case-insensitive collation. $where evaluates JavaScript per document, ignores every index, and is blocked on Atlas. $expr does the same job properly.

Array operators

OperatorMatches
$all: [a, b]Array contains all of these values, order and extras irrelevant
$elemMatch: { ... }At least one single element satisfies all the inner conditions
$size: 3Array length is exactly 3. No ranges, no $gt
db.posts.find({ tags: { $all: ["mongodb", "indexes"] } })
db.posts.find({ tags: { $size: 3 } })

// One item that is BOTH sku A-1 AND qty >= 2
db.orders.find({ items: { $elemMatch: { sku: "A-1", qty: { $gte: 2 } } } })

There is no operator for "array longer than n". Either maintain a counter field or use { "tags.9": { $exists: true } } for "at least 10".

An array matches if any element matches

Querying an array field with a scalar tests every element. This is the single most useful and most surprising rule in MQL.

// tags: ["sale", "new", "clearance"]
db.posts.find({ tags: "sale" })        // matches, "sale" is one element
db.posts.find({ tags: "new" })         // matches too
QueryMeaning
{ tags: "sale" }Any element equals "sale". Also matches a scalar tags: "sale"
{ tags: ["sale", "new"] }The array equals exactly ["sale","new"], in that order
{ tags: { $all: ["sale", "new"] } }Contains both, any order, extras allowed

The trap is multiple conditions on one array field. Without $elemMatch they are satisfied by different elements.

// scores: [ 5, 95 ]
db.tests.find({ scores: { $gt: 90, $lt: 10 } })            // MATCHES
// 95 satisfies $gt: 90, and 5 satisfies $lt: 10, separately

db.tests.find({ scores: { $elemMatch: { $gt: 90, $lt: 10 } } })  // no match

The same applies to arrays of subdocuments. { "items.sku": "A-1", "items.qty": 99 } matches an order where one item has that sku and a different item has that qty. Use $elemMatch whenever the conditions must land on one element.

Exact subdocument vs dot notation

These two look equivalent and are not. An embedded-document equality match is a whole-document comparison, so it is field-order sensitive and rejects extra fields.

// Stored: { _id: 1, address: { city: "Lisbon", zip: "1200" } }

db.users.find({ address: { city: "Lisbon", zip: "1200" } })  // matches
db.users.find({ address: { zip: "1200", city: "Lisbon" } })  // NO match, wrong order
db.users.find({ address: { city: "Lisbon" } })               // NO match, zip extra
db.users.find({ "address.city": "Lisbon" })                  // matches, always
FormSemantics
{ a: { b: 1 } }a equals that exact document. Same fields, same order, nothing more
{ "a.b": 1 }a.b equals 1, whatever else a holds and in whatever order

Use dot notation for essentially every real query. Exact subdocument matching is only right for a fixed-shape value you always write whole, such as a compound key.

null matches missing

{ field: null } matches documents where the field is null and documents where the field does not exist at all. There is no way to spell "is null" with a bare null.

// A: { _id: 1, deleted_at: null }
// B: { _id: 2 }                       (no deleted_at)
// C: { _id: 3, deleted_at: ISODate("2026-01-05") }

db.users.find({ deleted_at: null })                    // A and B
db.users.find({ deleted_at: { $type: "null" } })       // A only
db.users.find({ deleted_at: { $exists: false } })      // B only
db.users.find({ deleted_at: { $exists: true, $ne: null } })   // C only
db.users.find({ deleted_at: { $ne: null } })           // C only
IntentQuery
Null or absent{ f: null }
Explicitly null{ f: { $type: "null" } }
Absent{ f: { $exists: false } }
Present and not null{ f: { $exists: true, $ne: null } }

The same asymmetry hits $ne and $nin generally. Both match documents that lack the field, because an absent field trivially fails to equal your value. If your soft-delete filter is { deleted_at: null } you are relying on this, and it works, but say what you mean in code that others will read.