skip to content

When the user navigates between two sibling child routes under the same parent, what does the router keep mounted?

level: middleimportance: must knowfreq 60%

answer

  1. a diff, not a rebuild
  2. the shared prefix is kept
  3. re-render is not remount
  4. the deepest differing level swaps
  5. same entry means same instance

basics

~20 s

Every level the two matches share stays mounted and is updated in place, so the parent's component instance and its state survive. Only the deepest level that differs is torn down and replaced inside its outlet.

solid answer

~40 s

The router computes the matched chain for the new URL and compares it with the old one level by level. Levels whose route entry is the same in both are **updated in place**: their component instances live on, keeping local state, scroll positions, subscriptions started at mount and any data they loaded once. At the first level where the entries differ, the old branch is unmounted — cleanup runs — and the new branch mounts into that outlet. So a parent that shows a section shell does not restart when the user moves between screens inside the section; it may re-render, because the child in its outlet changed, but re-render is not remount. The practical consequence: state you want to survive belongs on a level that both URLs share.

go deeper

for a junior

Hold on to the headline: the shared part of the screen stays alive and only the inner area is replaced. That is why a sidebar does not flash on every click.

for a middle

Explain the level-by-level diff, and keep re-render and remount apart: a kept level may re-render with new inputs while its instance, state and mount-time work stay untouched.

for a senior

Demonstrate the parameter trap. Show how you place state so it survives deliberately, and how a shared level reacts to a changed identifier instead of relying on a mount that never comes.

for a principal

State retention is a structural guarantee your route tree hands the whole team. Decide where it lives and write it down, because a level moved later silently changes what resets.

## Two matches, one shared prefix A navigation inside a nested app is a diff, not a rebuild. The router resolves the new URL into a matched chain, lines it up against the chain that is currently rendered, and walks down comparing level by level: 1. While the entries agree, the existing components at those levels are **kept** and re-rendered with whatever changed (the new matched child below them, new parameter values). 2. At the first level where the entries differ, the currently rendered branch from there down is **unmounted** and the new branch is **mounted** into that level's outlet. 3. Everything above the divergence point never learns that it could have been rebuilt; from its point of view, one input changed. Moving between two siblings under the same parent is the simplest case of this: the divergence point is the child level, so exactly one outlet's contents change. ## What "kept" means concretely At a kept level, the instance is the same object the framework created when that level first mounted. Therefore: - **local state survives** — a collapsed navigation panel stays collapsed, a filter box keeps its text, a tab keeps its selection; - **element references survive**, so a scroll container keeps its offset instead of snapping to the top; - **work started at mount survives** — a subscription, a timer, an observer, and any one-time data load. Mount-time work does **not** run again; - **derived and memoised values survive** unless the values they depend on changed; - **cleanup does not run**, because nothing was destroyed. At the divergence point the opposite happens: the outgoing branch's cleanups run, its instances and their state are gone, and the incoming branch starts from scratch. ## Re-render is not remount This is the distinction the question is really testing. | | re-render | remount | |---|---|---| | triggered by | new inputs, new parameters, a new child in the outlet | the level's route entry changing, or an explicit identity change | | instance | the same one | a new one | | local state | preserved | discarded | | mount-time work | not repeated | repeated | | cleanup | not run | run for the old instance | A parent whose outlet content changed will typically re-render, and a candidate who says "nothing happens to the parent at all" is overstating it. The correct claim is narrower: the parent is not rebuilt. ## The parameter-change trap The interesting edge is a parent whose own segment is dynamic — a record shell wrapping several tabs for that record. Moving from one record to another keeps the **same route entry** at that level, so most routers keep the instance mounted and simply hand it a new parameter value. Consequences: - work the shell did when it mounted — including the load for the previous record — does **not** re-run on its own; - state left over from the previous record is still there, so a stale name, a stale count or a half-edited draft can bleed across records; - the fix is to make the shell **react to the parameter** rather than assume mount equals fresh: re-run the load when the identifying value changes, and reset the state that is tied to it. Some routers also let you request a fresh instance for that level deliberately, which is the blunt version of the same fix and costs you the retained scroll and chrome state. ## Frameworks differ in *how*, not in *whether* The retention rule is a router-level decision, but what happens inside a kept level depends on the reactivity model. A runtime that re-runs a component's function re-executes it with the new values while preserving the state it holds; a fine-grained runtime does not re-run the component body at all and only updates the specific reactive reads that changed; a compile-time runtime updates the generated instructions for the changed values; a dirty-checking runtime re-evaluates the level's bindings. None of them create a new instance merely because a value changed — creating instances is what mounting does. ## Verifying the behaviour rather than assuming it When you need certainty on a given app, instrument it instead of arguing: - record a counter or log line at each level's creation, navigate between siblings, and see which levels report a new creation; - put a visible piece of throwaway state in the shared level (a typed character) and check whether it survives; - watch whether the shared level's data request fires again — if it does, something above it is being rebuilt. The answer that instrumentation gives you is also the answer to the design question: whatever must survive belongs at a level both URLs share, and whatever must reset belongs below the divergence point.

  • The parent's own dynamic segment changes from one record to another. Does its state reset?
    Usually not. The route entry at that level is unchanged, so most routers keep the instance and hand it a new parameter value. Mount-time work does not re-run and state tied to the old record survives, which is how stale values bleed across records.
  • How do you make a shared level respond to a parameter change without remounting it?
    Treat the identifying value as an input and react to it: re-run the level's load whenever it changes, and reset only the state that belongs to that record. That keeps the chrome, scroll position and subscriptions the level exists to hold, while the record-specific parts refresh.

saying these in an interview costs you the question

  • Thinks every navigation tears down and rebuilds the whole screen
  • Says a shared header refetches its data on each child navigation
  • Assumes a changed parent parameter remounts that level by itself
  • Claims the parent does not re-render at all when the child changes
  • Expects state in a shared level to reset when the section's child changes