How to Identify Database Relationships Before Writing a Single Line of Code
I've seen juniors jump straight into writing schema code without stopping to think about how their collections relate to each other. Then halfway through the project, they realize their data model is wrong and everything needs to change. Save yourself that pain. Before you touch code, ask the right questions.
The Only Three Questions You Need
Every relationship in a database comes down to three questions. Ask these for any two collections — users and videos, playlists and videos, users and users — and the relationship type will tell itself.
Q1: Can one [A] have many [B]s?
Q2: Can one [B] have many [A]s?
Q3: Who creates or owns this entity?
That's it. From those answers, you land in one of four buckets.
The Four Relationship Types
One-to-One
One A, one B. Neither side holds multiples.
Example: A user has one profile. A profile belongs to one user.
User ──── Profile
This is the rarest relationship. If you find yourself using it a lot, ask whether you even need a separate collection or if you can just embed the data.
One-to-Many
One A has many Bs. But each B belongs to only one A.
Example: One user uploads many videos. Each video has exactly one uploader.
User ──── Video
──── Video
──── Video
This is the most common relationship you will build.
Many-to-One
Same thing as One-to-Many, just from the other direction. Many videos belong to one user. The relationship is the same — only your perspective changes.
Many-to-Many
One A has many Bs, AND one B has many As.
Example: One playlist has many videos. One video can be in many playlists.
Playlist A ──── Video 1
──── Video 2
Playlist B ──── Video 2
──── Video 3
Video 2 lives in both playlists. That's Many-to-Many.
The Decision Tree
When you're staring at two collections and not sure, walk through this:
Q1: Can one [A] have many [B]s?
│
├── NO
│ Q2: Can one [B] have many [A]s?
│ ├── NO → ONE-TO-ONE
│ └── YES → (Re-examine. This usually means you've got A and B backwards.)
│
└── YES
Q2: Can one [B] have many [A]s?
├── NO → ONE-TO-MANY
└── YES → MANY-TO-MANY
Walking Through Real Examples
Users and Videos
Q1: Can one user have many videos? Yes — a creator uploads hundreds of videos.
Q2: Can one video belong to many users? No — a video has one creator.
Result: One-to-Many (User → Videos)
// Video stores a reference to its owner
videos {
owner: ObjectId // points to users._id
}
Users and Plans
Q1: Can one user have many plans? No — a user has one active plan at a time.
Q2: Can one plan belong to many users? Yes — thousands of users can be on the Premium plan.
Result: Many-to-One (Users → Plan)
// User stores a reference to their plan
users {
plan: ObjectId // points to plans._id
}
Playlists and Videos
Q1: Can one playlist have many videos? Yes.
Q2: Can one video be in many playlists? Yes — the same video can appear in "Favorites," "Watch Later," and a custom playlist.
Result: Many-to-Many
// Playlist stores an array of video IDs
playlists {
videos: [ObjectId, ObjectId, ObjectId] // array of video._id
}
Users and Profile
Q1: Can one user have many profiles? No.
Q2: Can one profile belong to many users? No.
Result: One-to-One
// Option A: Just embed it inside the user document
users {
username: String,
avatar: String,
bio: String
}
// Option B: Separate collection with a reference
profiles {
userId: ObjectId // points to users._id
}
For One-to-One, I usually go with Option A unless the embedded data is very large or only needed occasionally.
Who Stores the Reference?
Once you know the relationship type, the next question is: which collection holds the foreign key?
Here's the rule I follow:
| Relationship | Who holds the reference | Example |
|---|---|---|
| One-to-Many | The MANY side holds it | videos.owner → users._id |
| Many-to-One | The MANY side holds it | users.plan → plans._id |
| One-to-One | Whichever side you query from more often | profiles.userId → users._id |
| Many-to-Many | The "container" side holds an array | playlists.videos[] → videos._id |
The logic behind this is simple: you want to avoid storing arrays that grow unbounded. If you put videos: [...] on the user document, that array grows every time the user uploads. Put the reference on the video instead (video.owner) and the user document stays small forever.
An Important Example: Videos Don't Reference Playlists
I get this question a lot. Why does the playlist store the video IDs, not the other way around?
Think about it practically. A video doesn't know which playlists it's in, and it doesn't need to. When you open a playlist, you want to load its videos. So the playlist is the one doing the looking up.
// ✅ Correct — playlist knows its videos
playlists {
videos: [videoId1, videoId2, videoId3]
}
// ❌ Wrong — video would need to track every playlist it's in
videos {
playlists: [playlistId1, playlistId2, ...] // this grows unpredictably
}
If you stored it on the video side, every time someone adds a video to a new playlist, you'd have to update the video document. That's messy and hard to manage.
One More Question Worth Asking
Does [B] need [A] to exist?
A comment cannot exist without a video. So video is a required field on the comment document.
comments {
owner: ObjectId, // required
video: ObjectId, // required — a comment must belong to a video
content: String
}
This tells you whether the field should be required or optional in your schema. It also tells you what to clean up when a parent gets deleted (cascading deletes).
The Quick Summary You Can Reuse
Any time you're designing two collections, ask:
Can one [A] have many [B]s?
Can one [B] have many [A]s?
Who creates or owns this entity?
Which side will I query from most often?
Does [B] need [A] to exist?
Those five questions will get you the right answer every time.
Let's Connect
| Platform | Link |
|---|---|
| https://www.instagram.com/kanchannath.webdev | |
| GitHub | https://github.com/kanchan-nath |
| https://www.linkedin.com/in/kanchan-nath | |
| Hashnode | hashnode.com/Kanchan Nath |
FAQs
Q: I identified a Many-to-Many relationship. Do I always need a junction collection?
Not always. If you just need to store the relationship with no extra data, an array of ObjectIds on one side works fine — like playlists.videos[]. But if the relationship itself has extra data (like when a user subscribed, or their watch progress), then yes, create a separate junction collection.
Q: When should I embed data instead of referencing it?
Embed when the data is always read together, doesn't change often, and is not shared across multiple documents. A user's address is a good embed candidate. A video that multiple playlists reference is not — reference it instead.
Q: My relationship feels like it could be both One-to-Many and Many-to-Many. How do I decide?
Ask whether the real-world rule allows multiple ownership. A video has one creator — even if a team made it, the system has one owner. That's One-to-Many. A video can be in multiple playlists — nothing prevents that. That's Many-to-Many. Business rules decide this, not the database.
Q: Can I change the relationship type later?
Technically yes, but it's painful in production. That's why I'm pushing you to think about this before writing code. Changing a One-to-Many to Many-to-Many means restructuring documents and writing migration scripts. Get it right early.
Q: Does the relationship type affect query performance?
Yes. One-to-Many with a reference on the child side is the most efficient in MongoDB — a simple indexed lookup. Many-to-Many with large arrays can slow down as the array grows. For high-volume many-to-many relationships (like likes or subscriptions), always use a separate junction collection with indexes on both foreign keys.
Q: Is there a rule for when to use an array of ObjectIds vs a junction collection?
If the array will stay small and bounded (a playlist with maybe 50-100 videos), array is fine. If it's unbounded (a user's subscribers, a video's likes), always use a junction collection. Arrays that grow to thousands of entries will hurt your query performance and hit MongoDB's 16MB document limit eventually.

