antipattern examples
**Bad** — `$exists: true` on a regular index still requires a document fetch:
Overview
$exists on Regular Index vs. Sparse Index
Bad — $exists: true on a regular index still requires a document fetch:
db.collection.createIndex({ a: 1 })
db.collection.find({ a: { $exists: true } })
// Cannot efficiently answer — null semantics require checking each document
Good — Use a sparse index, which only contains entries where the field exists:
db.collection.createIndex({ a: 1 }, { sparse: true })
db.collection.find({ a: { $exists: true } })
// Answered directly from the index — no document fetch needed
Why: Regular indexes store null for both missing and existing fields that are set to null, so $exists can't be answered from the index alone. Sparse indexes only store entries for documents where the field exists.
Unanchored $regex vs. Anchored $regex
Bad — Unanchored case insensitive regex cannot use the index efficiently:
db.collection.find({ name: { $regex: /smith/i } })
// Full index or collection scan — case-insensitive, not anchored
Good — Anchored, case-sensitive regex uses the index as a range query:
db.collection.find({ name: { $regex: /^Smith/ } })
// Efficient index range scan on the "Smith" prefix
Why: Indexes store values in sorted order. Only a left-anchored, case-sensitive $regex can be converted into an efficient index range scan. For case-insensitive matching, use a case-insensitive collation index instead.
$where / JavaScript vs. Native MQL Operators
Bad — Server-side JavaScript execution:
db.collection.find({
$where: "this.price * this.quantity > 1000"
})
Good — Native aggregation expression:
db.collection.find({
$expr: { $gt: [{ $multiply: ["$price", "$quantity"] }, 1000] }
})
Why: JavaScript executed on the server is always slower than native MQL, cannot use indexes. It's also a security risk and is deprecated. Use $expr with aggregation operators instead.
In-Memory Sort vs. Index-Supported Sort
Bad — Sort on an unindexed field triggers in-memory sort:
db.orders.find({ status: "processing" }).sort({ createdAt: -1 })
// Index: { status: 1 } — sort is done in memory
Good — Compound index supports both filter and sort:
db.orders.createIndex({ status: 1, createdAt: -1 })
db.orders.find({ status: "processing" }).sort({ createdAt: -1 })
// No SORT stage in the plan — results come pre-sorted from the index