MongoDB Schema Design Best Practices
· 3 min read · Web Dev · Written for MongoDB
TL;DR MongoDB has no rules, no process and no algorithm for splitting up your data: you design around how your app uses it. Favor embedding unless there is a compelling reason not to, and never let an array grow without bound.
“How do I model my schema for my application?” It’s one of the most common questions developers ask about MongoDB, and the answer is: it depends. This talk is how I think about that answer, with real examples, for anyone coming to document databases from SQL.
What the talk covers
Relational vs. MongoDB schema design. In SQL, you model your data independently of your queries. You normalize to third normal form, split everything into tables, and join it back together with foreign keys. MongoDB has no rules, no process and no algorithm for that. You design around how your app actually reads and writes the data: what the app reads together, whether the app is read-heavy or write-heavy, and how the data set will grow.
Embedding vs. referencing. Those are the two tools you have. Embedding puts related data right in the document, so you get everything back in a single query and update it in one atomic operation, as long as the document stays under the 16 MB limit. Referencing keeps documents small and avoids duplicating data, but you pay for it with a second query or a $lookup.
Every kind of relationship, with an example:
- One-to-one: embed it. Key-value pairs right in the document.
- One-to-few: embed it in a small array that isn’t going to grow much.
- One-to-many: a product made of thousands of parts. Reference the parts by ID from the product.
- One-to-squillions: the logs from a burning server farm. Too many for any array, so it’s a reverse reference: each log message keeps track of its host’s ID.
- Many-to-many: a shared to-do list, where users have many tasks and tasks have many owners. Keep an array of references on each side.
My rules of thumb
- Favor embedding unless there is a compelling reason not to. It’s your default.
- Needing to access an object on its own is a compelling reason not to embed it.
- Avoid joins and
$lookupif you can, but they’re not an anti-pattern. Most people coming from SQL split their data up by default, out of habit. Use a join when you have a real reason to. - Arrays shouldn’t grow without bound. An unbounded array anywhere in your data set is a huge code smell.
Above everything else, how you model your data depends entirely on the needs of your application.
Where I’ve given it
MidwestPHP (Minneapolis, April 2020), Data Architecture Day (May 2020) and Conf42 JavaScript (2020). Other recordings:
If you’re coming from SQL, my talk From SQL to NoSQL covers how to shift your thinking first.
Slides and resources
If you liked this post, consider sharing it on Hacker News, Bluesky, LinkedIn, or X.

Joe
Karlsson
I write about my homelab, AI tools, databases, and the weird stuff I build on the side.
More about me →