Where should a screen's data request be declared: inside the component that reads it, on the route, or in a shared definition?
answer
- definition and start site are different decisions
- start early, cancel, see the failure
- shared definition keeps one key
- blocking sets grow without a rule
- declaration sites are team boundaries
basics
~20 sSeparate the request's definition from its start site. Describe it once, parameterised and keyed, so any layer can refer to it; then choose who starts it per surface — the route, an intent handler, or the component that reads it.
solid answer
~50 sThe declaration site decides three things that are hard to change later: **who can start the request early**, **who can cancel or supersede it**, and **who sees it fail**. A request written inside the component that reads it can only start after that component renders, is cancelled when that component goes away, and fails where it can be shown in place. A request declared on the route starts before the screen exists, is superseded by the router when navigation changes, and fails before there is a screen to show it in. The arrangement that scales is to keep the *definition* — a named, parameterised request with a stable key — in one shared place, and let the route, an intent handler, or the component decide *when* to start it. That way the timing becomes a movable decision, and two call sites cannot key the same data differently.
go deeper
Know that a request can be written in the component that reads it or attached to the route that leads to the screen, and that the choice affects when it can start.
Explain the consequences of each site: what can start the request early, what cancels it, and where a failure can be shown to the user.
Argue for separating the request's definition from its start site, and show how that lets you move timing on a slow surface without touching the components that read the data.
Own the policy and the guardrails: what may block a navigation and why, a user-facing latency budget, key ownership, and how the declaration site maps onto team boundaries.
"Where is the request declared" sounds like a style question. It is really an architectural one, because the declaration site fixes who has the *authority* to start, stop and observe the request — and those capabilities are exactly what you need when a screen turns out to be slow a year later. ## The three capabilities the site decides 1. **Who can start it early.** Only a layer that runs before the screen exists can move a request off the critical path. A request written inside a component can never be speculated on from a link, because the thing that would start it does not exist until after the navigation. 2. **Who can supersede or cancel it.** Ownership follows the site: a component owns the lifetime of what it started; a router owns the lifetime of a pending transition; an intent handler owns something that outlives the element that fired it. 3. **Who sees the failure.** A failure inside a rendered screen can be shown next to the thing that failed, with a retry in place. A failure before the screen exists must be handled by the route, which usually means a whole-screen error — a coarser, sometimes worse, experience. ## Comparing the three sites | | In the reading component | On the route | In a shared definition used by both | |---|---|---|---| | Earliest possible start | after that component renders | when the route matches | whenever any layer chooses | | Cancelled or superseded by | the component's teardown | the router's transition | whoever started it | | Failure surfaces | in place, with local retry | before the screen, usually whole-screen | wherever the read happens | | Reuse across screens | duplicated per screen | duplicated per route | one definition, many call sites | | Risk of two different keys for one dataset | high | medium | low | | Coupling | none to routing | component needs a route entry | call sites depend on a shared catalogue | | Best for | data an interaction reveals | data the screen is meaningless without | anything read from more than one place | The table is the argument for the hybrid. The first two columns each make one thing easy and one thing impossible; the third makes the *timing* a separate, movable decision. ## The policy to actually propose - **Define once.** Every remote read gets a named, parameterised definition with a deterministic key derived from its parameters. Nothing outside that definition constructs the key by hand. - **Block deliberately.** Only data the screen would be misleading without may hold a navigation. Everything else is either started at the boundary and read as it settles, or started by the component. - **Speculate where it is cheap.** Intent handlers refer to the same definitions, so a prefetch and the real read resolve to the same in-flight work instead of two requests. - **Keep local reads local.** Data revealed by an interaction — a panel the user opens, a row expanded — belongs to the component. Hoisting it to a route pays latency for visits that never open it. - **Make the exception explicit.** When a screen must serialise two requests, the dependency is written down in the place that starts them, so the next reader sees the chain rather than inferring it from two files. ## The organisational half Declaration sites are also team boundaries. If routes are owned by a platform group and screens by feature teams, putting requests on routes makes every data change a cross-team change; putting them inside components makes screen performance invisible to the group accountable for it. A shared definitions layer is the only arrangement where both teams can act: the feature team owns what a request is, the platform layer owns when transitions may block. Say this out loud in a leadership interview, because it is the reason the hybrid wins in real organisations and not merely in a diagram. Two guardrails keep it honest. First, a review rule that a new blocking entry on a route needs a stated reason, or the blocking set grows monotonically until every navigation is slow. Second, a budget expressed from the user's side — time from committed navigation to the screen being useful — so the discussion is about that number rather than about which file the request lives in. ## Migration, when the codebase already fetches everywhere Do not rewrite the tree. Extract definitions for the requests on the slowest surfaces first, leave the components reading them exactly as they did, then move the start site for those definitions to the route or an intent handler. Because the read did not change, each step is independently shippable and independently measurable, and you can stop when the number is good enough instead of finishing a refactor for its own sake.
- What guardrail keeps the set of requests a navigation blocks on from growing forever?A review rule plus a user-facing number. Every new blocking entry needs a stated reason why the screen would be misleading without it, and the surface carries a budget measured from committed navigation to usable screen. Without both, each addition looks individually harmless and the transition degrades a step at a time.
- How would you handle a screen whose data is owned by three different teams?Give each team its own definitions and let the screen compose them, with only the parts the screen is meaningless without allowed to block the transition. Everything else resolves into the rendered screen, so one team's slow service degrades its own region instead of the whole navigation, and the owner of that region is the one who sees the number move.
saying these in an interview costs you the question
- Treats the declaration site as a formatting preference
- Moves every request to routes and makes navigations uniformly slow
- Lets each call site build its own key for the same dataset
- Keeps data an interaction reveals on the route the user may never act on
- Adds blocking route data with no stated reason or budget
- Plans a whole-codebase rewrite instead of extracting definitions surface by surface