skip to content

questions

4

What does a federated subgraph's _entities field receive in each representation?

level: middleimportance: must knowfreq 62%

answer

  1. A pointer to an object, not a copy
  2. One reserved root field, one list argument
  3. Type name plus the fields of a key
  4. @requires fields ride along too
  5. The argument scalar is opaque to validation

basics

~20 s

A representation is a plain JSON object carrying __typename plus the fields of one key the subgraph declared for that entity type, and nothing else is guaranteed. It arrives as an untyped scalar, so the schema validates none of it.

solid answer

~50 s

The router builds each representation from the entity's key: an object whose `__typename` names the entity type, plus the exact fields of one `@key` selection set that this subgraph declared, nesting included when the key is nested. If the field being fetched declares `@requires`, the router folds those required fields into the same object as well. Everything else about the parent is deliberately absent — on a 37-field `Trial` a representation is usually two keys — because the subgraph is expected to look the entity up from its key in its own store. The argument type is `_Any`, a custom scalar, so nothing in the schema validates the contents: a reference resolver must treat a representation as untrusted input, check `__typename`, read only the key fields it declared, and fail cleanly rather than throw when they are missing.

code

json · 7 lines
json
{
  "representations": [
    { "__typename": "Trial", "nctId": "NCT-04471" },
    { "__typename": "Trial", "site": { "id": "SITE-3308" } },
    { "__typename": "Site", "sponsorId": "SP-119", "siteCode": "3308" }
  ]
}

go deeper

for a junior

Recall the two guaranteed parts: the type name under __typename, and the fields of a key. Be able to read a representation in a JSON payload and say which entity it points at.

for a middle

Explain where each part comes from — the key selection set the subgraph declared, and @requires fields the router folded in — and why the untyped scalar argument means validation gives you nothing.

for a senior

Show the defensive resolver: dispatch on __typename, coerce declared key fields only, handle every declared key, and fail one slot rather than the whole batch when a representation is malformed.

for a principal

Own the argument for why representations carry identity and nothing else: one owner per field, plans that stay stable, and a trust boundary where a reserved root field bypasses the checks written on ordinary root fields.

## The shape on the wire When a query plan needs fields of an object that another service owns, the router does not call that subgraph's ordinary root fields. It calls one reserved root field, `_entities`, and hands it a list of *representations* — its minimal description of the objects it already has a handle on. ```graphql query($representations: [_Any!]!) { _entities(representations: $representations) { ... on Trial { enrollmentTarget primaryOutcome } } } ``` ```json {"representations": [ {"__typename": "Trial", "nctId": "NCT-04471"}, {"__typename": "Trial", "nctId": "NCT-05128"} ]} ``` ## The two things it always carries **`__typename`.** `_entities` returns `[_Entity]!`, and `_Entity` is a union of every type in this subgraph that declares a key. A union member cannot be resolved without knowing which type an object is, so the type name is not optional and the federation specification requires it in every representation. **One key.** The fields of one `@key` selection set that this subgraph declared for that type. A single key field gives `{"__typename": "Trial", "nctId": "NCT-04471"}`. A composite key contributes both fields. A nested key such as `@key(fields: "site { id }")` contributes the nesting itself: ```json {"__typename": "Trial", "site": {"id": "SITE-3308"}} ``` A type may declare several keys, and the router chooses whichever one the subgraph it is coming *from* can produce. A reference resolver therefore has to handle every key its schema advertises as resolvable, not only the first one written. ## The one thing it sometimes carries If the field being fetched declares `@requires`, the router first collects those fields from the subgraph that owns them and folds them into the same representation object, arriving beside the key fields. That is the only legitimate route by which a non-key field appears in a representation, and it is opt-in, declared in the schema. ## What is not in there Everything else. Give the registry graph a `Trial` type with 37 fields, and the representation still carries two keys. The parent subgraph's copies of `title`, `phase` and `status` are not forwarded, even when the router already holds them in memory for the response it is assembling. This is the classic bug in a first reference resolver: reading `representation["title"]` works in the one query shape that was tested and yields nothing in every other, because whether the router happens to have that field is a property of the query plan, not of the entity. The narrowness is the point. A representation is a *pointer*, not a copy. Federation transmits identity between services and nothing else, so each subgraph stays the single source of truth for the fields it owns; the moment representations started carrying data, two services could disagree about a value and the router would have to pick a winner. ## Where the values come from The router does not invent key fields. While planning, it adds the key selections to the *upstream* fetch, so the parent subgraph returns `nctId` for every `Trial` even though the client never selected it; those values become the representations for the next step. Key fields therefore have to be plainly requestable with no arguments and no extra context — how a key is declared is its own subject. ## `_Any` means the schema is not helping you `representations` is typed `[_Any!]!`, and `_Any` is a custom scalar. Scalars are opaque to GraphQL validation, so no validation error is raised for a missing `__typename`, a misspelled key field, a string where an `ID` was expected, or fifty extra keys nobody asked for. The value reaches the resolver as whatever the JSON parser produced. That makes a representation *input from another process*, and it deserves the treatment given to any input: - verify `__typename` names a type this subgraph actually resolves; - read only the key fields you declared, and coerce them; - on anything missing or unrecognised, return a null slot or a field error rather than throwing out of the batch. Two consequences follow. First, `_entities` is a root field like any other, so anything reachable through it is reachable without passing the authorization checks written on your ordinary root fields — a trust boundary worth its own conversation. Second, a representation is never proof that the entity exists; it is a claim by the router that something upstream said it did, and a subgraph that cannot find it has a defined way to say so. ## The interview-sized answer `__typename` plus one declared key, delivered as a scalar the schema does not check, with `@requires` fields folded in when a field asks for them. The follow-up that separates candidates is *why so little* — because the representation identifies an object rather than describing it.

  • A representation arrives without the key field your reference resolver expects. What should the subgraph do?
    Treat it as bad input rather than a crash. Return a null slot for that position, or raise a field error at that index, and keep the list the same length as the input. Log the `__typename` and the keys that were present, never the whole object, since it comes from another service and may carry identifiers you would rather not have in logs.
  • Can one _entities call carry representations of more than one entity type?
    Nothing in the contract forbids it: `representations` is a list of an opaque scalar and the return type is a union, so the field is defined for a mixed list. Routers commonly group same-type representations into one fetch, but a correct resolver dispatches on `__typename` element by element instead of reading the first entry and assuming the rest match.
  • Why doesn't the router forward the parent fields it has already fetched?
    Because then two services could hold different values for the same field and the answer would depend on the query plan. Sending identity only keeps one owner per field. It also keeps representations small and uniform, which is what lets the router collapse many parents into a single fetch, and it forces the subgraph to apply its own authorization when it loads the entity.

It is a coat-check ticket rather than the coat: enough for the service that holds the garment to find it, and useless as a description of what is on the hanger.

saying these in an interview costs you the question

  • Says the representation contains the whole parent object
  • Assumes GraphQL validates the fields inside a representation
  • Thinks __typename is optional when a subgraph has one entity
  • Reads a non-key field out of a representation
  • Treats a representation as proof the entity exists
  • Handles only the first key a type declares

context

open as a page

Why does federation composition read a subgraph schema from _service { sdl } instead of introspection?

level: middleimportance: should knowfreq 38%

basics

~20 s

Standard GraphQL introspection exposes directive definitions but never where directives are applied, so the federation directives that describe entities would be invisible. The _service field returns the subgraph's schema as text with those applications intact, and works where introspection is disabled.

open as a page

Why must a subgraph's _entities resolver return its results in the input order?

level: seniorimportance: should knowfreq 47%

basics

~20 s

The router matches results to representations by position, not by identity. A reordered or shortened list silently attaches one entity's fields to a different entity. Return exactly one element per input, in order, with null where the entity could not be resolved.

open as a page

Why are federation's _entities and _service named with one underscore rather than two?

level: juniorimportance: nice to knowfreq 17%

basics

~20 s

The GraphQL specification reserves the double-underscore prefix for the introspection system and forbids schema authors from using it. Federation is a layer built on ordinary GraphQL, so its added members take a single underscore instead — legal, and conventionally set aside for machinery.

open as a page