MongoDB — Overview

Updated 6 interview angles 4 min read source
On this page8
  1. The model
  2. Embedding vs referencing
  3. CRUD
  4. Async with Motor
  5. Schema design patterns
  6. Other files in this folder
  7. When NOT to use MongoDB
  8. Interview angle

MongoDB — Overview

Document database. Schemaless-ish (no fixed schema at the DB level, but you should treat documents as having a logical schema). Single-document atomicity by default; multi-document transactions since 4.0.

The model

javascript
// A collection (~ table) of documents (~ rows)
{
  "_id": ObjectId("..."),
  "name": "Alice",
  "email": "alice@example.com",
  "addresses": [
    { "type": "home", "city": "Berlin" },
    { "type": "work", "city": "Amsterdam" }
  ],
  "preferences": { "lang": "en", "newsletter": true }
}

Nested objects, arrays — first-class. No JOINs traditionally; either embed related data or use $lookup (modern aggregation).

Embedding vs referencing

The most important MongoDB design decision.

Embed when

  • Data is accessed together >80% of the time.
  • The embedded data is owned by the parent (orders’ line items, post’s comments, user’s addresses).
  • Lifetime ≤ parent’s lifetime.
  • Size is bounded (no unbounded arrays).
javascript
// Order with embedded line items
{
  "_id": "ord_42",
  "user_id": "user_7",
  "items": [
    {"sku": "X1", "qty": 2, "price": 9.99},
    {"sku": "Y2", "qty": 1, "price": 19.99}
  ],
  "total": 39.97
}

Reference when

  • Related data is shared across many parents.
  • Updates to related data should be one-write.
  • Size is unbounded.
javascript
{
  "_id": "ord_42",
  "user_id": "user_7"         // reference
}

// Followed by:
{
  "_id": "user_7",
  "name": "Alice",
  "email": "..."
}

Two reads instead of one — but updates to the user are one document.

Hybrid: extended reference

Embed a few frequently-accessed fields from the referenced entity to skip the join on hot paths:

javascript
{
  "_id": "ord_42",
  "user_id": "user_7",
  // denormalized for display
  "user_name": "Alice",
  "user_email_hash": "..."
}

Refresh when the user changes — eventual consistency on the embedded copy.

CRUD

python
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017")
db = client.myapp

# Insert
db.users.insert_one({"name": "Alice", "email": "alice@example.com"})

# Find
user = db.users.find_one({"email": "alice@example.com"})
for u in db.users.find({"active": True}).sort("created_at", -1).limit(20):
    print(u)

# Update — atomic operators
db.users.update_one(
    {"_id": user_id},
    {"$set": {"name": "New Name"}, "$inc": {"login_count": 1}}
)

# Delete
db.users.delete_one({"_id": user_id})

Update operators are atomic at the document level: $set, $inc, $push, $pull, $addToSet, $unset. Multiple operators in one update are all applied atomically.

Async with Motor

python
from motor.motor_asyncio import AsyncIOMotorClient

client = AsyncIOMotorClient("mongodb://localhost:27017")
db = client.myapp

async def get_user(user_id):
    return await db.users.find_one({"_id": user_id})

async def list_users():
    async for user in db.users.find({"active": True}):
        yield user

Same API as pymongo with await. Use Motor in FastAPI / async Django.

Schema design patterns

Bucket pattern (time-series)

Group time-series points into per-hour or per-day buckets:

javascript
{
  "_id": ObjectId("..."),
  "sensor_id": "s42",
  "hour": "2026-05-12T18:00:00Z",
  "samples": [
    {"t": "2026-05-12T18:00:01Z", "value": 23.4},
    {"t": "2026-05-12T18:00:02Z", "value": 23.5}
  ],
  "sample_count": 2,
  "min": 23.4,
  "max": 23.5
}

One write per sample (push to the current bucket), one read per hour. Beats one-document-per-sample at scale.

Subset pattern

Heavy fields (full history, all reviews) stored separately; the parent embeds only the most recent N:

javascript
{
  "_id": "product_42",
  "name": "Widget",
  "latest_reviews": [...]    // most recent 10
}

Full review history in a separate reviews collection. Read for product page is cheap; “see all reviews” pages do a separate query.

Computed pattern

Pre-aggregate values at write time:

javascript
{
  "_id": "product_42",
  "review_count": 142,
  "avg_rating": 4.3,
  "ratings_breakdown": {"5": 80, "4": 40, "3": 15, "2": 5, "1": 2}
}

Avoid expensive aggregations on every read. Update on each new review (with $inc).

Polymorphic pattern

Different document shapes in one collection, distinguished by a type field:

javascript
{ "_id": ..., "type": "photo", "url": "...", "width": 1024, "height": 768 }
{ "_id": ..., "type": "video", "url": "...", "duration": 120, "codec": "h264" }

Single collection, single query. Application code branches on type.

Other files in this folder

Deep dives on specific topics:

When NOT to use MongoDB

  • Strong relational requirements (heavy JOINs, complex transactions across many tables).
  • Strict schema validation as a primary need (Postgres’ JSONB + check constraints can match flexibility with stronger guarantees).
  • Analytics workloads (Postgres / Snowflake / BigQuery are better at aggregations on huge datasets).
  • Small-data CRUD where you’d benefit from Postgres’ richer ecosystem (advisory locks, listen/notify, foreign keys).

When MongoDB shines:

  • Document-shaped data with deeply nested structure.
  • High write throughput on a single document type.
  • Horizontal scaling needs (sharding is first-class).
  • Rapid schema iteration during product development.

Interview angle 6

  • “Embedding vs referencing — decision criteria?” — embed when data is accessed together, owned by parent, lifetime ≤ parent, and bounded in size. Reference when data is shared, frequently updated independently, or unbounded. Hybrid: extended reference (embed a few hot fields, reference the rest).
  • “How does MongoDB handle atomicity?” — atomic at the single document level by default. Multi-document transactions added in 4.0 (replica sets) and 4.2 (sharded); slower; use sparingly.
  • “What’s a good use case for the bucket pattern?” — time-series data (IoT sensors, metrics). Group per-hour or per-day to reduce document count, improve compression, fit more in memory.
  • “When would you NOT use MongoDB?” — heavy relational JOINs, strict normalized schema, complex analytics on huge datasets. Postgres with JSONB often matches MongoDB’s flexibility with stronger guarantees.
  • “Async Python driver?” — Motor (motor.motor_asyncio.AsyncIOMotorClient). Same API as pymongo with await. Use in FastAPI / async Django.
  • “What’s the 16 MB document limit and how do you handle larger data?” — hard limit per document. Solutions: split (bucket / subset patterns), reference (separate collection), or for true binary blobs, GridFS (which splits files into chunks).