As the lead on an app with hundreds of lazily loaded routes, how do you decide what preloads, on which signals, and how far ahead?
answer
- an exchange rate, not a switch
- confidence, cost, value
- tiers from strong intent to click-only
- cap concurrency, yield to real work
- hit rate per signal decides
basics
~20 sTier it by confidence and cost: preload the matched branch on strong intent like hover or focus, nominate a short list of likely-next routes for idle time, leave the long tail to the click, and tune from measured hit rate.
solid answer
~50 sThere is no single right setting, so make it a policy with three inputs: the **confidence** of the signal (one hovered or focused link is strong evidence; fifty links in the viewport is not), the **cost of being wrong** (bytes on a metered connection, requests competing with work the user is definitely waiting for, parsing on a slow device, cache pressure), and the **value when right**, which is only the part of the wait the preload covers. In practice: strong intent preloads that link's matched branch; idle time preloads a short, owned list of likely-next destinations; everything else loads on click with a pending state. Cap concurrency, keep speculation behind the current screen's critical work, and treat a stated reduced-data preference as an instruction. Then measure hit rate per signal and navigation wait with and without a preload, against thresholds agreed in advance.
go deeper
Take away the principle: preloading is a guess that costs bandwidth, so it is worth it on strong signals and wasteful when it is applied to everything.
Be able to describe the tiers and what triggers each, and why a link entering the viewport justifies far less speculation than a hover or a focus.
Show the operational side: caps, deferral behind critical work, respecting user preferences, and hit rate per signal reported per release.
Own the policy itself — thresholds agreed in advance, a named owner for the rules, and the judgment to say when the real problem is code size or data rather than prediction.
## What the decision is actually about Preloading spends bandwidth and requests on a guess in order to remove a wait. A policy is therefore an exchange rate, and it has three inputs: - **Confidence of the signal.** A pointer resting on one link, or keyboard focus on it, is strong evidence about the next navigation. Fifty links entering the viewport is barely evidence at all. Idle-time preloading is not evidence from the user; it is you nominating destinations. - **Cost of being wrong.** Bytes on a metered connection, requests competing with work the user is definitely waiting for, parsing and storage on a low-end device, and cache pressure that can evict something useful. - **Value of being right.** Only the part of the wait the preload actually covers. A preload that starts 40 ms before the click removes 40 ms, not the whole load; and a route the user reaches once a month barely matters however fast it is. ## A tiering that survives review 1. **Always, on strong intent.** Hover and focus on a link preload that link's matched branch. Cheap, bounded by one branch at a time, and the signal is honest. 2. **Nominated set, on idle.** After the current screen has settled, preload a short, explicit list of likely-next destinations — the next step of the main flow, the screens the current one links to most. Keep the list small and reviewable; a list nobody maintains becomes "everything". 3. **On click only.** The long tail: rarely visited routes, the admin area, anything reached from a search result. The pending state and the failure path are the whole answer there. 4. **Never speculatively.** Routes whose load has side effects beyond fetching code, and anything gated behind an authorisation decision the app has not made yet. Viewport-triggered preloading sits awkwardly between tiers 1 and 2: acceptable on a page with a handful of distinct destinations, indefensible on an infinite list. Make it a per-surface decision rather than a global default. | Tier | Trigger | Scope | Reviewable? | |---|---|---|---| | 1 | hover, touch start, focus | the matched branch of one link | yes, it is bounded by the pointer | | 2 | idle after the screen settles | a named list of destinations | only if the list is short and owned | | 3 | the click itself | nothing preloaded | nothing to review | | 4 | never | — | the rule is the review | ## The costs teams forget - **Contention.** Preloads issued while the current screen is still fetching its own data or images make the screen the user is looking at slower to finish, to speed up one they may never open. - **Concurrency without a cap.** Pointer movement across a dense navigation fires many triggers; without a cap and without deduplication the app can have a dozen speculative requests in flight. - **Devices unlike yours.** On a slow device the CPU cost of parsing speculatively fetched code is not free even when the bytes were. - **The user's stated preference.** An explicit reduced-data preference is an instruction, not a hint, and a policy that ignores it is a defect rather than an optimisation. - **Drift.** Preload rules are added by whoever is optimising one flow and are never removed. Without an owner and a measurement they only ratchet upwards. ## Instrument it, then tune it - **Hit rate**: preloads used by a later navigation, divided by preloads issued, broken down per signal. A signal below a threshold you agreed in advance is demoted or dropped. - **Navigation wait**: time from click to committed screen, split by preloaded versus not. This is the number the policy exists to move; if it does not move, the policy is decoration. - **Speculative volume**: bytes and requests issued while the user is idle, watched per release so a new rule cannot quietly double it. - **Failure rate of speculative loads**, so an aggressive rule is not also inflating your error reporting. Set the thresholds before you look at the numbers, or the policy becomes a defence of whatever is already shipped. ## Where preloading is the wrong tool If a route's code is large enough that even a preload started on hover cannot finish before the click, preloading is hiding a sizing problem rather than solving it, and the question moves to how that route's code is split and how large it is allowed to be — a different discipline with its own owners. Likewise, if the wait users complain about is mostly the route's data rather than its code, tuning code preloading will not move it. The lead's job is to say which of the three problems you actually have — too much code, a wait nobody predicted, or a prediction you are not acting on — and only then to reach for this mechanism.
- What single measurement most often shows a preloading rule is not worth keeping?Hit rate per signal: preloads later used by a navigation divided by preloads issued. A viewport rule on a long list typically measures very low, meaning almost all of that bandwidth was speculative waste. Agree the threshold before reading the numbers, or the measurement becomes a defence of whatever already ships.
- Why can aggressive preloading make the screen the user is currently looking at slower?Speculative requests share the connection, the CPU and the cache with the current screen's own data, images and code. If preloads are not deferred behind that critical work and capped in number, you trade a definite slowdown on the screen in front of the user for a possible speed-up on one they may never open.
- When is a preloading policy the wrong place to spend the effort?When the route's code is so large that even a hover-triggered preload cannot finish before the click — that is a sizing and splitting question with its own owners — or when the wait users complain about is mostly the route's data rather than its code. Name which of the three problems you have before tuning this one.
saying these in an interview costs you the question
- Turns preloading on globally because it made one flow feel faster
- Preloads every link in view and treats a high request count as progress
- Ignores a stated reduced-data preference as merely a hint
- Lets speculative requests compete with the current screen's critical work
- Ships preload rules with no owner, no cap and no hit-rate measurement
- Uses preloading to mask a route whose code is far too large to arrive in time