Understanding that a flexible schema doesn't mean no design, mastering the principle of data locality and the habit of writing query-oriented schemas, and recognizing anti-patterns such as massive arrays and excessive nesting along with their solutions.

Up to episode 5, you became proficient at writing queries in various forms. Now it's time for a more fundamental question: how should data be structured? This is the most often overlooked topic — many developers feel "schemaless" means being free to store data however you like. In fact, precisely because there's no rigid schema, you must be even more disciplined in designing it.
Episode 6 instills the mindset of a MongoDB schema designer. The roadmap: first we clear up the myth of "schemaless = no design", second we understand the golden principle of data locality, third we learn that schema must follow access patterns, and fourth we dissect the most common anti-patterns along with their solutions. By the end of the episode, you'll see a MongoDB schema as an architectural decision, not just a place to pile up JSON.
There's a dangerous misconception: because MongoDB doesn't enforce a schema, design isn't important. This is totally wrong. A flexible schema actually moves the responsibility from the database to you as the data architect. The database won't complain if you store an email as an array instead of a string — but your application will crumble when used in production.
Think about the difference: in an RDBMS, the schema is a constraint that protects you from messy data; in MongoDB, the schema is a decision you must make with full awareness. The key questions you must answer before writing your first document:
If you can answer those three questions, you already have a much better design foundation than most developers who just write carelessly.
One way to channel design discipline is documenting the schema. It can be through a JSON Schema file for validation (we cover that in episode 8), or through a library like Zod or Mongoose on the application side. The key: consistency of field structure — naming, data types, and units — must be agreed across the whole team, or the application will break when two services read the same field with different assumptions.
This is the number one principle of MongoDB schema design, often called data locality. In an RDBMS, you normalize data then combine it with JOINs when reading. In MongoDB, that combining is expensive — it's better to place data that is frequently read together in the same document.
The clearest example: a user profile page in a social app displays name, email, bio, and follower count all at once. In an RDBMS, that could mean combining three tables. In MongoDB, store everything in one document:
{
"_id": ObjectId("66xxxxxxxxxxxxxxxxxxxxxxxx"),
"username": "arman",
"email": "arman@example.com",
"bio": "Cloud engineer dan pembelajar seumur hidup",
"followerCount": 1280,
"profile": {
"location": "Jakarta",
"website": "https://arman.dev"
}
}A single find query retrieves all the data needed for the profile page. No JOIN, no second query. This is the main performance advantage of a document database.
Data locality usually means duplicating data in several places. For example, a user's name is duplicated into every order document they've ever created. This duplication is intentional — it trades update consistency (must be updated in many places) for read speed. We'll dissect embedding vs referencing in depth in episode 7; for now, remember the principle: place data based on how it's read, not based on normalization theory.
A good MongoDB schema isn't designed from the entity side ("what does a user have?"), but from the query side ("which queries run most often?"). Start by writing down the list of queries your application will execute most frequently, then design documents so each primary query can be resolved by reading as few documents as possible.
The practical steps:
db.orders.find({ userId: ObjectId("66xxxxxxxxxxxxxxxxxxxxxxxx") })The query above is very common: "show all of this user's orders". If userId isn't indexed, every execution becomes a full scan. Understanding access patterns like this will guide you in placing fields and choosing indexes — a topic we continue in episode 12.
First anti-pattern: arrays that grow without limits. The most common example: storing all product comments inside the product document. A viral product can get tens of thousands of comments, and every operation touching the product document must load that entire array — slow and memory-hungry.
{
"_id": ObjectId("66xxxxxxxxxxxxxxxxxxxxxxxx"),
"name": "Laptop Gaming",
"comments": [
{ "user": "budi", "text": "Keren!" },
{ "user": "siti", "text": "Harga oke" }
]
}The solution: separate comments into their own collection referencing the product. Arrays inside documents should be limited to "one-to-few" relationships — a few dozen elements at most, not thousands.
Second anti-pattern: embedding that is too deep and too wide. Documents nested beyond three levels become hard to read, hard to index, and hard to query. Complex nested documents make query paths like profile.address.history.locations[...] painful.
{
"user": {
"account": {
"settings": {
"notifications": {
"email": {
"marketing": true
}
}
}
}
}
}The solution: flatten the structure. Limit nesting depth to 3 levels maximum, and for truly hierarchical data (categories, organizations), consider patterns like an ancestors array or a separate collection.
Third anti-pattern: carrying over RDBMS habits — breaking every entity into tiny separate collections then stitching them together with $lookup on every query. This throws away the entire advantage of a document database. The indicator: your queries always start with many $lookups, and every data read requires dozens of documents to be combined.
The solution isn't full normalization or full denormalization, but the hybrid approach we cover in episode 7: embed data read together, reference data that is large or frequently changes on its own.
Info
There's a quick thinking framework to decide: ask "is this data always read together with the parent document?" If yes and it's small in quantity, embed it. If not, or if it's large, reference it. This decision is rarely black-and-white — that's why MongoDB gives you the freedom to choose, and why you must choose deliberately.
In episode 6 you understood that a flexible schema actually demands more disciplined design, mastered the main principle of data locality — data accessed together is stored together — and learned to design schemas oriented toward access patterns, that is, arranging documents based on the queries most frequently executed. You also recognized the three main anti-patterns: massive arrays that grow uncontrollably, excessive nesting beyond three levels, and over-normalized schemas that bring RDBMS habits into MongoDB.
Key takeaways:
In the next episode, episode 7, we dissect one of the most important decisions in design: Data Modeling: Embedding vs Referencing. You'll learn when to embed documents, when to reference with ObjectId, the one-to-few, one-to-many, and many-to-many patterns, and the hybrid approach that combines both. See you in episode 7!