MongoDB $match Stage

Beginner
5 min read
Updated: Oct 2026
4 examples
Live preview

What Is $match?

$match filters documents in a pipeline—like find(), but inside aggregation. Reach for it when you need “only documents where…” before sorting, joining, or grouping.

Remember
$match = "keep only docs that pass this query"

{ $match: { status: "active", price: { $gte: 20 } } }

same syntax as find()
place EARLY → fewer docs for later stages
indexed fields → MongoDB can use IXSCAN

equality     → { category: "Books" }
comparison   → { price: { $gt: 50 } }
list         → { status: { $in: ["a", "b"] } }
cross-field  → { $expr: { $gt: ["$price", "$cost"] } }
UsePrefer
Filter in aggregation$match
Simple query (no pipeline)find() (same filter syntax)
Count matching docs$match then $count

How to Use $match in MongoDB

Write a query document, put $match early, then reshape or aggregate what remains.

  1. Start with { $match: { … } } using find()-style conditions.
  2. Prefer indexed fields in the first $match.
  3. Optional: chain $sort, $project, $limit.
  4. Optional: use $expr when comparing two fields on the same document.
PatternExample
Equality{ status: "active" }
Comparison{ price: { $gte: 20 } }
List{ category: { $in: ["Books", "Toys"] } }
Logic{ $or: [ … ] } / { $and: [ … ] }
Cross-field{ $expr: { $gt: ["$price", "$cost"] } }

Pseudocode

Pseudocode
for each document in input:
  if document satisfies query predicate:
    pass document to next stage
  else:
    drop document

// first $match on indexed fields:
//   MongoDB may use an index (like find)
// later $match (after $lookup / $addFields):
//   filters in memory on the stream

Cheat Sheet

Keep these facts handy when filtering in aggregation.

TopicDetail
TypeAggregation stage (filter)
SyntaxSame query language as find()
Best positionAs early as possible
IndexesFirst $match can use them
Multiple stagesAllowed; may be coalesced
Cross-fieldUse $expr
vs find()Same filter; $match is for pipelines

Live Preview

Filter a sample products list (teaching model—not a live MongoDB connection).

Sample: 5 products with category, status, price, and cost.

Result
Notebook    Books      $12  active
Keyboard    Gadgets    $45  active
Mug         Home       $8   active

Worked Walkthrough — Active Electronics

Goal: keep only active products in Electronics with price at least 20.

Calculation
products:
  { name: "USB Hub",  category: "Electronics", status: "active", price: 25 }
  { name: "Novel",    category: "Books",       status: "active", price: 12 }
  { name: "Old HD",   category: "Electronics", status: "retired", price: 40 }
  { name: "Cable",    category: "Electronics", status: "active", price: 8 }

$match: {
  category: "Electronics",
  status: "active",
  price: { $gte: 20 }
}

keeps → USB Hub only
drops → Novel (wrong category), Old HD (retired), Cable (price < 20)

MongoDB Examples

We use five sample products in products. Insert them once in Example 1, then run each $match pipeline in Examples 2–4. Copy blocks into mongosh.

Example 1 — Sample Data

Five products with name, category, status, price, and cost. We load them into a collection named products.

mongosh
db.products.drop()

db.products.insertMany([
  { _id: 1, name: "Notebook", category: "Books",   status: "active",  price: 12, cost: 5 },
  { _id: 2, name: "Keyboard", category: "Gadgets", status: "active",  price: 45, cost: 20 },
  { _id: 3, name: "Mug",      category: "Home",    status: "active",  price: 8,  cost: 2 },
  { _id: 4, name: "Old HD",   category: "Gadgets", status: "retired", price: 40, cost: 10 },
  { _id: 5, name: "Cable",    category: "Gadgets", status: "active",  price: 6,  cost: 8 }
])

How the Data Is Stored in MongoDB

After the insert, MongoDB keeps five documents in products. Run find() to see them:

mongosh
db.products.find()

Each product document uses these fields:

Field in MongoDBExamplePlain English
name"Keyboard"Product label
category"Gadgets"Grouping for filters like $in
status"active" / "retired"Whether the item is still sold
price45Sale price (number)
cost20Cost basis for margin checks
Picture
products (5 rows):

  name       category   status    price  cost
  --------   --------   -------   -----  ----
  Notebook   Books      active      12     5
  Keyboard   Gadgets    active      45    20
  Mug        Home       active       8     2
  Old HD     Gadgets    retired     40    10
  Cable      Gadgets    active       6     8

Next examples filter this same set with $match.

How It Works

  1. Reset once. drop() clears old rows; insertMany loads the five products.
  2. Stable ids. _id 1–5 makes it easy to spot documents in output.
  3. find() confirms storage. You see the same fields the pipeline will read.
  4. Reuse. Examples 2–4 assume this data is already in products.

Example 2 — Equality + Comparison

Goal: keep active products with price at least 20 (uses the products data from Example 1).

mongosh
db.products.aggregate([
  {
    $match: {
      status: "active",
      price: { $gte: 20 }
    }
  }
])

How It Works

  1. Same five products. Pipeline reads the documents you inserted in Example 1.
  2. Equality. status: "active" drops retired items (Old HD).
  3. Comparison. price: { $gte: 20 } keeps mid/high prices.
  4. AND by default. Fields in one object must all match—only Keyboard passes.

Example 3 — $in and $or

Goal: keep Books or Home, or any product whose status is in a short list (still on the Example 1 products collection).

mongosh
db.products.aggregate([
  {
    $match: {
      $or: [
        { category: { $in: ["Books", "Home"] } },
        { status: { $in: ["active", "featured"] } }
      ]
    }
  },
  { $project: { name: 1, category: 1, status: 1, _id: 0 } },
  { $sort: { name: 1 } }
])

How It Works

  1. Example 1 data. All five products enter the pipeline.
  2. $in. First branch: category Books or Home (Notebook, Mug).
  3. $or. Second branch: status active or featured (adds active Gadgets).
  4. Project + sort. Slim fields and alphabetical names for readable output.

Example 4 — $expr Cross-Field

Goal: keep products that sell above cost (margin check across two fields on the Example 1 documents).

mongosh
db.products.aggregate([
  {
    $match: {
      $expr: { $gt: ["$price", "$cost"] }
    }
  },
  {
    $project: {
      name: 1,
      price: 1,
      cost: 1,
      margin: { $subtract: ["$price", "$cost"] },
      _id: 0
    }
  }
])

How It Works

  1. Example 1 rows. Each product brings its own price and cost.
  2. $expr. Lets aggregation operators run inside $match.
  3. Field paths. "$price" and "$cost" are compared per document.
  4. Cable dropped. Price 6 < cost 8; others pass and show computed margin.

Edge Cases & Pitfalls

Check these before relying on $match in production pipelines.

order

Put $match early

Filtering after $lookup / $group wastes work. Match first whenever the filter does not depend on later stages.

types

Types must match

price: "20" does not equal price: 20. Align numbers, strings, and ObjectIds with stored types.

expr

Cross-field needs $expr

Comparing two fields requires $expr. A plain query treats "$cost" as a literal string, not a field path.

index

$expr may skip indexes

Some $expr forms cannot use a simple index the way equality filters can. Prefer normal query operators when possible.

Performance Notes

ApproachCostBest for
Early $match on indexed fieldsLow (IXSCAN possible)Most pipelines
Late $match after heavy stagesHigh wasted workOnly when filter needs later fields
$expr filtersOften in-memoryCross-field / computed logic
Selective compound filtersFewer docs downstreamBefore $lookup / $group

Rule of thumb: filter early, filter selectively, index the fields you match on most.

Key Takeaways

  • Role: filter documents in an aggregation pipeline.
  • Syntax: same query language as find().
  • Position: put $match early for indexes and speed.
  • Cross-field: use $expr to compare fields.

One line: $match answers “which documents should continue through this pipeline?”

Frequently Asked Questions

$match filters documents in an aggregation pipeline—keeping only those that satisfy the query conditions. It uses the same query syntax as find(), including comparison operators, $in, $and, $or, and $expr.
As early as possible. Early $match reduces documents flowing through expensive stages like $lookup, $group, and $sort, and lets MongoDB use indexes on matched fields.
The query syntax is identical. db.collection.find({ status: "active" }) equals [{ $match: { status: "active" } }] as the first aggregation stage. You can also use $match later in a pipeline.
Yes. $expr allows aggregation expressions—e.g. { $match: { $expr: { $gte: ["$price", "$cost"] } } } compares two fields on the same document.
Yes. MongoDB may coalesce adjacent $match stages. You can $match before $lookup to filter input, then $match again after $lookup on joined fields.
Standard query operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or, $exists, $regex, $elemMatch, and $expr for aggregation expressions.

Next: $merge

Write pipeline results into a target collection with insert/update/replace modes.

$merge tutorial →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

14 people found this page helpful