BSON Data Types & ObjectId

MongoDB · Lesson 4 of 28 7 min read Updated

MongoDB documents look like JSON, but JSON only knows strings, numbers, booleans, null, arrays and objects. A database needs more than that: real dates, whole numbers that never lose precision, exact decimals for money and compact unique IDs. So MongoDB stores documents in BSON (Binary JSON), a binary format with many more types.

In this lesson you will learn the BSON types you will use every day, see how mongosh decides which number type to store, avoid the classic money and time-zone mistakes, and look inside an ObjectId.

The common BSON types

Each BSON type has a name (an alias) that you use when you check types in queries. These are the ones you will meet most often:

TypeAliasExample in mongoshTypical use
Stringstring'Pune'Text, always UTF-8, so Hindi text and the ₹ sign work
32-bit integerint250Counts, quantities, stock
64-bit integerlongLong('5000000000')Very large whole numbers
Doubledouble4.5Measurements, ratings
Decimal128decimalDecimal128('149.50')Money and other exact decimals
BooleanbooltrueYes/no flags
Datedatenew Date() or ISODate('2026-10-30')Points in time
ObjectIdobjectIdObjectId()Default _id values
Arrayarray['tea', 'spices']Lists
Objectobject{ city: 'Kochi' }Embedded documents
NullnullnullA known "no value"

There are a few more, such as binary data (binData), regular expressions (regex) and internal timestamps (timestamp), but the list above covers almost every application.

One document, many types

Create a test document that uses most of these types. The comments after // are allowed in mongosh, because it is JavaScript:

MongoDB Shell
use practice
db.typesdemo.insertOne({
  _id: 1,
  name: 'Masala Chai Mix',                         // string
  qty: 250,                                        // stored as int
  rating: 4.5,                                     // stored as double
  weight: 1.0,                                     // careful: stored as int
  views: Long('5000000000'),                       // long
  price: Decimal128('149.50'),                     // decimal
  inStock: true,                                   // bool
  addedOn: new Date('2026-10-30T09:30:00+05:30'),  // date: 9:30 am IST
  tags: ['tea', 'spices'],                         // array
  supplier: { name: 'Kochi Spices', city: 'Kochi' }, // object
  discount: null                                   // null
})
db.typesdemo.findOne()
Output
{ acknowledged: true, insertedId: 1 }
{
  _id: 1,
  name: 'Masala Chai Mix',
  qty: 250,
  rating: 4.5,
  weight: 1,
  views: Long('5000000000'),
  price: Decimal128('149.50'),
  inStock: true,
  addedOn: ISODate('2026-10-30T04:00:00.000Z'),
  tags: [ 'tea', 'spices' ],
  supplier: { name: 'Kochi Spices', city: 'Kochi' },
  discount: null
}

Two things in the output deserve attention: weight printed as 1, and the date moved from 09:30 to 04:00. Both are explained below.

To see the stored type of each field, you can ask the server with the $type expression. You will learn aggregate() properly in The Aggregation Pipeline; here it is just a tool:

MongoDB Shell
db.typesdemo.aggregate([
  { $project: { _id: 0, qty: { $type: '$qty' }, rating: { $type: '$rating' }, weight: { $type: '$weight' },
                views: { $type: '$views' }, price: { $type: '$price' }, addedOn: { $type: '$addedOn' } } }
])
Output
[
  {
    qty: 'int',
    rating: 'double',
    weight: 'int',
    views: 'long',
    price: 'decimal',
    addedOn: 'date'
  }
]

How mongosh chooses a number type

JavaScript has only one number type, so mongosh follows a simple rule: if a number is a whole number that fits in 32 bits (between about -2.1 billion and 2.1 billion), it is stored as an int; otherwise it is stored as a double. Since 1.0 and 1 are the same JavaScript number, weight: 1.0 became an int. If the type matters, say so explicitly with Double(1.0), Int32(250), Long('...') or Decimal128('...'). Pass large longs and decimals as strings, so JavaScript never rounds them first.

Numbers of different types still compare by value, so a query for { weight: 1 } matches an int 1, a double 1.0 and a long 1 alike. Problems appear only when code checks the type itself, or when a value is stored as a string.

Money: never use doubles

Doubles are binary fractions, and most decimal amounts cannot be stored exactly in them. You can see the effect in mongosh itself:

MongoDB Shell
0.1 + 0.2
Output
0.30000000000000004

That tiny error turns into wrong totals and failed comparisons once you add up thousands of payments. For money, use one of two safe designs:

  • Store Decimal128 values, which keep exact decimal digits: Decimal128('149.50').
  • Store whole paise as integers: ₹149.50 becomes 14950, and you divide by 100 only for display.

The practice shop in this course stores whole rupees as integers (price: 799), which is exact and keeps outputs simple.

Dates are stored in UTC

A BSON date is a count of milliseconds since 1 January 1970, always in UTC (Coordinated Universal Time). When you created the date with +05:30, the Indian time offset, MongoDB converted 09:30 IST into 04:00 UTC. mongosh prints dates with ISODate(...) and a Z at the end, which means UTC. Your application converts back to IST only when it shows the date to a person.

Use new Date() or ISODate() to create date values. Do not use Date() without new: in JavaScript that returns a string, and a date stored as a string cannot be compared or sorted as a date.

Inside an ObjectId

An ObjectId is 12 bytes, shown as 24 hexadecimal characters. The first 4 bytes are the creation time in seconds, the next 5 bytes are a random value chosen once per process, and the last 3 bytes are a counter. This makes ObjectIds unique without asking the server, and roughly sorted by creation time. You can read the time back:

MongoDB Shell
const id = ObjectId('6ae41d48c3f5e8a91d2b7a01')
id.getTimestamp()
id.toString()
Output
ISODate('2026-10-30T04:30:00.000Z')
6ae41d48c3f5e8a91d2b7a01

getTimestamp() returns the creation time and toString() returns the plain hex string, which is what you would put in a URL such as /orders/6ae41d48c3f5e8a91d2b7a01. Calling ObjectId() with no argument creates a brand-new one.

Common mistakes

  • Storing numbers as strings. price: '799' is text, so { price: 799 } will not match it and sorting puts '1299' before '799'. Store numbers as numbers.
  • Using doubles for money. Use Decimal128 or integer paise.
  • Storing dates as text. '30-10-2026' cannot be compared with other dates. Use real date values.
  • Expecting dates in local time. MongoDB returns UTC; convert for display.

Key points

  • MongoDB stores documents as BSON, which adds types such as int, long, double, decimal, date and ObjectId to JSON.
  • mongosh stores whole numbers that fit in 32 bits as int and everything else as double, so 1.0 becomes an int.
  • Use Decimal128 or integer paise for money, never doubles.
  • Dates are stored in UTC; new Date() and ISODate() create them, Date() returns a string.
  • An ObjectId holds a timestamp, a random value and a counter; getTimestamp() reads the time.

Exercise

In the practice database, insert a document into a payments collection for a UPI payment of ₹1,299.50 made by Priya at 6:45 pm IST on 2 November 2026. Store the amount exactly, store the time as a real date, and let MongoDB create the _id. Then read the document back and work out the UTC time you expect to see.

Show solution
MongoDB Shell
db.payments.insertOne({
  customer: 'Priya Nair',
  method: 'UPI',
  amount: Decimal128('1299.50'),
  paidAt: new Date('2026-11-02T18:45:00+05:30')
})
db.payments.findOne()

The amount prints as Decimal128('1299.50') and the date as ISODate('2026-11-02T13:15:00.000Z'), because 6:45 pm IST is 5 hours 30 minutes ahead of UTC. The _id is a new ObjectId whose timestamp is the moment you ran the insert.

Quick Quiz

1. In mongosh, which type is stored for { weight: 1.0 }?

2. Which type should you use for prices with paise, such as ₹149.50?

3. A document has createdAt: ISODate('2026-10-30T04:00:00.000Z'). What is that in IST?

4. What do the first 4 bytes of an ObjectId contain?

Learn MongoDB with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call