MongoDB Cheatsheet

Insert and Read

Use this MongoDB reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Inserting

insertOne and insertMany are the only insert methods. MongoDB generates _id as an ObjectId when you leave it out, and the collection and database are created on the first successful write.

db.orders.insertOne({
  customer_id: ObjectId("65f1a2b3c4d5e6f701234567"),
  status: "paid",
  total: NumberDecimal("119.98"),
  items: [{ sku: "A-1", qty: 2, price: NumberDecimal("59.99") }],
  placed_at: new Date()
})
// { acknowledged: true, insertedId: ObjectId("6612...") }

db.orders.insertMany([
  { sku: "A-1", qty: 1 },
  { sku: "B-2", qty: 3 }
])
// { acknowledged: true, insertedIds: { '0': ObjectId(...), '1': ObjectId(...) } }

insertedIds is keyed by the array position, as a string, not by any field of the document. Grab the id you need with res.insertedIds[2].

OptionEffect
{ ordered: true }Default. Stop at the first failing document
{ ordered: false }Attempt every document, report all failures at the end
{ writeConcern: { w: "majority" } }Wait for a majority of replica set members
{ bypassDocumentValidation: true }Skip a $jsonSchema validator on this call

Ordered vs unordered

This is the option people get wrong. Neither mode is a transaction. A failure part way through an ordered insertMany leaves the earlier documents inserted.

try {
  db.users.insertMany(
    [ { _id: 1 }, { _id: 1 }, { _id: 2 } ],   // second one is a duplicate key
    { ordered: false }
  )
} catch (e) {
  print(e.result.nInserted)      // 2, both _id 1 and _id 2 landed
  printjson(e.writeErrors.map(w => w.index))   // [1]
}
ModeOn a duplicate at index 1Inserted
ordered: trueStops immediately, throwsOnly index 0
ordered: falseSkips it, keeps going, throws at the endIndex 0 and index 2

Unordered is faster (the server may parallelize) and is what you want for an idempotent bulk load where duplicates are expected and harmless.

find and findOne

find returns a cursor, lazily. findOne returns one document or null and takes no cursor methods.

db.orders.find()                       // every document, same as find({})
db.orders.find({})                     // identical
db.orders.find({ status: "paid" }, { total: 1, status: 1 })
db.orders.findOne({ _id: ObjectId("6612...") })
db.orders.findOne()                    // some single document, no order guaranteed

find() and find({}) are the same call. There is no implicit LIMIT, so on a large collection always attach .limit() when you are just looking.

Cursor methods

Chain these on find before the cursor is consumed. Nothing is sent to the server until you iterate.

MethodDoes
.sort({ placed_at: -1 })Order, 1 ascending and -1 descending
.limit(n) · .skip(n)Window. Large skip values are slow
.toArray()Materialize everything into a JS array
.forEach(d => ...)Stream one document at a time, no full array in memory
.map(d => ...)Transform into an array client side
.hasNext() · .next()Manual iteration
.tryNext()Non-blocking next, returns null rather than waiting. Used with change streams
.batchSize(n)Documents per network round trip
.count()Deprecated, use countDocuments
.itcount()Iterate and count, mostly a debugging tool
.projection({ a: 1 })Same as the second find argument
.hint({ status: 1 })Force an index
.collation({ locale: "en", strength: 2 })Case-insensitive compare and sort
.maxTimeMS(2000)Kill the operation server side after a deadline
.allowDiskUse()Let a blocking sort spill to disk
.explain("executionStats")Plan plus real counters
.close()Release the server cursor early
db.orders.find({ status: "paid" })
  .sort({ placed_at: -1 })
  .limit(20)
  .forEach(o => print(`${o._id} ${o.total}`))

Idle cursors are dropped by the server after about 10 minutes. Either finish iterating or call .close().

Counting and distinct

Two counts exist and they are not interchangeable.

CallAccuracyCostFilter
db.orders.countDocuments(filter)Exact, reads dataScans an index or the collectionYes
db.orders.estimatedDocumentCount()Metadata estimateConstant timeNo filter allowed
db.orders.countDocuments({ status: "paid" })
db.orders.countDocuments()               // exact, still walks an index
db.orders.estimatedDocumentCount()       // instant, may be stale
db.orders.distinct("status")             // [ 'cancelled', 'paid', 'pending' ]
db.orders.distinct("items.sku", { status: "paid" })

estimatedDocumentCount reads collection metadata, so it can be wrong after an unclean shutdown, and it is not supported inside a transaction. distinct returns a single array, so it fails once the result would exceed the 16MB BSON limit. For a big cardinality use a $group instead.

Dot notation

One quoted string reaches into subdocuments and array positions. Quoting is mandatory, because a bare address.city is not valid JavaScript as a key.

db.users.find({ "address.city": "Lisbon" })
db.users.find({ "address.geo.lat": { $gt: 38 } })
db.orders.find({ "items.sku": "A-1" })      // any element of items
db.orders.find({ "items.0.sku": "A-1" })    // only the FIRST element
db.orders.find({ "tags.2": { $exists: true } })   // has at least 3 tags
PathMatches
"items.sku"Any element of items whose sku matches
"items.0.sku"Only element zero
"tags.0"The first array entry, by position
"a.b.c"Nested subdocuments, any depth

Numeric path segments are ambiguous by design. On an array they mean the index, and on a subdocument with a literal "0" key they mean that key. Avoid numeric object keys.