What do the Relay server specification's Node interface and node root field give a client?
answer
- One identifier, one lookup field
- Refetch without replaying the query
- Unique across the schema, not per type
- Interface return means inline fragments
- Relay server spec, not GraphQL spec
basics
~20 sThe Node interface makes every implementing type expose a non-null ID field whose value is unique across the whole schema. The node root field takes one of those identifiers and refetches that object, whatever its concrete type is.
solid answer
~50 sGlobal Object Identification comes from the **Relay server specification**, not from the GraphQL specification itself — a valid GraphQL server may omit it. It has two halves. An interface, `interface Node { id: ID! }`, which any refetchable type implements; the field is named `id`, typed `ID`, non-null, and its value must be unique across the entire schema rather than merely within its own type. And a root field, `node(id: ID!): Node`, which takes one of those identifiers and returns the object it names. Because the return type is the interface, a document can directly select only interface fields — in practice `id` — so type-specific fields need inline fragments, usually with `__typename` alongside. The nullable return type is how an unmatched identifier is reported. What this buys is reach: one field addresses any implementing object, so a caller holding only an identifier can reload that object without replaying the query that produced it.
code
graphql · 20 linesinterface Node {
id: ID!
}
type SolarPanel implements Node {
id: ID!
serial: String!
tiltDegrees: Float!
}
type Inverter implements Node {
id: ID!
serial: String!
firmwareVersion: String!
}
type Query {
node(id: ID!): Node
panel(serial: String!): SolarPanel
}go deeper
Recall the three moving parts: an interface named Node with a non-null ID field, a node root field taking one of those identifiers, and inline fragments for anything type-specific. Be able to write the document.
Explain why node returns the interface rather than a concrete type, what that forces the document to look like, why the value must be unique graph-wide, and what an unmatched identifier returns.
Show where you drew the line: which types in your schema implement Node and which deliberately do not, and how you keep one polymorphic entry point observable when metrics group by field name.
Own whether the schema adopts the convention at all. It is a standing promise that every implementing type stays identifiable and refetchable for the life of the API, and that promise is far harder to withdraw than to make.
## The problem it solves Most APIs identify an object relative to something else: a row is "number 8412 in the panels table", a nested value is "the third reading under the panel you just fetched". That is enough while a client is walking down from a root field, and useless the moment it holds an object in isolation and wants it again. A dashboard that cached a solar panel yesterday, a link someone pasted into a chat, a background refresh that wants one row and emphatically not the twelve-field query that produced it — all of them need to say *this object*, with nothing else in hand. Global Object Identification is the convention that answers that. It comes from the **Relay server specification**, a separate document from the GraphQL specification; a GraphQL server without it is perfectly valid. It is adopted widely enough that many interviewers treat it as part of the language, and knowing which half is specified where is itself worth a mark. ## The two halves **The interface.** The schema declares a `Node` interface carrying a single field, `id`, of type `ID`, non-null. Any type that wants to be refetchable implements it. The name, the type and the non-null modifier are all the specification's, not house style. The value must be unique across the **entire schema**, not within its own type. In a solar-array telemetry graph a `SolarPanel` and an `Inverter` may both live at local key 8412 in their own tables; if both surfaced `id: "8412"`, nothing downstream could tell the two apart, and the scheme is broken. Uniqueness across the whole graph is what makes the identifier *global*. **The root field.** The query type declares `node(id: ID!): Node`. It takes one identifier and returns the object it names, typed as the interface. The return type is nullable, and that is deliberate: an identifier matching no object comes back as `null` for that field rather than blowing the request up. ## What the client's document has to look like Because `node` is typed as an interface, a selection set may name only the fields the interface declares — in practice just `id`. Everything type-specific has to sit inside an inline fragment, and `__typename` is usually selected too so the caller knows which branch it actually received: ```graphql query RefetchNode($id: ID!) { node(id: $id) { __typename id ... on SolarPanel { serial tiltDegrees } ... on Inverter { serial firmwareVersion } } } ``` Writing `node(id: $id) { serial }` instead fails validation before execution ever starts, because `serial` is not a field of `Node`. That is the mistake this question is usually screening for. ## What the reach buys * **Refetch.** Reload a single object without knowing, storing or replaying the query that first produced it — the original motivation for the whole convention. * **A stable key.** Two different queries that both mention panel 8412 produce the same identifier, so anything downstream that keys on it converges on one entry instead of two. * **Handoff.** An identifier can travel between screens, jobs, services or people and still resolve on its own, because it carries everything needed to find the object. ## What the reach costs The same property is the bill. `node` is one entry point that can return anything in the schema, and the argument arrives as an opaque string, so a metrics pipeline that groups by field name sees a single hot field and cannot tell you which backend the traffic is actually hitting. If you want that visibility you have to decode the identifier and label the span or counter with the concrete type yourself. Plan for that before the field is in production, not after a busy morning peak leaves you staring at one undifferentiated line on a chart. ## Which types should implement it Not all of them, and interviewers do ask. The useful test is whether an object is meaningfully refetchable on its own. Entities with an independent lifecycle qualify — a panel, an inverter, a maintenance window. Wrapper and value types do not: connection and edge types, mutation payloads, an embedded address, a per-request aggregate. Those exist only inside the result that produced them; giving one an `id` invites callers to store and refetch a thing that has no independent existence, and forces you to invent a stable identifier for something that never had one. ## Where it is not the answer Adopting this convention does not retire ordinary lookup fields. A caller who knows a serial number, an email address or a slug does not hold a global identifier and should not be made to construct one — constructing identifiers client-side is exactly what opacity forbids. `panel(serial: "...")` stays. `node` serves the narrower and very common case of a caller who already holds an identifier the server previously handed out.
- Which types in a schema should implement the Node interface, and which should not?Types with an independent lifecycle that a caller could sensibly reload on its own — a panel, an inverter, a maintenance window. Not connection or edge types, mutation payloads, embedded value objects or per-request aggregates: they exist only inside the result that produced them. Giving one an identifier invites callers to store and refetch something with no independent existence, and forces you to invent a stable key for an object that never had one.
- Why does a client usually select __typename alongside id when it refetches through node?Because `node` is typed as the interface, the value could be any implementing type. `__typename` tells the caller which inline fragment's branch it actually received, so it can dispatch on the result instead of guessing, and anything storing the object can record what kind of object it just refreshed. Without it, a response whose type-specific fields all happen to be null is ambiguous.
- Does adopting Global Object Identification mean dropping ordinary lookup root fields such as panel(serial:)?No. `node` serves a caller who already holds an identifier the server issued. A caller holding a serial number, an email address or a slug holds no such identifier, and since identifiers are opaque they must not build one. Keep the natural lookup fields; the two coexist, and a schema with only `node` is hostile to first-time callers.
The node field is a lost-property counter: you hand over the claim ticket and get the item back, without having to describe the shelf it was originally taken from.
saying these in an interview costs you the question
- Says the Node interface is part of the GraphQL specification
- Selects a concrete type's fields directly on node
- Thinks the identifier need only be unique within its type
- Makes every type in the schema implement Node
- Expects an error, not null, for an unmatched identifier
- Claims node replaces all other lookup root fields