Practice this topic in a realistic system design interview
Document databases store records as JSON-like documents instead of spreading every piece of data across many tables.
They are useful when the data naturally belongs together: a product with its specifications, a user profile with preferences, an article with extra details, or an application setting with nested options.
The core idea is simple: store data in the shape your application usually reads and writes.
If the application usually needs one complete object, storing that object as one document can be efficient and easy to work with.
Document databases are not just "relational databases without schemas." They have their own strengths and their own ways to get into trouble.
With good modeling, they make nested data simple to store and query. With careless modeling, they can create duplicated data, inconsistent records, and documents that grow without limit.
This chapter covers the document model, how to design documents well, and where document databases fit.
Loading simulation...
A document is a structured record, usually represented as JSON or a binary JSON-like format such as BSON. It can contain simple values, nested objects, arrays, and optional fields.
Here is a document representing a blog post:
Nested objects like author and stats group related fields. Arrays such as tags and comments store repeated values.
Other posts might have optional fields such as featured_image or series_id. Some posts might have no comments at all.
The comment also stores a copy of user_name. That makes rendering the post easier because the application does not need another read just to show the commenter's name.
Documents are grouped into collections. A collection is similar to a table, but every document in the collection does not have to contain the exact same fields.
The terminology maps fairly directly:
Document databases are often called schemaless, but in production, schemaless should not mean "anything goes."
Documents in the same collection usually share a common shape because the application needs predictable data. The flexibility is for controlled change and natural variation, not for skipping data design.
Every document needs a unique ID. MongoDB uses _id; other databases have similar fields.
Common ID choices include:
The ID choice affects indexes, URLs, data imports, replication, and sharding. Random IDs spread writes well but may make indexes less cache-friendly. Time-ordered IDs keep nearby records closer together, but they can create write hotspots if used carelessly as shard keys.
The most important modeling decision in a document database is whether related data should live inside the same document or be referenced by ID.
Relational design often starts by separating data into tables. Document design starts with a different question:
What does the application usually read and write together?
Embedding means storing related data inside the parent document:
This is often a good model for orders. An order should preserve customer and product details as they were at purchase time. If the customer changes their email later, old order history usually should not change.
Referencing means storing IDs that point to other documents:
To display the full order, the application usually fetches the order, customer, and products separately, or uses a database feature that performs a lookup.
Several factors push the decision one way or the other. The table below gives a practical rule of thumb.
| Factor | Favor Embedding | Favor Referencing |
|---|---|---|
| Access pattern | Read together most of the time | Read independently |
| Relationship size | One-to-one or one-to-few | One-to-many or many-to-many |
| Growth | Has a clear limit | Can grow without limit |
| Update frequency | Rarely changes, or changes with parent | Changes independently |
| Consistency need | A saved copy is acceptable | One official copy is needed |
| Write pressure | Few writers update it | Many writers update related data |
Embed small data with a clear size limit that is read with the parent. Also embed saved copies when history should preserve the old value.
Reference data that grows without limit. Also reference data that changes often and must have one official copy.
Avoid arrays that can grow forever, such as all comments on a viral post or all events for a user.
Document databases can query fields, nested fields, arrays, and indexes. The examples below use MongoDB syntax because many engineers recognize it, but the same ideas appear in other document databases with different APIs.
These MongoDB queries match on a nested field, a numeric range, array membership, and a date window.
Projection means returning only the fields the caller needs. It is just a way to ask for a smaller result:
Projection reduces network transfer and parsing work when documents contain fields the caller does not need.
Arrays are a natural fit for tags, small lists, and child records with a clear size limit:
Array queries are useful, but very large arrays are a warning sign. If the array can grow without a clear limit, consider a separate collection.
Many document databases support some form of aggregation. In MongoDB, an aggregation pipeline processes documents through steps:
The diagram below shows how documents move through each step, from raw orders to the final ten results.
Document databases are flexible, but they still work best when the application mostly reads and writes documents.
Single-document reads and writes are usually the best case. Queries by an indexed field work well when the index matches the query. Join-like lookups are possible in some systems, but they are not the main design target.
Multi-document transactions are supported in some systems, but they add extra work. Flexible reporting is often better served by a relational, columnar, or analytical store. Relationship-heavy queries are usually better in a graph database.
If most screens require assembling data from many collections, the model is probably a poor fit for the workload.
Indexes make queries fast by keeping a searchable structure next to the documents. Without the right index, a query may scan the entire collection.
Document databases offer several index kinds. Each one helps a different kind of query.
| Index Type | Use Case | Example |
|---|---|---|
| Single field | Query by one field | email |
| Compound | Query or sort by multiple fields | {customer_id, created_at} |
| Multikey | Query values inside arrays | tags |
| Text | Basic text search | title, content |
| Geospatial | Location queries | nearby stores |
| TTL | Expire old documents | sessions or temporary tokens |
| Hashed | Spread data evenly for sharding | hashed user ID |
The commands below build common MongoDB indexes: single-field, compound, unique, partial, and TTL.
Indexes speed up reads but slow down writes, because each insert, update, or delete may need to update multiple indexes. Indexes also use memory and storage. In large systems, indexes can become a major part of what the database keeps hot in memory.
Compound index order matters because the index should match how queries filter and sort. Array indexes can grow quickly because a large array may create many index entries for one document.
Indexes do not fix a poor model. If the application constantly needs to assemble many scattered records, another model may be better.
Document databases are often described as schemaless, but production systems still need rules.
Modern document databases usually support some form of schema validation. Applications also commonly validate documents before writing them.
The goal is to keep flexibility where it helps and enforce rules where bad data would be expensive to repair.
The settings control how strictly validation is applied. validationLevel: "strict" checks inserts and updates. validationLevel: "moderate" checks only documents that already match the rules.
For the action, validationAction: "error" rejects invalid documents. validationAction: "warn" allows invalid documents but logs a warning.
Schema validation is not a replacement for modeling. It is a guardrail.
You still need clear ownership of document shape, migrations for old documents, and a plan for old and new application versions to work during rollout.
Most document databases provide atomic updates to a single document. Atomic means all-or-nothing.
This is one reason embedding related data is useful: the whole document can be updated safely in one operation.
Multi-document transactions are supported in several modern document databases, but they are not free. They add extra work between records, error handling, conflicts, and performance cost.
A single-document update is the best fit because it is simple and usually atomic. A transaction across a few documents is useful when correctness requires it.
A large distributed transaction is usually a sign to revisit the model. A workflow across services is often better handled with events, sagas, or explicit state machines.
The design goal is to model common writes so they usually touch one document, and reserve multi-document transactions for cases where they are needed.
Consistency also depends on read settings, write settings, replication mode, and the database's specific guarantees. Always check the behavior of the system you are using.
Many document databases support scaling across machines through partitioning or sharding. Sharding means splitting documents across nodes based on a shard key.
The concepts below use MongoDB terminology, but the design ideas apply broadly.
A query router sits between the application and the shards. It uses shard mapping information to send each request to the node that holds the matching data.
The shard key determines where documents live. A poor shard key can overload one shard or force every query to touch every shard.
| Shard Key Property | Good | Risky |
|---|---|---|
| Number of distinct values | Many distinct values | Few values, such as status |
| Distribution | Even load | One tenant or value dominates |
| Write pattern | Spreads writes | All new writes hit one shard |
| Query pattern | Included in common queries | Rarely used in filters |
| Locality | Keeps related data together | Splits common reads across shards |
Good shard keys depend on the workload. A tenant_id can work for multi-tenant systems if tenant sizes are balanced. A hashed user ID can spread writes evenly. A compound key can keep useful data together while still spreading load.
Bad shard keys usually have too few distinct values, always increase over time, or do not appear in common queries.
When a query includes the shard key, the router can send it to the right shard. When it does not, the router may need to query every shard and merge the results.
Scatter-gather queries may be acceptable for rare admin operations. They are usually a poor fit for high-traffic user requests.
Document databases differ in query model, consistency guarantees, hosting, and day-to-day operations.
MongoDB is a widely used general-purpose document database. It supports rich queries, secondary indexes, aggregation pipelines, schema validation, replica sets, sharding, change streams, and multi-document transactions.
It is a common choice for flexible application data, catalogs, content systems, user profiles, and event-like records.
Firestore is a serverless document database commonly used for mobile and web applications. It provides real-time listeners, offline support in client SDKs, automatic scaling, and a query model built around collections and documents.
Its query model is more limited than MongoDB's. There are no joins. Multi-field queries usually require explicit composite indexes. Firestore does not support an aggregation pipeline. Plan reads carefully when many filters need to combine.
It is a good fit for client-facing apps that benefit from real-time sync and managed operations.
CouchDB is built around JSON documents, HTTP APIs, and replication. It fits offline-first and occasionally connected use cases, where replicas may drift apart and later sync.
It is a good fit when replication and conflict handling are central requirements.
Amazon DocumentDB is an AWS-managed document database with MongoDB API compatibility. It can be useful for teams that want a managed AWS service and can work within its compatibility and feature limits.
It is important to check compatibility carefully instead of assuming it behaves exactly like MongoDB.
Choose a document database when the application usually reads and writes one complete object. They also fit when data is nested or variable, with optional fields, nested structures, or type-specific attributes.
They are useful when the product changes often and you need controlled flexibility in the data shape. They also help when duplicating saved copies of data makes common reads simpler and faster.
Sharding fits best when common queries usually include the partition or shard key.
Consider another database type when strict relational rules are central and foreign keys, joins, and constraints are core to the workload. A relational or graph database may model the problem better when many-to-many relationships dominate.
Documents that grow without limit create operational problems through large arrays and ever-growing records. Frequent flexible analytics are usually better served by analytical stores built for broad scans and reports. A key-value store may be simpler when the data is mainly exact-key lookup.
Document databases work best when the document boundary is clear. If you cannot explain what belongs in one document and what does not, the model needs more thought.
Document databases trade strict table structure for flexible JSON-like documents with nested objects and arrays. The schema is flexible by default, but production systems still need validation and clear rules.
Relationships are handled by embedding small data that belongs with the parent and referencing shared data or data that can grow without limit.
Single-document operations are the natural fit. Multi-document transactions vary by system. Important field, array, sort, and shard-key queries need indexes. Sharding works best when queries include the shard key.
The core design skill is choosing document boundaries: embed what is small, limited in size, and read together; reference what is shared, updated on its own, or able to grow without limit; and add validation and indexes deliberately.
10 quizzes