In a document tree where each child also points back at its parent, which of those two edges should be non-owning?
answer
- exactly one edge, never both
- ownership follows containment
- which side outlives the other
- the back-pointer must not count
- check the non-owning edge before following it
basics
~20 sThe 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 sA 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
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.
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.
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.
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