skip to content

In a many-to-many relationship, which side should hold the array of references?

level: seniorimportance: should knowfreq 40%

answer

  1. Both sides could hold an array
  2. Ask what bounds each side's list
  3. Asymmetric cardinality picks the side for you
  4. The pairing may carry its own data
  5. Paging the relationship rules out arrays

basics

~20 s

Put the array on the side whose list is genuinely bounded, and never on a side that can grow without limit. When both sides are unbounded, or the relationship carries its own attributes, give it a collection of link documents.

solid answer

~50 s

There are three shapes and the cardinality picks between them. **One-sided array**: the bounded side stores identifiers of the other — a book holds its three authors. Queries in that direction are one read; the reverse direction needs an index on the array and a query against it, which is fine as long as the array side is the small one. **Two-sided arrays**: both records list the other. It makes both directions a single read, but the two lists must be kept in agreement across documents with no cross-document atomicity, so it is only defensible when both sides are small and drift is repairable. **Link documents**: one record per pair, indexed on both identifiers. This is the answer when either side is unbounded, when the pairing itself has attributes — enrolled_at, role, grade — or when you need to page the relationship. It costs an extra read but it scales in both directions and never inflates a parent.

code

json · 5 lines
json
{
  "_id": "book-3",
  "title": "Modelling Documents",
  "authorIds": ["a-11", "a-42"]
}

go deeper

for a junior

Know that a many-to-many can be stored as an array of identifiers on one side, and that the small side is the one that should carry it.

for a middle

Explain all three shapes and what each costs to read, to update and to delete, including why the reverse-direction query needs an index on the array field.

for a senior

Argue from asymmetric cardinality and from growth over time, and recognise the two signals that force link documents: an unbounded side, and a relationship carrying its own attributes.

for a principal

Own the call when the relationship itself becomes a domain concept with a lifecycle, and decide when it graduates into its own entity with its own ownership, retention and query surface.

## Three shapes, not two A many-to-many relationship in a document store can be expressed three ways, and the interview question is which one and why. **Array on one side.** One collection's documents carry an array of the other side's identifiers. Books hold `authorIds`. Reads in that direction are a single fetch; reads in the reverse direction query the array field, which requires an index over it. **Arrays on both sides.** Books hold `authorIds` and authors hold `bookIds`. Both directions are one read, at the cost of storing the relationship twice. **Link documents.** A separate collection holds one small document per pair, indexed on each identifier. Neither parent stores anything about the relationship. ## Cardinality decides first Look at the maximum size of each side's list, not the average. A book has a handful of authors; an author may have a handful of books. Both bounded — an array on either side works, and the one-sided array on the book is the usual choice because it is the side written when the relationship is created. A post has a handful of tags; a tag has millions of posts. Wildly asymmetric — the array goes on the post, never on the tag. Putting it on the tag reproduces the unbounded-array failure exactly: a hot tag's document grows until writes fail. Students and courses at a university are both potentially large and both keep growing over years. Neither side is a safe home for the array; this is link-document territory. The rule that falls out: **the array lives on the bounded side, and if no side is bounded, no side gets the array.** ## Which direction do you query? Cardinality narrows the choice; access patterns finish it. If you only ever ask "which authors wrote this book" and never "which books did this author write", a one-sided array is complete. If you ask both, you either index the array and query it from the other direction — perfectly workable, one extra query — or you accept the duplication of two-sided arrays. Two-sided arrays deserve scepticism. They buy a single read in each direction and they cost you a consistency problem: adding a pair means writing two documents, and there is no atomicity across them. Every failed second write leaves a relationship that exists from one side and not the other, and that asymmetry is invisible until someone queries the wrong direction. If you choose it, you need a reconciliation job, and at that point a link collection is often simpler. ## When the relationship has its own data The strongest signal for link documents is that the pairing carries attributes. A student's *grade* in a course, the *date* an employee joined a project, the *role* a user holds in an organisation — these belong to the pair, not to either side. Squeezing them into an array of objects on one side works only while that side stays bounded, and it makes the data awkward to query from the other direction. A link document has an obvious home for them, can be indexed on them, and can be sorted and paged. Link documents also give you a relationship you can page. If a course has 40,000 enrolments, no array-based shape lets you fetch page seventeen of them cheaply; a link collection with an index on `courseId` does exactly that. ## Growth over time A relationship that looks bounded today may not be. Ask what stops it growing. An author's book list is bounded by a career; a user's list of "organisations I belong to" is bounded by plausibility; a user's list of "documents I have viewed" is bounded by nothing at all and must never be an array. Time-accumulating relationships are unbounded by construction, whatever their current size. ## Update and delete cost Each shape has a different maintenance profile. With a one-sided array, creating and removing a pair is a single atomic update to one document — the cheapest option. With two-sided arrays it is two writes with no atomicity. With link documents it is a single insert or delete of a small record, also atomic, and removing everything for one side is a bounded delete against an index. Deleting a parent is where arrays hurt most: removing an author from every book requires updating every book that lists them, whereas link documents localise the cleanup to one indexed range. ## Rules of thumb to state Array on the bounded side. Never an array on a side that grows without limit. Two-sided arrays only when both sides are small and you accept reconciliation. Link documents when either side is unbounded, when the relationship has attributes, or when you need to sort or page the pairs. And if the relationship is really a first-class thing in the domain — an enrolment, a membership, a subscription — it deserves its own documents regardless of size, because that is what it is.

  • Posts and tags: why put the array of tags on the post rather than the list of posts on the tag?
    Because the cardinality is wildly asymmetric. A post has a few tags and that number is bounded by what an author will type; a popular tag accumulates posts without limit, so an array on the tag grows until the document hits the size cap. The post-side array is bounded, and the reverse query — posts for a tag — is served by indexing that array.
  • You chose two-sided arrays. What operational work does that commit you to?
    Keeping two documents in agreement without cross-document atomicity. You need the second write to be idempotent and retried, an ordering that makes a half-applied pair harmless, and a periodic reconciliation that compares the two directions and reports mismatches. If that machinery sounds heavier than an extra read, a link collection was the better choice.
  • When is a link collection right even though both sides are small?
    When the relationship is a domain concept in its own right — an enrolment, a membership, a subscription — or when it carries attributes such as a role, a grade or a joined-at date. Modelling it as its own document gives those attributes a home, lets you index and sort by them, and lets the relationship have a lifecycle independent of both parents.

saying these in an interview costs you the question

  • Puts the reference array on the unbounded side
  • Uses two-sided arrays with no reconciliation plan
  • Stores relationship attributes on one parent's array only
  • Assumes a link collection is always the relational habit to avoid
  • Judges each side by its current average, not its maximum

context