When would a subgraph declare @key(fields: "id", resolvable: false) on a type?
answer
- An argument on the key directive
- Defaults to true
- Points at the object, cannot fetch it
- The stub carries key fields only
- Someone else must still be resolvable
basics
~20 sWhen the subgraph only points at the entity and cannot look one up. It declares the identity so its own fields can reference the object, while telling composition never to route key lookups for that type here.
solid answer
~50 sIn Apollo Federation 2, `@key` takes a `resolvable` argument that defaults to `true`; setting it to `false` declares a **reference-only** entity. The subgraph is saying: I return objects of this type from my fields, here is the identity I can produce for them, but I hold none of their data and cannot answer a lookup by that identity. In a bank statements graph, the statements subgraph exposing `Statement.account: Account` needs `Account` to exist in its schema with a key so it can name one — it has an account identifier on every statement row — but the account record lives elsewhere. Without `resolvable: false` the declaration would promise a lookup capability the service cannot honour. With it, composition knows to route every `Account` field to a subgraph that can actually resolve one, and the declaration stays honest.
code
graphql · 11 lines# statements subgraph: points at accounts, owns none of them
type Account @key(fields: "id", resolvable: false) {
id: ID!
}
type Statement @key(fields: "id") {
id: ID!
period: String!
closingBalanceMinor: Int!
account: Account!
}go deeper
You are unlikely to be asked this. It is enough to know that a subgraph can declare an entity purely so it can point at one, without holding any of its data.
Be able to explain that @key bundles an identity claim with a lookup-capability claim, and that resolvable: false keeps the first while dropping the second so composition never routes lookups there.
Show why an honest declaration beats a stub resolver that returns hollow objects: the failure of a false capability claim surfaces as a null or an empty object in a client response, far from the schema that caused it.
Frame it as ownership: a reference-only declaration is how a team says it names an entity without owning it, and it is a useful staging point when an entity's ownership moves between teams.
## Two different things a subgraph can say about an entity Declaring `@key` on a type normally bundles two claims: *this is the identity of the entity*, and *I can turn that identity back into an object*. Most of the time both are true together — the subgraph owning account records can both name an account and look one up. But a service often needs only the first half. In a bank statements graph, the statements subgraph stores an account identifier on every statement row. It wants to expose: ```graphql type Statement { id: ID! period: String! account: Account! } ``` For that field to typecheck, `Account` must exist in this subgraph's schema, and for the router to follow it across the boundary, `Account` must be an entity with a key. Yet this service holds nothing about accounts beyond the identifier. It can *point*, not *produce*. ## What the argument declares Federation 2's `@key` takes a `resolvable` argument defaulting to `true`. Written explicitly: ```graphql type Account @key(fields: "id", resolvable: false) { id: ID! } type Statement @key(fields: "id") { id: ID! period: String! account: Account! } ``` The first declaration is a **stub**: the type exists here with its key fields and nothing else, and the subgraph explicitly states it cannot answer a lookup by that key. Composition records it as a reference-only participant. When a client asks for an account's display name reached through a statement, the plan carries the identity to a subgraph that *is* resolvable for `Account` and asks there. ## Why declaring it honestly matters Omitting `resolvable: false` does not make the subgraph capable. It makes it dishonest. The declaration is a capability claim, and a service that claims to resolve an entity it knows nothing about has to do something when asked — return null, return a hollow object with only the key fields populated, or fail. Each of those is worse than the truth, and each surfaces far from its cause: a null branch or an empty object in a client response, with nothing in the schema hinting that one subgraph was pretending. Telling the truth in the schema keeps that ambiguity out of runtime entirely. Composition simply never plans a lookup here. ## What the stub must still contain Only the key fields. The reference-only declaration needs `id` because the subgraph must be able to *produce* the identity for objects it returns — that half of the promise is still real and still required. It contributes no other fields, and it does not need to know or restate anything the owning subgraph declares. Keeping the stub minimal is the point: it is a pointer, and a pointer that accumulates fields has quietly become a second owner of the entity. ## The limits At least one subgraph must be resolvable for the entity, or nothing can ever turn an identity back into an object. If every declaration of `Account` said `resolvable: false`, any field on `Account` reachable only through a lookup would be unreachable, and composition fails closed rather than publishing a graph with a hole in it. It is also worth being precise about what the flag does *not* mean. It says nothing about whether a particular reference will resolve at runtime — a valid identity pointing at a deleted account is a different problem entirely. It does not hide the type from clients. And it does not make the key optional: the stub still has to produce the full key for every object it returns, composite or nested parts included. ## Where it shows up in practice Two situations dominate. The first is the one above: a service that stores foreign identifiers and wants to expose them as typed references rather than as bare `ID` scalars, which is a real schema-quality win — a client can traverse `statement { account { displayName } }` instead of receiving an opaque string it must look up itself. The second is a graph where ownership is being moved: a subgraph relinquishing an entity can drop to reference-only while it still returns references, without waiting for every one of its fields to be migrated first. As an interview question this is a differentiator rather than a gate. Plenty of strong federation engineers have never needed the argument, because the subgraph they worked on happened to own the entities it referenced. But knowing it exists usually signals that someone has thought about the difference between naming an object and owning it.
- What must a reference-only subgraph still be able to do?Produce the full key for every object of that type it returns. Half of what `@key` declares is the identity itself, and that half is unchanged: if the key is composite or nested, the stub must supply every part. What it is excused from is the other half — turning an identity back into an object.
- What happens if every subgraph declares the entity with resolvable: false?Nothing can turn an identity into an object, so any field on that entity reachable only through a lookup cannot be served. Composition fails closed rather than publishing a graph containing a field no plan can reach. At least one subgraph must be resolvable for the entity to be useful for anything beyond passing its key around.
- Why expose a typed reference at all instead of just returning the account identifier as an ID?Because a bare `ID` scalar makes the client do the join: it receives an opaque string and must issue a second operation to make sense of it. A typed reference lets one document traverse `statement { account { displayName } }`, and the router does the cross-service work. The stub is the cost of that traversal being expressible in the schema.
saying these in an interview costs you the question
- Thinks resolvable: false hides the type from clients
- Reads it as a runtime signal that a reference is missing
- Puts it on the subgraph that owns the entity's data
- Adds non-key fields to a reference-only stub
- Assumes the router still sends lookups to that subgraph
- Believes it makes the key fields optional