MongoDBLogical Operators

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)

JS
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

JS
// 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

JS
// 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

JS
// (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

JS
// 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();
Note
`$not` cannot be applied directly to a field value — use `$ne` for simple inequality. `$not` wraps **operator expressions** like `{ $gt: 30 }` or a regular expression. The syntax is always `{ field: { $not: { $operator: value } } }`.
$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

JS
// 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

JS
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 })
Tip
Replace `{ $or: [{ field: 'a' }, { field: 'b' }] }` with `{ field: { $in: ['a', 'b'] } }` when conditions are on the same field — it's cleaner and can better leverage a single index rather than requiring an index union.