How does a stitched GraphQL gateway resolve a field whose data lives in another service?
answer
- The second service never sees the client's document
- Values from the parent become arguments
- One call per parent is the trap
- Results must be re-associated by key
- Returned error paths need rewriting
basics
~20 sThe gateway becomes a client of that service. It resolves the parent first, then builds a brand-new document against the second service using values from that parent as arguments, and grafts the result into the response tree.
solid answer
~50 sDelegation has four steps. The gateway resolves the parent from the owning service. It then **constructs a new executable document** for the second service — not a forward of the client's document — selecting only the sub-selection the client asked for beneath the delegated field, with arguments filled from the resolved parent's values. It executes that document over the second service's own transport, as any client would. Finally it merges the result back into the response at the right path, and remaps any errors the second service returned onto the outer path so the client sees a path from its own document. Gateways also batch: they collect keys across the list into one call to a list-taking remote field, then re-associate by key. A composed graph's router does the analogous fetch; the difference is that the key and entry point were declared by the subgraph, not written centrally.
code
graphql · 14 lines# what the client sent to the gateway
query SeatMapWithPrices($flight: String!) {
seatMap(flightNumber: $flight) {
seats { seatNumber price { amountMinorUnits } }
}
}
# what the gateway then sends to the fares service - a different document
query DelegatedPrices($keys: [String!]!) {
seatPrices(keys: $keys) {
key
amountMinorUnits
}
}go deeper
Recall the shape: the gateway fetches the parent first, then makes its own request to the second service using values from that parent, and inserts the answer into the response. It is a client of the other service.
Be ready to walk the four steps and name the generated document explicitly, including how the client's sub-selection and variables are translated into it. Interviewers probe whether you know the second service never sees the original operation.
Demonstrate the operational hazards: batching keys across a list, re-associating rows when the remote returns fewer than it was asked for, rewriting error paths, and what a non-null delegated field does to the response when one delegation fails.
Frame the constraint this places on service design: a stitched join is only as batchable as the remote schema allows, so the promise of never touching the backing services quietly fails the moment a list-taking field is missing.
## Delegation is the gateway acting as a client The single most useful mental correction here: a stitched gateway does **not** forward the client's document to the other service. The second service never sees the client's operation. The gateway executes a document it wrote itself, against a schema the second service published, exactly as any other client would. Take the airline seat-map graph. The inventory service owns `SeatMap` and `Seat`. The fares service owns pricing and exposes a root field `seatPrices(keys: [String!]!): [SeatPrice!]!`. A client asks for a seat map and, under each seat, its price. ## Step one: resolve the parent The gateway sends the seat-map part of the request to the inventory service and gets back the seats. Nothing unusual has happened yet — this is one ordinary GraphQL request to one ordinary server. ## Step two: build a new document Now the gateway needs `Seat.price`. It consults its delegation rule and builds a document against the fares service containing only what is needed: the remote root field, arguments derived from the resolved parents, and the client's sub-selection beneath `price` translated into the fares service's own type system. If the client selected `price { amountMinorUnits currency }`, that selection travels; if it selected only `amountMinorUnits`, only that travels. Everything else in the client's document is irrelevant to the fares service and is not sent. Two things commonly go wrong at this step and both make good interview follow-ups. If the client used a **fragment** or a **variable**, the gateway must inline or forward the definitions the generated document actually needs — a generated document that references an undefined variable is simply an invalid document and fails validation at the second service. And if the gateway's configuration maps the parent's value onto the wrong argument, the request succeeds and returns confidently wrong data, which no schema check will catch. ## Step three: execute — and batch Done naively, this is one remote call per parent object, which is the classic amplification: 312 seats on an aircraft means 312 calls. Every serious stitching setup therefore batches. The gateway collects the parent keys across the whole list, calls a remote field that accepts a **list** of keys once, and re-associates the returned rows to parents by key. This is why the shape of the remote schema matters so much to stitching. If the fares service only exposes `seatPrice(key: String!)`, batching is impossible without changing that service — the very service the stitching story promised you would not have to change. A real seat-map query spanning 22 flights collected 8,400 seat keys into a single delegated call; the same query against a per-key remote field would have made 8,400 round trips. Re-association is the other subtlety: the gateway matches returned rows to parents by the key it sent, so the remote field must return the key (or return results positionally). A remote field that returns fewer rows than keys — because some seats have no fare — leaves parents unmatched, and the gateway has to decide whether that is `null` or an error. ## Step four: graft and remap The result is spliced into the response tree under the delegated field. Errors need care. The fares service reports a field error with a path from **its own** document, something like `["seatPrices", 4, "amountMinorUnits"]`. The client never sent that document and cannot interpret that path. A good gateway rewrites it to the client's path — `["seatMap", "seats", 4, "price", "amountMinorUnits"]` — and a poor one leaks the internal path, which is both confusing and a mild information disclosure about your internal topology. Nullability compounds it. If the merged schema declares `Seat.price` non-null but the delegated call errors for one seat, the null cannot stop there and the error propagates up through the seat, and possibly the whole seat map, exactly as it would inside a single schema. ## How the composed path compares A composed graph's router performs the same *kind* of second fetch — resolve part of the tree, take identifying values from what came back, fetch the rest elsewhere, splice it in. The mechanical difference is the provenance of two things: the identifying fields come from a key the subgraph declared in its own schema, and the entry point for fetching by those values is a convention every subgraph implements rather than a rule someone wrote at the gateway. That is why a composed graph gets a *plan* it can reason about, while a stitched gateway executes a configuration it was handed. ## What to say in an interview "The gateway is a client of the second service. It writes a new document, fills arguments from the resolved parent, batches keys across the list into one call, re-associates by key, and rewrites the returned error paths into the client's path." That sentence covers the whole mechanism and every hazard the interviewer is likely to probe.
- What stops a stitched delegation from making one remote call per parent object?The gateway collects the keys across the whole list and calls a remote field that takes a list, then re-associates rows to parents by key. That only works if the remote schema exposes a list-taking field — if it exposes only a single-key lookup, batching requires changing that service, which is exactly what stitching was supposed to avoid.
- The delegated service returns an error. What must the gateway do before the client sees it?Rewrite the path. The second service reports the error with a path from the document the gateway generated, which the client never sent and cannot interpret. The gateway remaps it onto the client's own path. Leaking the internal path confuses clients and quietly reveals internal topology and field names.
- How does a composed graph's router differ from this at the mechanical level?It performs the same shape of second fetch, but the identifying fields come from a key the subgraph declared in its own schema and the fetch entry point is a convention every subgraph implements. Nothing about the join was written centrally, so the router can compute a plan from declarations rather than execute a hand-written mapping.
Like a translator who does not hand over your letter, but writes a fresh one containing only the questions the other party can answer, then copies the replies back into the margins of yours.
saying these in an interview costs you the question
- Thinks the gateway forwards the client's document unchanged
- Assumes one remote call per parent is unavoidable
- Forgets results must be matched back to parents
- Leaks the delegated service's error paths to clients
- Says the second service sees the client's variables automatically
- Confuses delegation with the client making two requests