How does a shared request layer stop three components that ask for the same resource at once from sending three requests?
answer
- the same work is already running
- a table of pending entries, keyed
- identity is the operation plus every input
- drop the entry when it settles
- count the attached consumers before cancelling
basics
~20 sBy keying requests in flight: the first ask starts the request and stores its pending result under a key derived from the request; identical asks arriving while it is pending attach to that same pending result.
solid answer
~40 sThe layer keeps a table of requests currently in flight, keyed by **request identity** — the operation plus every input that changes the answer, serialised in a stable order. The first asker finds no entry, starts the request, and stores the pending result under that key. Later askers find the entry and wait on the same pending result, so one round trip serves all of them. When it settles, the entry is removed so a later ask starts fresh work rather than receiving that one old answer. Because several consumers now share one request, the layer also counts them, and only considers cancelling when the last one goes away. Deduplication applies to reads; two writes are two intended changes and must both be sent.
go deeper
Recall the idea: if the same request is already running, join it instead of starting a second one, and everyone waiting gets the single response.
Explain the table of pending entries keyed by request identity, why the entry is removed when it settles, and what belongs in the key.
Show the operational edges: consumer counting before cancellation, scope in the key, shared failures and where retry sits, and refusing to dedup writes.
Decide the boundary — what the layer dedups by default, what it refuses to touch, and when repeated identical asks are a signal to restructure ownership of the data instead.
## What in-flight deduplication is Three components on one screen each need the same resource. Each asks independently, in the same tick, before any answer exists. Without coordination that is three round trips for one answer — three connections, three server handlers, three parses, and three separate races about which response lands where. Deduplication (also called request coalescing) removes the duplicates *while they are in flight*. It is not caching: a cache answers from a result that already exists, whereas dedup joins askers to work that has started but not finished. The two compose — a cache read avoids the request entirely, and dedup handles the window in which there is nothing to read yet — but they are separate mechanisms, and a layer can have either without the other. The mechanism is small: 1. Derive a **key** from the request. 2. Look the key up in a table of pending entries. On a hit, return the existing pending result. 3. On a miss, start the request, store its pending result under the key, and return it. 4. When it settles, remove the entry, then deliver the outcome to everyone who joined. Every asker receives the same outcome, success or failure. ## Request identity is the whole mechanism The key decides what counts as "the same request", and almost every dedup bug is a key bug. - **Include everything that changes the answer**: the operation, the target, query inputs, paging inputs, sort order, and any body. - **Serialise inputs in a stable order** so two logically identical asks with differently ordered inputs produce one key, not two. - **Include the scope the answer belongs to** — the identity of the viewer, the selected workspace, the locale — or one consumer receives an answer prepared for a different scope. - **Key on values, not on object identity.** Two freshly built input objects with equal contents must produce equal keys; a key built from object identity dedups nothing. - **Leave volatile noise out** — a timestamp or a random correlation value inside the key makes every ask unique and silently disables dedup. ## Shared lifetime and cancellation Once several consumers share one request, ownership is no longer obvious, so the layer tracks how many are attached. - A consumer disappearing decrements the count; the request is only a candidate for cancellation when the count reaches zero. - Cancelling because the *first* consumer left breaks the others, which is one of the nastier bugs in this area: it appears only when timing puts two consumers on one request. - If the layer supports cancellation at all, it must also decide what an entry whose only consumers left should do — abort, or finish and be kept for whoever asks next. ## Dedup, debounce and a cache read compared | | In-flight dedup | Debounce | Read from an existing result | |---|---|---|---| | Question answered | is this same work already running? | should this work start yet? | does an answer already exist? | | Removes | duplicate concurrent asks | asks from an input burst | the request entirely | | Scope | across consumers, same instant | one input, over time | across consumers, over time | | Needs | a request key | a timer | a store with a lifetime policy | They are complementary. Dedup does **not** decide which response wins a race: if one asker's inputs change, that is a *different* key and therefore a different request, and the latest-wins rule still has to choose between the two. ## What must not be deduplicated - **Writes.** Two submits are two intended changes. Silently collapsing them into one loses a user action, and matching an in-flight write is not the same problem as making a retry of *one* write safe. - **Requests whose answer depends on when they ran** — a counter that advances per call, a token that is consumed, anything that is not safely repeatable from the caller's point of view. - **Anything whose key you cannot compute honestly.** If two asks differ in a way the key omits, dedup hands one of them the wrong answer, which is worse than the duplicate request it saved. ## Traps worth naming - **Keeping the settled entry** turns dedup into an accidental cache with no expiry, so every later ask receives one frozen answer. Remove the entry when it settles and let a real store, with a real policy, own anything longer-lived. - **Sharing a failure without thought**: if one ask fails, every joined asker fails together, so a retry policy has to sit at a level where one consumer's retry does not starve or duplicate the others'. - **Deduping across different viewers or sessions**, which is the path to showing one person's data to another. - **Hiding a design problem**: if three components independently need the same resource on every screen, dedup silences the symptom while the real answer may be for one owner to request it and pass it down.
- What goes wrong if the pending entry is kept after the request settles?It becomes a cache nobody designed: every later ask for that key receives the one stored answer, forever, with no freshness rule and no way to revalidate. Dedup's contract is only 'join work that is running', so the entry must be removed on settle and any longer-lived storage handed to a store with an explicit lifetime and invalidation policy.
- Why must the key include the viewer or session scope?Because the same operation and inputs can legitimately produce different answers per viewer. If scope is omitted, two consumers in different scopes collide on one key and one of them receives an answer prepared for the other — a correctness and privacy failure that only appears when their asks overlap in time.
- Does deduplication remove the need for a latest-wins rule?No. Dedup joins asks that are identical; a latest-wins rule chooses between asks that are different, such as the old and new value of a changing input. Those are different keys, so both requests exist and something still has to decide which response may write into the slot they share.
- One joined asker needs a retry when the shared request fails. How should that be handled?Treat the retry as a new request rather than a resumption: the failed entry is already gone, so the retrying consumer starts fresh work and later askers join that. Retry policy belongs to one place, or several consumers each retrying the same failure will multiply load exactly when the dependency is already struggling.
saying these in an interview costs you the question
- Deduplicates writes, so a second deliberate submit is silently dropped
- Keys pending requests by target only, ignoring inputs that change the answer
- Keeps the pending entry after it settles, so later asks get one frozen answer
- Cancels the shared request when the first of several consumers disappears
- Believes deduplication also decides which response wins a race
- Builds the key from object identity, so equal inputs never match