Logical Operators
Logical operators combine multiple conditions. MongoDB supports $and, $or, $not, and $nor — giving you full boolean logic for filtering documents.
Implicit $and
When you specify multiple fields in a filter document, MongoDB implicitly ANDs them. This is the most common pattern and the most readable for conditions on different fields.
Implicit AND (multiple fields)
const users = db.collection('users');
// Implicit AND: both conditions must be true
await users.find({ role: 'admin', active: true }).toArray();
// Equivalent explicit $and form (rarely needed for different fields)
await users.find({
$and: [{ role: 'admin' }, { active: true }],
}).toArray();
// Three conditions — still implicit AND
await users.find({
role: 'admin',
active: true,
verified: true,
}).toArray();$and — Explicit AND
Use explicit $and when you need two or more conditions on the same field — for example, a range query expressed as two separate operator objects. A filter document key can only appear once, so implicit AND cannot express this.
$and with same-field conditions
// Range query on a single field — MUST use explicit $and
// (you can't write { age: { $gt: 25 }, age: { $lt: 35 } } — duplicate key)
await users.find({
$and: [
{ age: { $gt: 25 } },
{ age: { $lt: 35 } },
],
}).toArray();
// Simpler: combine operators on the same field (implicit $and on operators)
await users.find({ age: { $gt: 25, $lt: 35 } }).toArray();
// $and with conditions on different fields (explicit but uncommon)
await users.find({
$and: [
{ role: 'editor' },
{ 'subscription.plan': 'pro' },
{ active: true },
],
}).toArray();$or — Either Condition
$or matches documents that satisfy at least one of the conditions in the array. Use it when you want documents matching any of several criteria.
$or examples
// Find admins OR premium users
await users.find({
$or: [{ role: 'admin' }, { subscription: 'premium' }],
}).toArray();
// Find cheap products OR highly-rated products
await db.collection('products').find({
$or: [
{ price: { $lt: 10 } },
{ rating: { $gte: 4.5 } },
],
}).toArray();
// Combine with other filter conditions
await db.collection('orders').find({
status: 'pending',
$or: [
{ total: { $gt: 500 } }, // high-value orders
{ expedited: true }, // or expedited orders
],
}).toArray();Combining $and and $or
You can nest $and and $or to build complex boolean expressions. Use parentheses in your mental model to clarify precedence.
Nested logic
// (role === 'admin' OR premium === true) AND active === true
await users.find({
active: true,
$or: [
{ role: 'admin' },
{ subscription: 'premium' },
],
}).toArray();
// More complex: active AND (admin OR (premium AND verified))
await users.find({
active: true,
$or: [
{ role: 'admin' },
{
$and: [
{ subscription: 'premium' },
{ verified: true },
],
},
],
}).toArray();$not — Negate a Condition
$not inverts the effect of an operator expression. Unlike $and/$or/$nor, it applies to a single field's condition, not a top-level query array.
$not examples
// Find users whose age is NOT greater than 30
// (equivalent to age <= 30, but useful when $lte is not available directly)
await users.find({ age: { $not: { $gt: 30 } } }).toArray();
// Find usernames that do NOT start with 'A' (using a regex)
await users.find({ username: { $not: /^A/i } }).toArray();
// Negate an $in condition
await users.find({ role: { $not: { $in: ['admin', 'superuser'] } } }).toArray();
// Equivalent and more readable:
await users.find({ role: { $nin: ['admin', 'superuser'] } }).toArray();$nor — Neither Condition
$nor matches documents that fail ALL conditions in the array — the logical opposite of $or. A document must not match any of the listed conditions to be returned.
$nor examples
// Find users who are neither admin nor moderator
await users.find({
$nor: [{ role: 'admin' }, { role: 'moderator' }],
}).toArray();
// Equivalent: { role: { $nin: ['admin', 'moderator'] } }
// Find products that are neither on sale nor out of stock
await db.collection('products').find({
$nor: [
{ onSale: true },
{ stock: { $lte: 0 } },
],
}).toArray();
// $nor also matches documents where fields don't exist
await users.find({
$nor: [
{ deletedAt: { $exists: true } },
{ suspended: true },
],
}).toArray();Performance Considerations
The logical operator you choose can significantly affect query performance. MongoDB's query planner treats $or branches independently, potentially using an index for each branch (index union). However, $in on a single indexed field is usually faster.
Pattern | Preferred Form |
|---|---|
{ $or: [{ field: "a" }, { field: "b" }] } | { field: { $in: ["a", "b"] } } |
Multiple $or branches on different fields | $or (ensure each branch field has an index) |
Negate a simple equality | $ne instead of $not: { $eq: ... } |
Multiple required conditions on different fields | Comma-separated fields (implicit $and) |
Real-World Combined Query
Here is a realistic example combining multiple operators to find active users who are either admins or have a premium subscription and were created in the last 30 days.
Real-world combined query
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const recentHighValueUsers = await users
.find({
active: true,
createdAt: { $gte: thirtyDaysAgo },
$or: [
{ role: 'admin' },
{ 'subscription.plan': { $in: ['premium', 'enterprise'] } },
],
})
.sort({ createdAt: -1 })
.limit(50)
.toArray();
// Ensure indexes exist for performance:
// db.users.createIndex({ active: 1, createdAt: -1 })
// db.users.createIndex({ role: 1 })
// db.users.createIndex({ 'subscription.plan': 1 })