Element Operators and Type Checks
Learners will query for the presence or type of a field using $exists and $type to handle optional or mixed-type data.
Element Operators and Type Checks is a free MongoDB Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the MongoDB Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Optional Fields in Flexible Schemas
MongoDB's flexible schema means some documents in a collection may have fields that others lack. A user document might have an optional phoneNumber field—some users provided it, others did not. In SQL, you would handle this with a NULL value in every row. In MongoDB, the field simply does not exist in some documents.
This difference has important implications for querying. MongoDB's element operators—primarily $exists and $type—let you query based on the presence or type of a field, rather than its value.
$exists: Checking Field Presence
$exists: true matches documents that have the specified field (regardless of its value, even if the value is null). $exists: false matches documents where the field is completely absent.
This distinction is subtle but important: a document with { phone: null } does match { phone: { $exists: true } } because the field exists—it is just null. Only a document with no phone field at all matches { phone: { $exists: false } }.
// Find users who have provided a phone number (field exists)
db.users.find({ phone: { $exists: true } });
// Find users who never provided a phone number (field absent)
db.users.find({ phone: { $exists: false } });
// Key distinction:
// { phone: null } => $exists: true (field exists, value is null)
// { name: 'Alice' } => $exists: false (no phone field at all)
// Combined: field exists AND is not null
db.users.find({ phone: { $exists: true, $ne: null } });null vs Missing Field Queries
MongoDB's equality filter with null has a dual behavior that often surprises beginners: { phone: null } matches documents where phone is explicitly null AND documents where phone does not exist at all. This is because MongoDB considers an absent field to be implicitly null for equality comparisons.
If you want to distinguish between 'field is null' and 'field is missing', combine $exists with $eq: null. For most practical purposes, both cases mean 'no phone number provided' and you can treat them the same way.
// Matches BOTH null and missing field:
db.users.find({ phone: null });
// Returns: { name: 'Alice', phone: null }
// AND: { name: 'Bob' } (no phone field)
// Only where field is explicitly null:
db.users.find({ phone: { $eq: null, $exists: true } });
// Returns: { name: 'Alice', phone: null }
// NOT: { name: 'Bob' } (missing field)
// Only where field is missing:
db.users.find({ phone: { $exists: false } });$type: Querying by BSON Type
MongoDB is schema-flexible, which means a field like price might be a Number in most documents but accidentally stored as a String in some legacy records. The $type operator lets you filter documents based on the BSON type of a field's value.
You can specify types by name (e.g., 'string', 'int', 'date') or by BSON type number (e.g., 2 for String, 16 for Int32). Using type names is more readable and preferred.
// Find documents where price is a string (data quality issue)
db.products.find({ price: { $type: 'string' } });
// These need to be fixed - price should be a number
// Find documents where price is a number (any numeric type)
db.products.find({ price: { $type: ['double', 'int', 'long', 'decimal'] } });
// $type accepts an array - matches any of the listed types
// Check for boolean
db.settings.find({ enabled: { $type: 'bool' } });Common BSON Type Names
The most commonly used BSON type names for $type queries are:
'double'— 64-bit floating point number (JS default number)'string'— UTF-8 string'object'— embedded document (sub-object)'array'— array'binData'— binary data'objectId'— ObjectId'bool'— boolean'date'— Date'null'— null value'int'— 32-bit integer'long'— 64-bit integer'decimal'— Decimal128
// Real-world use: find documents where tags is an array
db.products.find({ tags: { $type: 'array' } });
// Find docs where createdAt is a date (not a string)
db.events.find({ createdAt: { $type: 'date' } });
// Audit: find any document where _id is not an ObjectId
db.users.find({ _id: { $not: { $type: 'objectId' } } });
// These might be docs with custom string _idsThe 'number' Alias
A useful shortcut is the 'number' type alias, which matches any numeric BSON type: double, int, long, and decimal. This is equivalent to writing an array of all four numeric type names but much cleaner.
Use 'number' when you want to find all documents where a field is any kind of number, regardless of the specific precision. This is particularly useful for data quality checks where you want to identify fields that should be numeric but were accidentally stored as strings.
// 'number' alias matches double, int, long, decimal
db.products.find({ price: { $type: 'number' } });
// Same as: { price: { $type: ['double', 'int', 'long', 'decimal'] } }
// Data quality check: find price fields that are strings
// These should be numbers:
db.products.find({ price: { $type: 'string' } });
// Fix those docs:
db.products.find({ price: { $type: 'string' } }).forEach(doc => {
db.products.updateOne(
{ _id: doc._id },
{ $set: { price: parseFloat(doc.price) } }
);
});Using $exists in Schema Migrations
Schema migrations in MongoDB often involve adding new required fields to existing documents. $exists helps you find the documents that still need to be updated—or that have already been migrated.
A typical migration flow: first use { newField: { $exists: false } } to find all legacy documents, then add the new field with a default value. Check progress by counting how many documents still have $exists: false. This rolling migration approach works without downtime because MongoDB's flexible schema allows old and new document shapes to coexist temporarily.
// Migration: add 'timezone' field with default value to legacy users
async function migrateTimezone() {
const batchSize = 1000;
let processed = 0;
while (true) {
const result = await db.collection('users').updateMany(
{ timezone: { $exists: false } }, // Legacy docs without field
{ $set: { timezone: 'UTC' } }, // Add default
{ limit: batchSize } // Process in batches
);
processed += result.modifiedCount;
if (result.modifiedCount === 0) break;
}
console.log('Migrated:', processed);
}Combining $exists and $type
You can combine $exists and $type in one query to find documents where a field exists AND has a specific type. This is useful for data quality audits where you want to identify documents with the field set to the wrong type versus documents where the field is simply absent.
For example, in a collection where age should be a number, you might want to separately count: (1) documents missing the age field entirely, (2) documents where age is a string (data entry error), and (3) documents where age is a valid number.
// Audit 'age' field data quality
const total = await db.collection('users').countDocuments({});
const missingAge = await db.collection('users')
.countDocuments({ age: { $exists: false } });
const ageIsString = await db.collection('users')
.countDocuments({ age: { $type: 'string' } });
const ageIsNumber = await db.collection('users')
.countDocuments({ age: { $type: 'number' } });
console.log(`Total: ${total}, Missing: ${missingAge}, String: ${ageIsString}, Number: ${ageIsNumber}`);$expr: Cross-Field Comparisons
The $expr operator lets you use aggregation expressions inside a regular find() filter. This unlocks cross-field comparisons—something that is not possible with standard query operators.
For example: 'find orders where the discount is greater than 50% of the original price' requires comparing discount to a computed fraction of originalPrice—two fields in the same document. $expr enables these relationships using expression operators like $gt, $multiply, and $divide.
// Find products where salePrice < originalPrice * 0.7 (>30% off)
db.products.find({
$expr: {
$lt: [
'$salePrice',
{ $multiply: ['$originalPrice', 0.7] }
]
}
});
// Find orders where quantity ordered > quantity in stock
db.orders.find({
$expr: { $gt: ['$quantityOrdered', '$quantityInStock'] }
});Indexing for $exists Queries
Queries using $exists have mixed index support. An index on a field does not help with { field: { $exists: false } }—if the field is absent, it is not in the index—so MongoDB must scan every document. However, { field: { $exists: true } } can use a sparse index: a sparse index only stores entries for documents that have the indexed field, enabling fast lookups of existing documents.
Use db.collection.createIndex({ field: 1 }, { sparse: true }) to create a sparse index when you frequently query for documents that have an optional field.
// Sparse index: only indexes docs where 'proSubscriptionId' exists
db.users.createIndex(
{ proSubscriptionId: 1 },
{ sparse: true }
);
// This query now uses the sparse index efficiently:
db.users.find({ proSubscriptionId: { $exists: true } });
// Only pro subscribers are in the index
// Regular (non-sparse) index would include null entries for all users
// Sparse index is smaller and faster for optional-field queriesData Quality Audit Workflow
A practical data quality audit workflow using and : first identify all distinct field patterns in your collection to spot inconsistencies, then categorize documents by their field types, and finally write targeted updateMany calls to normalize the data.
MongoDB's aggregation pipeline makes auditing easy: by of a field to count how many documents have each type. This gives you a clear picture of data quality issues before you start fixing them.
// Audit: how many docs have price as each type?
db.products.aggregate([
{
: {
_id: { : '' }, // Group by BSON type of price
count: { : 1 }
}
}
]);
// Results like:
// [{ _id: 'double', count: 4820 },
// { _id: 'string', count: 3 },
// { _id: 'missing', count: 12 }]
// Now you know exactly what to fix!Quick Check
Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.
Lesson Recap
In this lesson you learned: $exists queries for the presence or absence of a field—a missing field and a null field are different and can be distinguished by combining $exists: true with $ne: null, $type filters by BSON type name (string, number, date, array, etc.) and is essential for data quality audits in schema-flexible collections, and sparse indexes efficiently support $exists: true queries on optional fields by only indexing documents that have the field. Next up we explore regex queries and pattern matching for flexible text searches.
Frequently asked questions
Is the “Element Operators and Type Checks” lesson free?
Yes — the full text of “Element Operators and Type Checks” is free to read here on the web, and the MongoDB Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the MongoDB Academy course, upgrade to CoddyKit PRO.
What will I learn in “Element Operators and Type Checks”?
Learners will query for the presence or type of a field using $exists and $type to handle optional or mixed-type data. You practise MongoDB Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start MongoDB Academy?
No prior experience is required. MongoDB Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Element Operators and Type Checks” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this MongoDB Academy lesson?
Yes. Every MongoDB Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Comparison Operators: $eq, $gt, $lt, $in
- Logical Operators: $and, $or, $nor, $not
- Element Operators and Type Checks
- Regex Queries and Pattern Matching