Should latest-wins and cancellation rules live in each component that requests data, or in one shared request layer?
answer
- it is a question about defaults
- name the parts before placing them
- discipline versus indirection
- shared data goes through the layer
- adopt per surface, delete local guards
basics
~20 sPut the policy in one shared layer once more than a handful of screens race: request identity, latest-wins, deduplication and cancellation then hold by default rather than per call site. Per-component guards suit only one-off, screen-private requests.
solid answer
~50 sThe question is really about defaults. Per-component guards are explicit and readable, and for two or three call sites they are fine — but the rule has to be re-implemented at every new one, and the ones that forget produce an intermittent wrong-data bug nobody reproduces. A shared layer defines request identity once, so latest-wins, in-flight deduplication and cancellation come for free at every call site, and correctness stops depending on whoever wrote the newest screen. The costs are real: a key design you now own, a lifetime and invalidation policy, and a debugging surface where "which response won" is no longer visible where the request was written. The split that usually holds is that the layer owns reads that anything might share, while a genuinely screen-private one-off keeps its own guard — and it can be adopted incrementally, one route at a time.
go deeper
Understand that the same race rule has to exist somewhere, and that writing it by hand in every component means some components will not have it.
Be able to list the parts — identity, latest-wins, deduplication, cancellation, teardown, classification — and say which a hand-rolled guard usually omits.
Argue the tradeoff with evidence: recurring wrong-data reports and duplicate requests versus indirection and a lifetime policy you would then own.
Own the default and the migration: what goes through the layer, what stays local, which rules are global regardless, how adoption proceeds per surface, and what you measure to know it worked.
## What the policy actually consists of "Race handling" is not one rule. Before deciding where it lives, name the parts, because each can sit in a different place: 1. **Request identity** — what makes two requests the same, which both deduplication and latest-wins depend on. 2. **Latest-wins** — which response may write when several are outstanding for one destination. 3. **Deduplication** — joining an ask to identical work already in flight. 4. **Cancellation** — when superseded or unwanted work is stopped, and who is allowed to stop shared work. 5. **Teardown behaviour** — what happens to a response whose consumer is gone. 6. **Classification** — a cancellation is not a failure, in the UI, the retry loop, and the logs. A per-component implementation almost always covers item two and forgets the rest. ## The case for keeping it at the call site - **Explicit and local.** The guard sits beside the request it protects; a reader sees the whole story without learning a layer. - **No shared lifetime to reason about.** Nothing outlives the screen, so there is no staleness policy, no key collisions, no cache to invalidate. - **Fine for genuinely one-off work** — a wizard step, an admin action, a screen-private lookup nothing else reads. - **No migration cost**, which matters when the racing surfaces are two screens rather than forty. Its failure mode is discipline. Each new call site is a new chance to omit the guard, code review catches it inconsistently, and the resulting bug is intermittent and environment-dependent — exactly the class that survives for months. ## The case for one shared layer - **Correct by default.** A call site that writes nothing special still gets identity, latest-wins, dedup and cancellation. - **Sharing becomes possible.** Several consumers on one request, one response, no duplicate round trips. - **Uniform semantics.** Cancellation is classified once, so it cannot be rendered as an error on one screen and swallowed on another. - **Observability in one place.** Duplicate-request counts, cancellation rates and stale-response drops can be instrumented centrally rather than screen by screen. - **Testable as a unit**, including the out-of-order case that is tedious to test per component. Its costs are equally concrete: you own key design and therefore key bugs; the layer implies storage with a lifetime whether you meant it or not; indirection makes "why did this render old data" harder to trace from the call site; and it is a dependency the whole application leans on. ## Compared | | Per component | Shared layer | |---|---|---| | Correctness depends on | every author remembering | the layer, once | | New call site | re-implements the guard | inherits it | | Sharing between consumers | not possible | the default | | What you must own | nothing extra | keys, lifetime, invalidation | | Debuggability | obvious locally | needs central instrumentation | | Best at | a few one-off, private requests | many screens over shared data | ## The split that usually holds Draw the line by **ownership of the data, not by convenience**: - Anything **more than one surface could read** goes through the layer, keyed by request identity. - **Screen-private, one-off** requests may keep a local guard — but the *classification* rule (a cancellation is never a failure) stays global, because inconsistency there is user-visible. - **Writes** are never deduplicated and never cancelled casually, wherever they are issued from. - The decision is **reviewable**: a short written rule plus a test that resolves two responses out of order gives reviewers something objective to point at. ## Adopting it without a freeze - Introduce the layer alongside existing call sites; the two coexist, since a local guard inside a layer-owned request is redundant rather than harmful. - Migrate by surface, starting with the screens that generate the actual bug reports. - Delete local guards as each surface moves, so there is one rule per call site rather than two half-rules. - Measure before and after: reports of wrong-data-on-screen, duplicate identical requests per screen load, cancellations classified as errors, and requests still running with no consumer. ## What makes this a judgement call There is no threshold that decides it for every team. A small application with two racing screens is worse off carrying a layer it does not need; a large one with many screens over shared data cannot hold the line per call site, because the failure is not technical difficulty but the number of chances to forget. Team size, review capacity, how much of the data is shared, and whether the failures already show up in support tickets are what move the answer — and the honest form of the answer names those factors rather than declaring one architecture universally correct.
- What evidence would tell you a per-component approach has stopped scaling?Recurring reports of briefly or persistently wrong data on screen, more than a couple of call sites with hand-rolled guards that differ, duplicate identical requests visible in one screen load, and cancellations showing up in error dashboards. When the same defect class reappears from new call sites rather than from one broken implementation, the problem is the default, not the code.
- Which parts of the policy should be global even if requests stay per component?Classification and teardown semantics: a cancellation you initiated is never a failure, and nothing writes into a destroyed consumer. Those must be consistent everywhere because they are user-visible and they distort error metrics. Identity, latest-wins and deduplication can start local and centralise later without changing what the user sees.
- What is the main risk of centralising, and how do you contain it?Hidden behaviour: the call site no longer shows which response won or why old data appeared. Contain it by making the layer observable — surface the active key, the attached consumers and dropped stale responses in a devtools-style view — and by keeping key derivation explicit and reviewable rather than inferred from whatever arguments were passed.
- Does a shared layer remove the need to think about request identity?The opposite: it concentrates it. The layer is only as correct as its keys, and a key that omits an input, a paging value or the viewer's scope hands one consumer an answer prepared for another. Centralising makes identity a designed, tested artefact instead of an accident repeated at every call site.
saying these in an interview costs you the question
- Answers always centralise, with no account of the costs
- Claims a shared layer removes the need to define request identity
- Believes per-component guards scale because each one is simple
- Treats the choice as all-or-nothing with no incremental adoption
- Centralises requests but leaves cancellation classified as a failure
- Names no evidence that would change the decision either way