skip to content

Why does starting a destination's data request at navigation intent change the perceived wait, and what does that cost?

level: seniorimportance: should knowfreq 54%

answer

  1. spend the user's decision time
  2. intent precedes the click
  3. the destination must find the same request
  4. hit rate is the honest metric
  5. code and data speculate together

basics

~20 s

Intent arrives before the click, so the request overlaps the user's decision time and is in flight when the screen renders. The costs: requests for destinations nobody opens, and a result the destination must find by key.

solid answer

~50 s

Hover, keyboard focus, a link entering the viewport and a pointer press all happen before the navigation commits — a hover often by several hundred milliseconds, a press by rather less. Starting the destination's request then lets the latency overlap time the user was spending anyway, and the destination renders immediately against a result that may still be settling — declaring, through an async boundary, that part of the tree is not ready yet. That is **render-as-you-fetch**: no wait that the ordering itself created, only whatever latency the data genuinely has. The result must land where the destination will read it, keyed the same way, or the screen starts a second request and you paid for nothing. And intent is a guess, so on a long list you are spending requests and bandwidth on destinations nobody opens; you cap it, prefer cheap signals, and skip it when the connection is constrained.

go deeper

for a junior

Understand the idea: the moment a user hovers or focuses a link is earlier than the click, so a request started then has a head start on the navigation.

for a middle

Explain the mechanism end to end: the request is registered under a key, the destination renders at once, and the region that needs the value waits inside a boundary instead of starting a second request.

for a senior

Show the operational side: pick signals for the surface, cap speculation, verify the destination reads the same request, and prove the win with navigation timing and hit rate.

for a principal

Treat it as spending server and network budget to buy latency. Decide which surfaces earn that spend, what the hit-rate floor is, and how speculation degrades on poor connections.

The fastest navigation is the one whose data was already being fetched before the user committed to it. **Intent** is any signal that a destination is likely: the pointer resting on a link, a link receiving keyboard focus, a row scrolling into the viewport, the pointer going down before it comes up. Each of those precedes the actual navigation by a meaningful interval — often 200-600 ms for a hover, tens of milliseconds even for a press — and that interval is free latency if you spend it on the request. ## What the destination has to do differently Starting early only pays if the destination can render against a result that has not arrived. That requires the framework to have a way for a subtree to say *not ready yet* and for an enclosing boundary to show something in the meantime, plus a shared place where an in-flight request can be found by identity. With both, the sequence becomes: 1. Intent is observed; the request starts and is registered under a key derived from the destination. 2. The navigation commits and the destination renders immediately. 3. The component that needs the data looks it up by the same key, finds the in-flight request, and suspends its own subtree rather than triggering a new request. 4. The response lands and only the waiting region renders. The wait has not been hidden, it has been **moved off the critical path and overlapped with the user's own decision time**. ## The failure that wastes the whole technique If the prefetch writes to a variable that the destination never consults, or is keyed differently from what the destination asks for, the destination starts a second identical request. The screen is exactly as slow as before and the server is doing twice the work. This is why the declaration of the request usually has to be shared — a named, parameterised description that the intent handler and the component both refer to — rather than duplicated at each site. ## Signals and what each is worth | Signal | Lead time | Precision | Good for | |---|---|---|---| | Pointer hover | large | medium | desktop lists, navigation menus | | Keyboard focus | large | medium | keyboard and assistive-technology users, who hover never covers | | Link in viewport | very large | low | short lists where most rows get opened | | Pointer down before release | small | very high | anything: cheap insurance with almost no waste | | Device idle after load | large | low | one or two likely next destinations, not a list | A viewport trigger on an infinite list is the classic over-reach: hundreds of speculative requests for a handful of visits. Hover and focus together are the honest desktop default, and a pointer-down trigger is worth adding everywhere because its waste is near zero. ## What it costs, stated plainly - **Requests for destinations nobody opens.** Server load and the user's data, spent on a guess. Cap concurrency, deduplicate by key, do not prefetch what is expensive to compute, and drop speculation when the connection reports itself as constrained or metered. - **Cache pressure and staleness.** A result fetched at hover may be read a while later or never; something has to decide how long a speculative result stays worth reading. - **Ownership questions.** A request started by an intent handler outlives the element that started it. Who cancels it if the user moves away, and who sees the failure if it rejects before any screen exists? Answer both deliberately — usually: nobody cancels a cheap speculative read, and a speculative failure is swallowed and retried by the real read, so the user never sees an error for a navigation they did not make. - **Code and data must start together.** Prefetching the destination's data while its code still loads on click, or the reverse, just relocates the staircase. A fast navigation needs both speculations fired from the same intent signal. ## Measuring whether it worked Judge it on the interval from the committed navigation to the destination being useful, and on the **hit rate**: what fraction of speculative requests were actually read. A hover prefetch with a 10% hit rate on a long list is a bandwidth tax with a rounding-error benefit; the same trigger on a four-item menu is close to free. Report both numbers when you propose it, because the technique is the rare optimisation that trades someone else's resource for the user's latency, and it should be defended with data rather than intuition.

  • Who should cancel a speculative request when the user moves the pointer away without navigating?
    Usually nobody. A cheap read that is already in flight is often worth completing, because the user may come back and the result is then free. Cancellation earns its keep when the request is expensive for the server or when many are outstanding at once, so the practical rule is to cap how many speculations can be in flight rather than to chase each one.
  • How should a speculative request's failure be surfaced?
    It should not be surfaced at all. The user never asked for that navigation, so an error banner for it is noise and can even mislead. Record it for monitoring, let the state stay as if nothing was attempted, and allow the real read after a genuine navigation to fail visibly on its own terms.
  • What hit rate makes hover prefetching defensible?
    There is no universal number, but the shape of the judgment is: multiply the expected saving per hit by the hit rate and compare it against the cost of the misses in server work and data. A menu of four destinations converts often enough to be nearly free; a long list converting in the low single digits is a tax you should not levy.

saying these in an interview costs you the question

  • Prefetches on every row of a long scrolling list
  • Stores the prefetched result where the destination never reads it
  • Assumes hover covers keyboard and touch users
  • Reports the win without the speculative hit rate
  • Speculates freely on constrained or metered connections
  • Loads the destination's code early but leaves its data request until after render