Why do routers treat a navigation as a cancelable pending transition instead of swapping the screen immediately?
answer
- a navigation has a lifetime
- old screen stays interactive
- commit or abandon, nothing else
- latest intent supersedes the pending one
- traversals move the address first
basics
~20 sBecause the next screen is rarely ready when the address changes. A pending transition keeps the current screen visible and interactive, and can be abandoned when a second navigation supersedes it, so a slow first one cannot commit late.
solid answer
~50 sA navigation has a lifetime: it starts, stays pending while the next screen is being prepared, then either commits or is abandoned. Modelling it that way buys three things. The current screen stays on screen and interactive rather than collapsing to an empty shell, so the user never sees a blank flash for a fast navigation. The pending state is observable, so the app can show progress on the control that started it. And because only one navigation may commit, a second one cancels the first — the work for the abandoned navigation is dropped and, crucially, cannot arrive late and overwrite the newer screen. Back and forward arrive as transitions the app did not start and whose address has already moved, so abandoning one means restoring the entry rather than merely ignoring a result. What the transition waits on is a separate concern from the transition itself.
go deeper
Know that a navigation takes time, and that a router can keep the current screen visible while the next one is being prepared instead of immediately showing an empty placeholder.
Describe the four states — started, pending, committed, abandoned — and explain why only one navigation may commit and what the pending state is used for in the interface.
Show you have debugged the late-arrival case: an abandoned navigation that commits after a newer one, work that was never stopped, and an address that briefly disagrees with the painted screen.
The tradeoff to own is how much latency you absorb silently before admitting it, and who decides: a per-app threshold, a per-route budget, and what the app does when a transition simply will not finish.
## A navigation is not an instant When the user activates a link, almost nothing about the next screen is guaranteed to be in hand. Something has to be prepared before the new screen can be shown, and that preparation takes time that varies from a millisecond to seconds on a bad network. A router therefore has two possible designs. The naive one commits immediately: change the address, unmount the current screen, render whatever placeholder the next route has. The cost is a blank or skeleton flash on every navigation, including the ones that would have completed in 40 ms, and the user loses the screen they were reading before anything replaces it. The other models a navigation as a **transition with a lifetime** — and that is what routers converge on. ## The state machine 1. **Started.** The router has resolved an address it owns and knows which route matches. Nothing has been torn down. 2. **Pending.** The current screen remains mounted, painted and interactive. The router exposes that a navigation is in flight, and usually which destination it is heading for. 3. **Committed.** The next screen replaces the current one, the history entry is recorded, and the restoration policy runs — scroll, focus, title. 4. **Abandoned.** The navigation never commits: it was superseded, or something consulted before the commit refused it. The current screen simply stays. The important property is that steps 3 and 4 are the only exits, and exactly one of them happens per navigation. ## Why cancelation is the point A user who clicks a slow link and then clicks a different one has expressed a newer intent. Without cancelation two navigations are in flight and the one that finishes last wins, which on a slow network is frequently the *first* one. The user ends up on the screen they abandoned, with the address possibly showing the other. The fix is a rule: the latest navigation supersedes any pending one, the abandoned navigation's work is signalled to stop, and its result is discarded even if it arrives. Two details separate a real implementation from a described one: - **Discarding is not enough; stopping matters.** Abandoned work that keeps running consumes the network, can commit side effects, and on a flaky connection keeps retrying for a screen nobody will see. The transition should carry a cancellation signal that the preparation work honours. - **A commit must be atomic from the user's point of view.** If the router half-applies a superseded transition — new address, old screen, or the reverse — the app is briefly lying about where the user is, and any code that reads the address disagrees with what is painted. ## Transitions the app did not start Back and forward are navigations the user initiated *outside* the app. They differ from an in-app navigation in a way that is easy to miss: by the time the app is told, the session has already moved, so the address in the bar is the destination while the screen is still the old one. Consequences: - A traversal cannot be *prevented* the way a pending in-app navigation can be held open; abandoning it means moving the session back to where it was, which is itself a traversal the app must not then treat as fresh user intent. - Traversals should generally commit faster than fresh navigations, because the user expects Back to be instant and because the destination was recently in view. - A pending traversal that is superseded by another traversal must not leave the session pointing at an entry the screen does not match. ## Showing the pending state Because the old screen stays, the app must say that something is happening, or the interface looks unresponsive. - Put the indicator **on the control that started it** when you can — a spinner or a pending style on the activated link tells the user which navigation is in flight. - Use a **short delay before showing anything**, so fast navigations produce no flicker at all; that is the main reward for not committing immediately. - Consider whether the old screen's controls should be partly inert. Leaving everything live means the user can start a third navigation, which is fine; letting them submit a form whose result belongs to a screen that is about to be replaced is not. - For a long transition, the honest escape hatch is a way to give up — a cancel affordance, or simply accepting that the user will press Back. What the transition is *waiting on* — the next route's data, or its code — is a separate concern with its own rules; the transition is the state machine around it, and the reason the rest of the app can be told "a navigation is in flight" in one place.
- Why is discarding an abandoned navigation's result not sufficient?Because the work keeps running: it holds connections, may retry, and can commit side effects for a screen that will never be shown. A transition should carry a cancellation signal the preparation honours, so an abandoned navigation stops consuming the network and cannot cause observable effects.
- How does a back navigation differ from an in-app one for a router?The session has already moved when the app is notified, so the address is the destination while the screen is still the old one. Abandoning such a transition means traversing back rather than simply not committing, and that corrective traversal must not be mistaken for fresh user intent.
- Why delay the pending indicator instead of showing it as soon as the transition starts?Most navigations complete quickly enough that an immediate indicator appears and vanishes as a flicker, which reads as instability. A short threshold shows nothing for fast transitions and a clear signal for slow ones — the whole point of keeping the old screen instead of committing to a placeholder.
It is the difference between a waiter clearing your table the moment you order and clearing it when the food actually arrives. If you change your order, nothing was thrown away.
saying these in an interview costs you the question
- The screen should change the instant the address changes
- Two navigations can both commit; the last render wins
- A full-page spinner is the correct pending state
- Back and forward behave exactly like an in-app navigation
- Cancelling means ignoring the response when it arrives
- Keeping the old screen visible means the app looks frozen