17 Topics
Chapter•180 Minutes

MongoDB Data Types and BSON

Master MongoDB's BSON data model, including strings, ObjectId, numeric types, Boolean values, null, missing fields, arrays, embedded documents, dates, timestamps, regular expressions, binary data, Decimal128, type conversion, type checking, and the effects of data types on queries, sorting, validation, and application development.

Understanding BSON and Its Relationship with JSON

Level:Beginner to IntermediateCore concept:BSON

MongoDB stores documents using BSON (Binary JSON), a binary-encoded serialization format designed to represent structured data efficiently. BSON builds on a JSON-like document model but supports additional data types and explicit type distinctions.

Understanding BSON is essential because MongoDB does not treat every value as an ordinary JavaScript value or JSON string. The type of a stored value influences matching, sorting, arithmetic, validation, and serialization.

JSON versus BSON

JSON supports objects, arrays, strings, numbers, Booleans, and null. BSON additionally supports types such as ObjectId, Date, Decimal128, binary data, and other specialized values.

Consider these documents:

{
 name: "Arun",
 age: 28,
 active: true
}
{
 name: "Arun",
 age: NumberInt(28),
 active: true,
 createdAt: ISODate("2026-10-22T10:00:00Z"),
 _id: ObjectId("65a000000000000000000001")
}

The second example uses MongoDB shell notation to demonstrate BSON types. NumberInt(), ISODate(), and ObjectId() are shell helpers, not standard JSON syntax.

Why BSON matters

1. Type-aware queries

MongoDB distinguishes numbers from strings. A field containing the string "28" is not equivalent to a field containing the number 28 in an ordinary equality or numeric range query.

2. Specialized values

Dates should generally be stored as BSON Dates when date comparisons and date arithmetic are required. Monetary values requiring exact decimal representation may benefit from Decimal128 or integer minor units.

3. Document serialization

Applications may transform BSON documents into JSON before returning them through an HTTP API. This transformation can change how values are represented. For example, an ObjectId may become a string, and a BSON Date commonly becomes an ISO-formatted string.

4. Storage and size

BSON has its own encoding rules and overhead. The size of a BSON document is not necessarily the same as the number of characters in its JSON representation.

MongoDB's maximum BSON document size is 16 MiB. This constraint matters when storing large arrays, embedded documents, or binary data.

BSON does not mean every query is binary

Developers generally write queries using MongoDB's document-shaped syntax. The driver serializes supported application values into BSON when communicating with the server.

For example:

db.users.find({ age: { $gte: 18 } });

You write a readable query expression. MongoDB evaluates the filter using its query semantics and stored BSON values.

Practical rule

Choose a type based on the meaning of the data, not merely on how the value looks when printed. A date string may look like a date, but it is not a BSON Date. A numeric string may contain digits, but it is not a BSON number.

JSON and BSON comparison
JSON: Text-based data interchange format
Supports common values such as objects, arrays, strings, numbers, Booleans, and null.
BSON: Binary-encoded document representation
Supports additional types such as ObjectId, Date, Decimal128, and binary values.
Type awareness: Important for query behavior
A numeric value and a numeric-looking string are distinct BSON types.
Maximum document size: 16 MiB
The BSON document limit affects large arrays, embedded data, and binary storage.
Key Takeaways
  • MongoDB uses BSON to represent documents.
  • BSON supports types beyond standard JSON.
  • The stored BSON type affects query and validation behavior.
  • BSON document size is not the same as JSON text length.
  • MongoDB documents have a maximum BSON size of 16 MiB.
Knowledge Check

What is the most important difference between the values `28` and `"28"` in MongoDB?

String Data Type and String Handling

The BSON string type represents textual data such as names, email addresses, titles, descriptions, slugs, and category labels.

Example document:

{
 name: "Meera",
 email: "meera@example.com",
 role: "developer",
 country: "India"
}

String values are enclosed in quotation marks in shell examples. MongoDB stores the string content as BSON string data.

Equality matching

db.users.find({ role: "developer" });

This matches documents whose role value equals the string developer, subject to the applicable collation rules.

Case sensitivity

String comparisons are normally case-sensitive under the default simple binary comparison behavior.

For example, these values are different under ordinary simple collation:

"MongoDB"
"mongodb"
"MONGODB"

If your application requires case-insensitive comparisons, consider an appropriate collation or a normalized field, depending on the query pattern and indexing requirements.

String length

A string can contain a single character or a long description. MongoDB supports string operators in queries and aggregation expressions, including operations for transforming or examining string content.

For example, an aggregation can calculate the string length of a title:

db.courses.aggregate([
 {
 $project: {
 title: 1,
 titleLength: { $strLenCP: "$title" }
 }
 }
]);

$strLenCP counts Unicode code points rather than UTF-8 bytes. This distinction matters when strings contain non-ASCII characters.

Normalization considerations

A user may enter Arun, arun, or ARUN. Whether those values should be considered equivalent depends on the application's requirements.

For usernames or email lookup, define a consistent normalization strategy. A normalized field can simplify lookups and indexing, but normalization rules should be applied consistently during both writes and queries.

Do not blindly lowercase every string. Names, passwords, tokens, identifiers, and natural-language text have different semantics.

Strings that contain numbers

Avoid storing numeric data as strings when numeric comparisons or arithmetic are required.

For example:

{ price: "100" }

is not equivalent to:

{ price: 100 }

Use strings for identifiers whose meaning is textual, such as postal codes or phone numbers, even if they contain only digits. Use numeric types when the value is genuinely a quantity.

Insert and query string fields
db.stringExamples.insertMany([
 { name: "Arun", role: "developer" },
 { name: "Meera", role: "Developer" },
 { name: "Ravi", role: "admin" }
]);

// Exact string match under the collection's applicable collation.
db.stringExamples.find({ role: "developer" });

// Inspect string lengths using aggregation.
db.stringExamples.aggregate([
 {
 $project: {
 name: 1,
 role: 1,
 roleLength: { $strLenCP: "$role" }
 }
 }
]);
💡 The example demonstrates string equality and string-length calculation. Under simple collation, developer and Developer do not match each other. $strLenCP counts Unicode code points and is useful when measuring text length without counting UTF-8 bytes.
Key Takeaways
  • Strings represent textual values.
  • Default string matching is case-sensitive under simple collation.
  • Collation can change string comparison behavior.
  • String normalization should follow the meaning of the field.
  • Store identifiers as strings when they represent labels rather than quantities.
Knowledge Check

Under simple collation, what happens when querying `{ role: "developer" }` against a document containing `{ role: "Developer" }`?

ObjectId: Structure, Generation, and Querying

ObjectId is a BSON type commonly used for MongoDB document identifiers. When an inserted document does not specify _id, MongoDB drivers commonly generate an ObjectId automatically unless the collection or insertion workflow uses another supported identifier strategy.

Example:

{
 _id: ObjectId("65a000000000000000000001"),
 name: "Arun"
}

Why _id exists

Each document in a collection must have a unique _id value. MongoDB creates a unique _id index for ordinary collections to enforce this requirement.

ObjectId is the conventional default, but _id can use other supported BSON types, including strings, numbers, and UUID representations. Your choice should reflect the application's requirements.

ObjectId structure

A standard ObjectId is 12 bytes, conventionally represented as 24 hexadecimal characters.

Its components are:

  1. A 4-byte timestamp representing seconds since the Unix epoch.
  2. A 5-byte process-unique random value.
  3. A 3-byte counter.

This structure supports distributed generation with a low probability of collisions. It should not be treated as a mathematically absolute guarantee that collisions are impossible.

Generating an ObjectId

In mongosh:

const id = ObjectId();
print(id);

In a Node.js application using the MongoDB driver:

import { ObjectId } from "mongodb";

const id = new ObjectId();
console.log(id.toHexString());

The exact import path depends on the package and module system used by your project.

Querying by ObjectId

const id = ObjectId("65a000000000000000000001");

db.users.findOne({ _id: id });

If _id is stored as an ObjectId, querying it with the corresponding hexadecimal string does not ordinarily match the stored ObjectId.

This distinction is common when handling route parameters in a web application. An API may receive an ID as a string, but the backend must convert it to an ObjectId when the database field uses that BSON type.

ObjectId timestamp extraction

In mongosh, an ObjectId provides access to its embedded timestamp:

const id = ObjectId();
id.getTimestamp();

The timestamp has only second-level resolution. It is not a reliable substitute for a dedicated createdAt field when an application requires precise event times or explicit business semantics.

ObjectId is not an authorization mechanism

An ObjectId can be difficult to guess in many contexts, but its format does not provide authorization. Every endpoint that accesses a resource should verify that the authenticated caller has permission to access that resource.

Create and query an ObjectId
db.objectIdExamples.insertOne({
 name: "Arun",
 createdAt: new Date()
});

const document = db.objectIdExamples.findOne({ name: "Arun" });
const id = document._id;

print("ObjectId:", id);
print("Hex string:", id.toHexString());
print("Timestamp:", id.getTimestamp());

// Query using the BSON ObjectId.
db.objectIdExamples.findOne({ _id: id });

// Convert a known hexadecimal string into an ObjectId.
db.objectIdExamples.findOne({
 _id: ObjectId(id.toHexString())
});
💡 The example inserts a document without specifying _id, retrieves the generated identifier, and inspects its hexadecimal representation and embedded timestamp. ObjectId values and their string representations are different types. The timestamp embedded in ObjectId has second-level resolution and should not replace a dedicated creation timestamp when greater precision is needed.
Key Takeaways
  • ObjectId is a BSON type commonly used for document identifiers.
  • A standard ObjectId contains 12 bytes and is represented by 24 hexadecimal characters.
  • MongoDB requires a unique _id value for each document in a collection.
  • A hexadecimal string must be converted to ObjectId when querying an ObjectId field.
  • ObjectId is not a replacement for authorization or a precise business timestamp.
Knowledge Check

A document stores `_id` as an ObjectId. Why might `{ _id: "65a000000000000000000001" }` fail to find it?

Numeric BSON Types: Int32, Int64, Double, and Decimal128

MongoDB supports multiple numeric BSON types. Choosing the appropriate type matters for precision, range, arithmetic, validation, and interoperability with application languages.

The main numeric BSON types are:

  • Int32: A signed 32-bit integer.
  • Int64 (Long): A signed 64-bit integer.
  • Double: A 64-bit IEEE 754 floating-point number.
  • Decimal128: A decimal floating-point type designed for decimal precision and a wide numeric range.

MongoDB also has BSON-specific numeric representations and shell helpers for creating particular types.

Int32

Int32 is suitable for integers within the signed 32-bit range:

  • Minimum: -2,147,483,648
  • Maximum: 2,147,483,647

In mongosh, NumberInt() explicitly creates an Int32 value:

{ age: NumberInt(28) }

The standard JavaScript number type does not separately represent Int32 values, so the shell helper is useful when you need to demonstrate or create that BSON type explicitly.

Int64 (Long)

Int64 supports larger signed integers:

  • Minimum: -9,223,372,036,854,775,808
  • Maximum: 9,223,372,036,854,775,807

In mongosh:

{ totalViews: NumberLong("9000000000") }

The string form avoids accidentally passing an already-imprecise JavaScript number when constructing an integer outside JavaScript's safe integer range.

JavaScript's largest safe integer is 9007199254740991. A JavaScript number cannot represent every integer above that value exactly. If your application must handle large integers, use a driver-supported representation that preserves the intended BSON Int64 value.

Double

Double is a binary floating-point type. It supports a wide range of numeric values, but many decimal fractions cannot be represented exactly in binary floating-point.

For example, calculations involving 0.1 and 0.2 can produce representation effects in floating-point arithmetic.

Double is often appropriate for measurements, scientific values, and approximate numerical computations.

Decimal128

Decimal128 is designed for decimal floating-point values and is often appropriate for monetary calculations that require decimal precision.

In mongosh:

{
 price: NumberDecimal("19.99")
}

Construct Decimal128 from a decimal string when exact decimal input matters. Converting an already-rounded binary floating-point value into Decimal128 cannot recover the decimal digits that were lost earlier.

Currency modeling

Two common strategies are:

Strategy A: Decimal128

{
 price: NumberDecimal("1999.95"),
 currency: "INR"
}

Strategy B: Integer minor units

{
 pricePaise: NumberLong("199995"),
 currency: "INR"
}

The integer-minor-unit approach can work well when the currency and its scale are clearly defined. Not every currency uses two decimal places, so do not assume that every currency can be represented as cents or paise using a universal factor of 100.

Numeric type conversion

Aggregation provides conversion operators such as $convert, $toInt, $toLong, $toDouble, and $toDecimal. Conversion may fail when a value cannot be represented in the requested type.

For inconsistent historical data, $convert can specify onError and onNull values to control conversion behavior.

Do not assume that converting every field to Double is safe. A conversion can lose precision, overflow the target type, or fail for invalid input.

Numeric BSON types
Int32: Signed 32-bit integer
Suitable for integers within the signed 32-bit range.
Int64: Signed 64-bit integer
Supports much larger integer values; JavaScript applications must preserve values beyond the safe integer range.
Double: Binary floating-point
Useful for approximate numerical calculations but not ideal for exact decimal currency arithmetic.
Decimal128: Decimal floating-point
Useful for decimal values where decimal precision is important, including many financial applications.
Do not use Double blindly for money

Binary floating-point cannot represent every decimal fraction exactly. Consider Decimal128 or integer minor units with a well-defined currency scale when financial correctness matters.

Store and inspect different numeric types
db.numericExamples.insertOne({
 age: NumberInt(28),
 totalViews: NumberLong("9000000000"),
 temperature: 36.6,
 price: NumberDecimal("1999.95")
});

// Inspect the stored numeric values.
db.numericExamples.findOne();

// Calculate a converted decimal value in aggregation.
db.numericExamples.aggregate([
 {
 $project: {
 totalViews: 1,
 price: 1,
 priceAsDouble: { $toDouble: "$price" }
 }
 }
]);
💡 The document illustrates Int32, Int64, Double, and Decimal128. $toDouble converts the decimal price to Double for demonstration, but this conversion may lose decimal precision. Avoid converting monetary values to Double when exact decimal arithmetic is required.
Key Takeaways
  • MongoDB supports Int32, Int64, Double, and Decimal128.
  • JavaScript numbers cannot represent every integer above the safe integer limit exactly.
  • Double is appropriate for many approximate numerical calculations.
  • Decimal128 is useful when decimal precision is important.
  • Converting numeric types can introduce errors or fail for out-of-range values.
Knowledge Check

Which BSON numeric type is generally a strong candidate for storing decimal monetary values?

Boolean Values and Boolean Query Semantics

The BSON Boolean type represents either true or false.

Typical fields include:

  • isActive
  • published
  • emailVerified
  • isDeleted
  • hasAccess

Example:

{
 name: "Meera",
 emailVerified: true,
 isActive: false
}

Exact Boolean matching

db.users.find({ emailVerified: true });

This matches documents where emailVerified equals the Boolean value true.

The following value is not the same BSON type:

{ emailVerified: "true" }

The string "true" is text, not a Boolean.

Missing fields and Booleans

Consider these documents:

{ name: "Arun", isActive: true }
{ name: "Meera", isActive: false }
{ name: "Ravi" }

The third document has no isActive field. A query for { isActive: false } does not generally mean 'the field is false or absent'; it matches the explicit Boolean false value.

If you need to match missing fields, use $exists:

db.users.find({ isActive: { $exists: false } });

Boolean fields versus status fields

A Boolean is appropriate when the concept genuinely has two states. If a workflow has states such as draft, published, archived, and rejected, a string or another controlled representation may be more expressive than multiple Boolean flags.

For example, separate flags such as isDraft, isPublished, and isArchived can permit contradictory combinations unless the application or database enforces consistency.

A status field can model the mutually exclusive state directly:

{ status: "published" }

Use a schema validation rule or application-level validation when only a defined set of statuses is permitted.

Query Boolean fields and missing values
db.booleanExamples.insertMany([
 { name: "Arun", isActive: true },
 { name: "Meera", isActive: false },
 { name: "Ravi" },
 { name: "Kavitha", isActive: "false" }
]);

// Explicit Boolean false.
db.booleanExamples.find({ isActive: false });

// Field is absent.
db.booleanExamples.find({ isActive: { $exists: false } });

// Field is stored as a string.
db.booleanExamples.find({ isActive: { $type: "string" } });
💡 The dataset includes true, false, a missing field, and a string containing false. These are distinct cases. The equality filter for Boolean false does not treat the string "false" as Boolean false and does not generally include documents where the field is absent.
Key Takeaways
  • Boolean values are true and false.
  • Strings such as "true" and "false" are not Boolean values.
  • An absent Boolean field differs from an explicit false value.
  • Use a status field when a concept has several mutually exclusive states.
  • Validation can prevent accidental Boolean type inconsistencies.
Knowledge Check

Which query specifically finds documents where the `isActive` field is absent?

Null, Missing Fields, and Undefined Values

One of the most important MongoDB distinctions is the difference between a field that contains null and a field that does not exist.

Consider the following documents:

{ name: "Arun", phone: null }
{ name: "Meera" }
{ name: "Ravi", phone: "9876543210" }

These represent three different situations:

  1. The phone field exists and its value is null.
  2. The phone field is absent.
  3. The phone field contains a string.

The distinction affects query results, aggregation, validation, and application logic.

Querying with null

db.users.find({ phone: null });

This query matches documents where phone is explicitly null and documents where phone is missing.

This behavior is useful to know, but it may be too broad when the application needs only one of those states.

Finding explicit null values

db.users.find({
 phone: { $type: "null" }
});

This identifies documents where phone contains the BSON null type.

Finding missing fields

db.users.find({
 phone: { $exists: false }
});

This identifies documents that do not contain the phone field.

Finding fields that exist and are not null

db.users.find({
 phone: {
 $exists: true,
 $ne: null
 }
});

This commonly selects documents where phone exists and is not null. It does not guarantee that the value is a valid phone number; type and format validation may still be needed.

Why missing fields occur

Missing fields can result from optional attributes, legacy document versions, incomplete migrations, conditional application writes, or inconsistent data contracts.

For example, an older user record may not contain emailVerified because the application did not introduce that field until a later version.

Null is not a universal substitute for missing

Use null when the field exists but has no known or applicable value, according to your data contract. Omit a field when its absence has a meaningful interpretation or when it is optional.

The correct choice depends on your application's semantics. Do not use null and missing interchangeably without considering how queries and validation interpret them.

Undefined values

JavaScript has an undefined value, but BSON's supported value model is not the same as JavaScript's. Do not design a MongoDB schema around storing undefined as though it were a normal, portable BSON value.

Modern MongoDB applications should use explicit values, null where appropriate, or omitted fields according to their data model. Legacy data may contain historical representations that need separate inspection and migration.

Compare null and missing fields
db.nullExamples.insertMany([
 { name: "Arun", phone: null },
 { name: "Meera" },
 { name: "Ravi", phone: "9876543210" }
]);

// Null or missing.
db.nullExamples.find({ phone: null });

// Explicit BSON null.
db.nullExamples.find({ phone: { $type: "null" } });

// Missing field.
db.nullExamples.find({ phone: { $exists: false } });

// Existing and non-null.
db.nullExamples.find({
 phone: { $exists: true, $ne: null }
});
💡 The first filter matches both explicit null and missing fields. The $type: "null" filter isolates explicit null values, while $exists: false identifies missing fields. The final query selects documents where the field exists and is not null, though it does not validate the phone number's format.
Key Takeaways
  • Null and missing fields represent different data states.
  • A query using { field: null } matches explicit null and missing fields.
  • $type: "null" identifies explicit BSON null values.
  • $exists: false identifies missing fields.
  • Schema design should define when to use null and when to omit a field.
Knowledge Check

Which query matches documents where `phone` is explicitly stored as BSON null rather than being absent?

Array Data Type and Nested Array Values

A BSON array stores an ordered sequence of values. Arrays are useful for tags, skills, prerequisites, addresses, line items, and other related collections of values.

Example:

{
 name: "Arun",
 skills: ["JavaScript", "Node.js", "MongoDB"]
}

Arrays of scalar values

An array can contain strings, numbers, Booleans, dates, or other supported BSON values.

{
 title: "MongoDB Fundamentals",
 tags: ["database", "backend", "mongodb"]
}

A query can match a document by an array element:

db.courses.find({ tags: "mongodb" });

This finds documents whose tags array contains the string mongodb.

Matching multiple elements

db.courses.find({
 tags: { $all: ["mongodb", "backend"] }
});

$all requires the array to contain all specified values. The elements do not need to be adjacent.

Exact array matching

db.courses.find({
 tags: ["mongodb", "backend"]
});

This is not the same as searching for documents that contain both values anywhere in the array. Exact array equality considers the array's contents and order.

Arrays of embedded documents

Arrays can contain documents:

{
 title: "MongoDB Mastery",
 lessons: [
 { title: "Introduction", durationMinutes: 20 },
 { title: "Queries", durationMinutes: 45 }
 ]
}

You can query nested fields inside arrays, but conditions involving multiple properties of the same array element require careful handling. The $elemMatch operator is often useful for that purpose.

Unbounded array risks

An array that grows indefinitely can lead to oversized documents, increasingly expensive updates, and inefficient queries. MongoDB has a maximum BSON document size of 16 MiB.

For example, storing every historical event for a busy user inside a single user document is often a poor design. A separate event collection may be more appropriate.

Array order and duplicate values

Arrays preserve element order and can contain duplicate values. Whether duplicates are allowed should be determined by the application requirements and validation strategy.

Do not assume that a MongoDB array automatically behaves like a mathematical set.

Query arrays and arrays of embedded documents
db.arrayExamples.insertMany([
 {
 title: "MongoDB Basics",
 tags: ["mongodb", "backend"],
 lessons: [
 { title: "Introduction", durationMinutes: 20 },
 { title: "Queries", durationMinutes: 45 }
 ]
 },
 {
 title: "React Basics",
 tags: ["react", "frontend"],
 lessons: [
 { title: "Components", durationMinutes: 30 }
 ]
 }
]);

// Match one array element.
db.arrayExamples.find({ tags: "mongodb" });

// Require both values somewhere in the array.
db.arrayExamples.find({
 tags: { $all: ["mongodb", "backend"] }
});

// Find an array element with a matching embedded field.
db.arrayExamples.find({
 "lessons.title": "Queries"
});
💡 The example inserts arrays of strings and arrays of embedded documents. A direct array-element query matches documents containing the specified value. $all requires all specified values in the array. Dot notation can query nested fields within arrays of documents.
Key Takeaways
  • Arrays preserve order and can contain duplicate values.
  • Queries can match array elements directly.
  • $all requires the array to contain all specified values.
  • Exact array equality differs from element matching.
  • Unbounded arrays can create document-size and performance problems.
Knowledge Check

What does `{ tags: { $all: ["mongodb", "backend"] } }` require?

Embedded Documents and Nested BSON Structures

An embedded document is a document stored as the value of a field inside another document.

Example:

{
 name: "Meera",
 address: {
 street: "Example Street",
 city: "Chennai",
 country: "India"
 }
}

The address field contains another document with its own fields.

Querying nested fields

Use dot notation to match a nested field:

db.users.find({ "address.city": "Chennai" });

Nested field projection

db.users.find(
 {},
 { _id: 0, name: 1, "address.city": 1 }
);

This projection returns the name and the nested city field while excluding _id.

Embedded document equality

MongoDB can compare an embedded document as a value, but exact embedded-document equality is stricter than checking a nested field. Field values and field order can matter in embedded-document equality comparisons.

For ordinary application queries, dot notation is often preferable when you want to match a particular nested property.

Embedded documents versus references

Embedding is useful when related information is commonly read and updated together and has an appropriate bounded size.

References are often useful when related records:

  • Have independent lifecycles.
  • Are shared by many parent records.
  • Grow without a practical bound.
  • Need to be queried independently.

For example, an address embedded in a user profile may be appropriate. A large history of transactions is generally better modeled separately.

Deep nesting

MongoDB supports nested documents, but extremely deep or complicated structures can make queries and application code difficult to maintain. The design should represent the domain clearly rather than maximizing nesting depth.

Field names containing dots or dollar signs

Dot notation uses dots to address nested paths, so ordinary application field names should avoid ambiguity with path syntax. MongoDB has restrictions and special handling for field names containing periods or beginning with dollar signs, with limitations across operations and tools. Prefer conventional field names unless a specific requirement justifies an unusual name.

Query nested documents
db.embeddedExamples.insertOne({
 name: "Meera",
 address: {
 city: "Chennai",
 country: "India"
 },
 preferences: {
 theme: "dark",
 notifications: {
 email: true,
 sms: false
 }
 }
});

// Query a nested city.
db.embeddedExamples.find({ "address.city": "Chennai" });

// Query a deeply nested Boolean.
db.embeddedExamples.find({
 "preferences.notifications.email": true
});

// Project selected nested fields.
db.embeddedExamples.find(
 {},
 { _id: 0, name: 1, "address.city": 1 }
);
💡 The example demonstrates nested objects, dot-notation filters, and nested projection. Dot notation lets the query target a particular property without requiring exact equality against the entire embedded document.
Key Takeaways
  • Embedded documents represent nested structures within a parent document.
  • Dot notation queries nested fields.
  • Exact embedded-document equality is stricter than matching a nested property.
  • Embedding and referencing should be selected according to access patterns and growth.
  • Unnecessarily deep nesting can make data models harder to maintain.
Knowledge Check

Which query selects users whose nested address city is Chennai?

Date Type: UTC, Time Zones, and Date Queries

MongoDB's BSON Date type represents a point in time as a signed 64-bit integer containing milliseconds since the Unix epoch. It does not store an arbitrary time-zone name as part of the Date value.

For example:

{
 createdAt: ISODate("2026-10-22T10:30:00Z")
}

The Z suffix indicates UTC.

BSON Date versus a string

These documents contain different types:

{ createdAt: ISODate("2026-10-22T10:30:00Z") }
{ createdAt: "2026-10-22T10:30:00Z" }

The first contains a BSON Date. The second contains a string. Store dates as BSON Dates when chronological comparisons and date arithmetic are required.

Creating dates in mongosh

new Date()

This creates a JavaScript Date representing the current instant. When inserted through the MongoDB shell, it is stored as a BSON Date.

A fixed date can be created using:

new Date("2026-10-22T10:30:00Z")

Filtering by a date range

db.events.find({
 createdAt: {
 $gte: ISODate("2026-10-01T00:00:00Z"),
 $lt: ISODate("2026-11-01T00:00:00Z")
 }
});

This selects events in October 2026 using an inclusive lower boundary and exclusive upper boundary.

Half-open ranges are useful because consecutive intervals can be joined without overlapping at the boundary.

Time zones

A Date represents an instant, not a local calendar label. A local time such as 9:00 a.m. in Asia/Kolkata should be converted to the corresponding UTC instant before being stored if it represents a specific moment.

For example, India Standard Time is UTC+05:30. A local time of 2026-10-22 09:00 in that time zone corresponds to 2026-10-22T03:30:00Z.

When working with user schedules, recurring events, or appointments, consider whether the application needs to retain the user's time-zone identifier in addition to the stored instant.

Date range filtering by local day

To query all records for a particular local calendar day, calculate the UTC start of that local day and the UTC start of the next local day using the intended time zone. Query with $gte and $lt using those two instants.

Do not assume that every local day is exactly 24 hours in every time zone; daylight-saving transitions can produce shorter or longer local days.

Date arithmetic

MongoDB's aggregation framework provides date operators for extracting components, formatting dates, calculating differences, and performing date arithmetic. These operations are covered in greater detail in the aggregation chapters.

Avoid storing queryable dates as strings

ISO-formatted strings may sort lexicographically in useful ways under restricted conditions, but they are not equivalent to BSON Dates and do not provide the same date-query semantics.

Insert dates and query a date range
db.dateExamples.insertMany([
 {
 event: "A",
 createdAt: ISODate("2026-10-01T00:00:00Z")
 },
 {
 event: "B",
 createdAt: ISODate("2026-10-15T12:30:00Z")
 },
 {
 event: "C",
 createdAt: ISODate("2026-11-01T00:00:00Z")
 }
]);

// October 2026 only.
db.dateExamples.find({
 createdAt: {
 $gte: ISODate("2026-10-01T00:00:00Z"),
 $lt: ISODate("2026-11-01T00:00:00Z")
 }
});
💡 The range includes the first instant of October and excludes the first instant of November. The result includes events A and B, but not C. Storing dates as BSON Date values allows MongoDB to perform chronological comparisons reliably.
Key Takeaways
  • BSON Date stores a point in time with millisecond precision.
  • A BSON Date does not retain an arbitrary time-zone identifier.
  • Use UTC instants consistently for date comparisons.
  • Half-open date ranges avoid overlapping consecutive periods.
  • Convert local-day boundaries using the intended time zone.
Knowledge Check

What does a BSON Date primarily represent?

BSON Timestamp versus BSON Date

MongoDB has a BSON Timestamp type that is distinct from BSON Date. This distinction is important because application developers sometimes assume that both types serve the same purpose.

BSON Date

BSON Date is designed for application date-and-time values such as:

  • createdAt
  • updatedAt
  • publishedAt
  • expiresAt
  • scheduledAt

Example:

{ createdAt: ISODate("2026-10-22T10:30:00Z") }

BSON Timestamp

BSON Timestamp is a special internal type consisting of a seconds component and an ordinal component. MongoDB uses it in internal contexts, including replication-related metadata and the oplog.

It is not generally the preferred type for ordinary application timestamps.

In mongosh, a Timestamp can be constructed using the shell helper:

Timestamp(10, 1)

This illustrates the type's two components; it is not an application timestamp such as October 22, 2026.

Why the distinction matters

A field called createdAt normally represents a real-world instant. BSON Date expresses that meaning directly and supports date operators and date comparisons.

BSON Timestamp is designed for specialized database uses and should not be selected simply because the field name contains the word timestamp.

Avoid relying on internal timestamps as business data

Do not treat an internal replication timestamp as a universal creation time for application documents. If your application needs an auditable creation time, store an explicit BSON Date field and define how it is populated and updated.

Date and Timestamp comparison
BSON Date: Application time values
Appropriate for event times, creation dates, expiry dates, and other application-level instants.
BSON Timestamp: Specialized internal type
Used in MongoDB internal contexts such as replication metadata.
Application audit field: Explicit Date field
Use a defined field such as createdAt when the application requires an auditable creation time.
Key Takeaways
  • BSON Timestamp and BSON Date are different BSON types.
  • BSON Date is the usual choice for application-level time values.
  • BSON Timestamp has specialized database uses.
  • A field's name does not determine its BSON type.
  • Store explicit application timestamps when business logic depends on them.
Knowledge Check

Which type should normally be used for an application's `createdAt` field?