Querying is the process of retrieving specific documents from a MongoDB collection using a query filter â a JSON-like object that describes the conditions a document must satisfy. MongoDB's query language is expressive and supports everything from simple equality checks to complex logical, array, and regular-expression matching. This tutorial covers the full range of query capabilities, from fundamentals to performance optimization.
Information
đ 1. Introduction
Every read operation in MongoDB revolves around a query document â an object whose keys represent fields to match and whose values describe the matching criteria. Queries can range from a simple exact match to deeply nested combinations of operators.
A basic query
// Simple equality query
const results = await collection.find({ category: "electronics" }).toArray();đ§ą 2. Query Fundamentals
A query filter is passed as the first argument to find() or findOne(). An empty filter {} matches every document in the collection.
Implicit vs. explicit equality
// Implicit equality
{ status: "active" }
// Explicit equality using $eq
{ status: { $eq: "active" } }Note
âī¸ 3. Comparison Operators
Comparison operators let you match values that are greater than, less than, or otherwise numerically or lexically related to a given value.
| Operator | Meaning |
|---|---|
| $eq | Equal to |
| $ne | Not equal to |
| $gt | Greater than |
| $gte | Greater than or equal to |
| $lt | Less than |
| $lte | Less than or equal to |
| $in | Matches any value in an array |
| $nin | Matches none of the values in an array |
Using comparison operators
// Products priced between 20 and 100
const results = await collection.find({
price: { $gte: 20, $lte: 100 }
}).toArray();
// Products in a specific set of categories
const inCategories = await collection.find({
category: { $in: ["electronics", "accessories"] }
}).toArray();đ 4. Logical Operators
Logical operators combine multiple query conditions using AND, OR, NOT, and NOR semantics.
| Operator | Description |
|---|---|
| $and | All conditions must be true |
| $or | At least one condition must be true |
| $not | Negates a condition |
| $nor | None of the conditions may be true |
Combining conditions with logical operators
// Implicit AND (multiple fields)
{ inStock: true, price: { $lt: 50 } }
// Explicit $or
{
$or: [
{ inStock: true },
{ price: { $lt: 10 } }
]
}
// $and with repeated field conditions
{
$and: [
{ price: { $gt: 10 } },
{ price: { $lt: 100 } }
]
}Tip
đ§Ŧ 5. Element Operators
Element operators check for the existence or type of a field, rather than its value.
Using $exists and $type
// Documents where "discount" field exists
{ discount: { $exists: true } }
// Documents where "price" is a number
{ price: { $type: "number" } }đ§ 6. Evaluation Operators
Evaluation operators perform higher-level logic such as regular expression matching, modulo arithmetic, or evaluating a $expr aggregation expression within a query.
Evaluation operators in action
// Regex match (case-insensitive)
{ name: { $regex: "^mouse", $options: "i" } }
// Modulo: even stock counts
{ stockCount: { $mod: [2, 0] } }
// Compare two fields using $expr
{ $expr: { $gt: ["$sold", "$stockCount"] } }Important
đ 7. Array Operators
Array operators query fields containing arrays, matching based on element presence, size, or structure.
| Operator | Description |
|---|---|
| $all | Array contains all specified values |
| $elemMatch | At least one element matches all conditions |
| $size | Array has an exact length |
Querying array fields
// Array contains both "sale" and "featured"
{ tags: { $all: ["sale", "featured"] } }
// At least one review has rating >= 4 and verified: true
{
reviews: {
$elemMatch: { rating: { $gte: 4 }, verified: true }
}
}
// Array with exactly 3 elements
{ tags: { $size: 3 } }đ§Ž 8. Bitwise Operators
Bitwise operators test integer field values at the bit level, useful for flags packed into a single numeric field.
Bitwise operators
// Bits at positions [0, 2] are set to 1
{ permissions: { $bitsAllSet: [0, 2] } }
// All specified bits are clear (0)
{ permissions: { $bitsAllClear: [1, 3] } }Caution
đ¤ 9. Regular Expressions
MongoDB supports PCRE-compatible regular expressions for pattern matching on string fields, either via $regex or native regex literals in the driver.
Native regex literal
// Names starting with "wireless" (case-insensitive)
const results = await collection.find({
name: /^wireless/i
}).toArray();Warning
đ 10. Query by _id
Every document has a unique _id field, typically an ObjectId. Querying by _id is the fastest possible lookup since it's always indexed.
Finding a document by its ObjectId
const { ObjectId } = require("mongodb");
const product = await collection.findOne({
_id: new ObjectId("64f1a2b3c4d5e6f7890a1b2c")
});đī¸ 11. Nested Document Queries
When a field contains an embedded document, you can query it either as a whole object or by targeting a specific nested field.
Querying embedded documents
// Exact match on the entire embedded document
{ address: { city: "Chennai", zip: "600001" } }
// Match a single nested field (order-independent, preferred)
{ "address.city": "Chennai" }Note
đĻ 12. Array Queries
Querying array fields can target the whole array, a single element, or elements at a specific index.
Array element matching
// Matches if the array contains "sale" anywhere
{ tags: "sale" }
// Matches if the element at index 0 is "sale"
{ "tags.0": "sale" }đ¯ 13. Dot Notation
Dot notation is the standard way to reach into nested documents and arrays using a string path like "field.subfield".
Using dot notation for deep paths
// Nested field inside an array of embedded documents
{ "reviews.author": "Alice" }
// Deeply nested path
{ "shipping.address.country": "India" }Hint
đī¸ 14. Projection
Projection controls which fields are returned, reducing payload size and improving performance.
Applying a projection
// Include only name and price
const results = await collection
.find({ inStock: true }, { projection: { name: 1, price: 1, _id: 0 } })
.toArray();âī¸ 15. Sorting
Use sort() to order query results. 1 sorts ascending, -1 sorts descending.
Multi-field sorting
const results = await collection
.find({ category: "electronics" })
.sort({ price: -1, name: 1 })
.toArray();đĸ 16. Limiting Results
limit() caps the number of documents returned by a query.
Limiting query results
const topFive = await collection
.find({})
.sort({ price: -1 })
.limit(5)
.toArray();âī¸ 17. Skipping Results
skip() bypasses a number of documents, often combined with limit() for pagination.
Paginating results
const page3 = await collection
.find({})
.sort({ _id: 1 })
.skip(20)
.limit(10)
.toArray();Caution
đąī¸ 18. Cursor Methods
find() returns a cursor â a pointer to the result set that can be iterated lazily instead of loading everything into memory at once.
Loading all results into memory
const docs = await collection.find({}).toArray();Iterating with forEach()
await collection.find({}).forEach(doc => {
console.log(doc.name);
});Streaming with async iteration
const cursor = collection.find({});
for await (const doc of cursor) {
console.log(doc.name);
}Tip
đ§ž 19. Distinct Values
The distinct() method returns all unique values for a given field across matching documents.
Finding distinct field values
const categories = await collection.distinct("category");
console.log(categories);
// [ "electronics", "accessories", "furniture" ]⥠20. Query Optimization
Efficient queries rely on indexes. Without an index, MongoDB must perform a collection scan, examining every document to find matches.
Creating indexes to support queries
// Create an index on the "price" field
await collection.createIndex({ price: 1 });
// Compound index for common query patterns
await collection.createIndex({ category: 1, price: -1 });Reference
đĄ 21. Query Hints
hint() forces MongoDB to use a specific index, overriding the query planner's default choice â useful when you know a better index exists.
Forcing a specific index
const results = await collection
.find({ category: "electronics", price: { $gt: 50 } })
.hint({ category: 1, price: -1 })
.toArray();đ§ 22. Explain Plans
The explain() method reveals how MongoDB executes a query â including whether it used an index (IXSCAN) or scanned the whole collection (COLLSCAN).
Analyzing query execution
const plan = await collection
.find({ category: "electronics" })
.explain("executionStats");
console.log(plan.executionStats.executionTimeMillis);
console.log(plan.queryPlanner.winningPlan.stage);Example
đ 23. Performance Considerations
- Index fields used frequently in filters, sorts, and joins ($lookup).
- Use covered queries â where projection matches an index exactly â to avoid reading full documents.
- Avoid leading wildcard regexes; they cannot use an index efficiently.
- Keep compound indexes ordered by equality, then sort, then range fields (ESR rule).
- Use explain() regularly during development to catch slow queries early.
đ 24. Best Practices
- Always project only the fields you need instead of returning full documents.
- Prefer $in over multiple $or clauses on the same field for readability and performance.
- Validate and sanitize any user-supplied values before placing them into a filter.
- Use dot notation rather than exact embedded-document matches for flexibility.
- Monitor slow queries using MongoDB's profiler or Atlas Performance Advisor.
â ī¸ 25. Common Mistakes
- Using unanchored regex patterns on large collections, causing full scans.
- Forgetting quotes around dot-notation paths, resulting in invalid JavaScript.
- Assuming { tags: ["sale"] } matches an array containing only "sale", when it actually requires an exact array match.
- Using deep skip() values for pagination on large collections, hurting performance.
- Not checking explain() output before deploying a new query pattern to production.
Best Practice
â 26. Frequently Asked Questions
Yes. Passing {} as the filter to find() returns every document in the collection.
Yes, using the $size operator, e.g. { tags: { $size: 3 } } matches arrays with exactly three elements.
By default, yes. Pass the i flag (e.g. { $options: "i" } or /pattern/i) for case-insensitive matching.