skip to content

In a document tree where each child also points back at its parent, which of those two edges should be non-owning?

level: middleimportance: should knowfreq 47%

answer

  1. exactly one edge, never both
  2. ownership follows containment
  3. which side outlives the other
  4. the back-pointer must not count
  5. check the non-owning edge before following it

basics

~20 s

The child's back-pointer. Ownership should follow containment: the section must outlive its paragraphs, so the downward edge holds the count and the upward one does not. Exactly one edge of a cycle has to be non-owning.

solid answer

~40 s

A cycle is broken by making exactly one of its edges **non-owning** — an edge that can still be followed but contributes nothing to the target's count. Which edge is not arbitrary: pick the one whose target is guaranteed to outlive its source. In a document tree the section outlives its paragraphs, so the section's child list owns and the paragraph's `parent` pointer does not. Weaken the other edge instead and each paragraph dies as soon as the handle that created it is dropped, leaving a section full of gaps; weaken both and nothing inside the structure owns anything at all. The non-owning side pays for the fix by having to cope with a target that may already be gone, so every use of that edge needs a check before it is followed.

go deeper

for a junior

Remember the shape of the fix: in a parent-child pair the downward reference owns and the upward back-pointer does not, and only one of the two edges may be non-owning.

for a middle

Justify the direction instead of reciting it. Say which object's lifetime contains the other's, and describe concretely what breaks if the owning edge is the one you weaken.

for a senior

Talk about enforcement. A non-owning edge is an invariant nothing checks in most settings, so say how the rule survives new contributors, graphs shaped by run-time data, and callers that follow the edge without a guard.

for a principal

Weigh convention against machinery. Hand-placed non-owning edges cost nothing at run time but decay with team size and data-shaped graphs, which is the point where a cycle collector or a periodic reachability pass starts paying for itself.

## The rule in one line A reference cycle becomes reclaimable again the moment **exactly one** of its edges stops contributing to a count. Such an edge can still be stored and still be followed; it simply does not keep its target alive. Breaking one edge is enough, because reclamation only needs some member of the ring to be able to reach zero — once one does, its destruction decrements the next, and the cascade runs the rest of the way around. The interesting part of the question is not *whether* to weaken an edge but *which*, because that choice decides lifetimes, and getting it backwards replaces a leak with a much worse bug. ## Walking the four possibilities | what owns | the section's fate | the paragraphs' fate | verdict | |---|---|---|---| | both edges own | never freed | never freed | the cycle: nothing is reclaimed | | child list owns, back-pointer does not | freed when the last outside handle drops | freed with the section | correct | | back-pointer owns, child list does not | survives while an outside handle holds it | destroyed as soon as the creating handle drops | children vanish early | | neither edge owns | freed when its handle drops | destroyed at once | the structure has no owner at all | Only the second row gives the lifetimes the document actually needs: the section is the container, so it must be the thing whose survival implies its contents survive. ## Three tests for the direction When the objects are not obviously parent and child, these three questions usually agree, and when they disagree the design is telling you something: 1. **Containment.** Which object's lifetime is conceptually inside the other's? The container owns; the reference pointing back out of it does not. 2. **Tolerance of absence.** Which side can carry on sensibly if its target has already gone? A paragraph asking about inherited formatting can handle "no section any more" — the section cannot handle half its paragraphs disappearing. 3. **Structure versus convenience.** Which edge is part of the data, and which one exists to save a lookup? A back-pointer is very often an optimisation over walking down from the root, and an optimisation should not extend a lifetime. ## What the non-owning side has to pay Making the back-pointer non-owning is not free; it moves the cost from memory to the call site. - Every use of the edge must **check first** and have a branch for the target being gone, so the type that expresses the edge has to be able to say "absent". - Some designs offer an unchecked non-owning edge instead, which costs nothing per use and turns the mistake into reading freed memory rather than seeing an absent value; that trade belongs to whoever owns the invariant. - The check has to happen at the moment of use, not once at startup, because the target can be destroyed between two uses. - Two count updates per link disappear, so the linking path actually gets slightly cheaper. ## Where the convention stops holding A hand-placed non-owning edge is a promise about a run-time shape made at authoring time, and it holds exactly as long as someone is keeping it. - **Graphs whose shape is data.** A user-built document with arbitrary cross-links, a plugin that registers an object holding the host, a cache that memoises objects holding the cache — nobody wrote the ring, so nobody placed the non-owning edge. - **Peers with no containment relation.** When neither object is naturally inside the other, all three tests come back silent, and forcing an answer invents an invariant that no reader can check locally. - **Growth.** The rule lives in a review comment or a style guide, and a compiler in most settings will not enforce it; every new contributor is a fresh chance to add an owning back-edge. - **Rings longer than two.** Three or four objects can close a loop across modules that each look correct on their own. When those apply, the answer is not a better convention: it is machinery — a cycle collector that searches for rings, or an occasional reachability pass — with the convention kept as the cheap first line for the shapes you do control. ## What an interviewer is listening for A candidate who just says "make the back-pointer weak" has memorised the shape. The stronger answer justifies the direction from containment, then says what the wrong direction does — paragraphs destroyed while the section is still on screen — and finally admits the cost: every read of the upward edge now needs a guard, and the rule itself is unenforced.

  • What if neither object obviously outlives the other?
    Then the containment test returns nothing and forcing an answer invents an invariant no reader can check. Give ownership to a third object that holds both, model one side as a registered observer with an explicit deregistration step, or accept that this graph needs a cycle collector rather than a hand-placed non-owning edge.
  • Does making the back-pointer non-owning cost performance?
    The linking path gets cheaper, because two count updates per link disappear. What it adds is a check at every use of the upward edge plus a branch for the target already being gone. That is cheap per use, but it has to exist in every caller, and a caller that skips it has a correctness bug rather than a leak.
  • Where does a hand-placed non-owning edge usually fail?
    On graphs whose shape is decided at run time: user data with arbitrary cross-links, plugin objects that register themselves with the host, a cache holding objects that hold the cache. The convention is an authoring-time promise about a run-time shape, so it holds only while every contributor knows the rule and the data obeys it.

saying these in an interview costs you the question

  • Makes both edges non-owning, so nothing keeps the subtree alive
  • Says either edge will do because the cycle is broken either way
  • Weakens the parent's child list, then wonders why children vanish early
  • Follows the non-owning edge without checking whether the target is gone
  • Believes a non-owning edge still delays destruction a little