What must an object type declare to implement a GraphQL interface, and what may it change?
answer
- SDL has no inheritance to lean on
- Coverage is checked when the schema builds
- Return types may tighten, arguments may not
- Added arguments carry one restriction
- Interface hierarchies must be spelled out
basics
~20 sIt must redeclare every interface field, with every argument the interface declared and the identical argument types. It may return a stricter type — a non-null or a subtype — add nullable arguments, and add fields of its own.
solid answer
~50 sThere is no inheritance in SDL: the implementing object type physically repeats each interface field, and the schema fails to build if one is missing. Coverage is strict on names and on arguments — every argument the interface field declares must appear with **exactly the same type**, no widening or narrowing — but the return type is **covariant**: the implementer may return the same type, a non-null wrapping of it, a list whose elements are stricter, or a possible type of it (an implementer of that interface, or a member of that union). The implementer may also add extra arguments, provided their types are nullable, and any number of extra fields. If the interface itself implements another interface, the object type must declare that one too. Field order, descriptions and applied directives are all free.
code
graphql · 24 linesinterface Protocol {
id: ID!
}
type ApprovedProtocol implements Protocol {
id: ID!
approvedOn: String!
}
interface Endpoint {
measuredAt(precision: TimePrecision = DAY): String
protocol: Protocol
}
type PrimaryEndpoint implements Endpoint {
measuredAt(precision: TimePrecision = DAY, timeZone: String): String!
protocol: ApprovedProtocol!
adjudicated: Boolean!
}
enum TimePrecision {
DAY
SECOND
}go deeper
Know that implementing an interface means writing its fields out again in your own type, and that leaving one out breaks the schema build rather than a request. Extra fields on your type are always allowed.
Be ready to state the rules precisely: arguments are invariant and must all be present, added arguments must be nullable, and return types may only get stricter — non-null, stricter list elements, or a possible type of an abstract field type.
Show that you cost the repetition before you widen an interface: one new field multiplies by the implementer count, and the schema does not build until every one is edited. Explain how you keep such a change reviewable.
Own the tradeoff between a small interface that stays cheap to evolve and a rich one that gives clients more, and be clear about whether your schemas are hand-written or emitted — that choice decides who pays the repetition tax.
## "Implements" is a claim the build checks, not an inheritance mechanism Writing `type PrimaryEndpoint implements Endpoint` does not import anything. The SDL text of `PrimaryEndpoint` must contain a declaration of every field `Endpoint` declares, spelled out again. The interface is a *specification of what must be present*, and schema construction is where the claim is verified: miss a field and the server refuses to build the schema, which is a deploy-time failure rather than a request-time one. That distinction is worth saying out loud in an interview, because it is the reason interfaces are safe to rely on at all. ## Field coverage: names are exact Every field name declared on the interface must appear on the implementer. Not a similar name, not an aliasable one — the same name. Extra fields on the implementer are always fine and are the normal case; that is exactly what makes an implementer more than the contract. ## Return types: covariance, in four allowed shapes The implementing field's type does not have to be identical. It has to be *valid for* the interface field's type, and there are four ways to be valid: 1. **The same type.** Always allowed. 2. **A non-null wrapping.** Where the interface declares `String`, the implementer may declare `String!`. Tightening is allowed because every value the implementer can produce is still a legal value of the interface's type; loosening is not — `String` where the interface said `String!` is invalid. 3. **A list whose item type is itself valid.** `[Protocol]` may become `[Protocol!]!` by the same reasoning, applied element by element. 4. **A possible type of an abstract interface field type.** If the interface field is typed as an interface, the implementer may return any object type implementing it; if it is typed as a union, any member of that union. A registry example puts all of these in one snippet: ```graphql interface Endpoint { measuredAt(precision: TimePrecision = DAY): String protocol: Protocol } type PrimaryEndpoint implements Endpoint { measuredAt(precision: TimePrecision = DAY, timeZone: String): String! protocol: ApprovedProtocol! } ``` `String` became `String!`; the interface-typed `Protocol` became the concrete `ApprovedProtocol!` that implements it. Both are legal, and both are genuinely useful: an implementer that can promise more should be allowed to say so. ## Arguments: invariant, and additions must be optional Arguments are where candidates most often guess wrong. For every argument the interface field declares, the implementing field must declare an argument of the same name **accepting the same type** — no covariance, no contravariance, no dropping it because this implementer ignores it. If `Endpoint.measuredAt` takes `precision: TimePrecision`, then `PrimaryEndpoint.measuredAt` takes `precision: TimePrecision`, full stop. The implementer may add arguments the interface never mentioned, subject to one rule: **an added argument must not be of a non-null type.** The reason is mechanical. A client that only knows the interface will write a selection with no value for the extra argument, and that selection must remain valid; a required argument would make the same document valid or invalid depending on which concrete type turned up, which the type system cannot express. `timeZone: String` above is fine; `timeZone: String!` would make the schema invalid. ## Transitive interfaces must be declared explicitly Since interfaces may implement other interfaces, an implements clause can be transitive — and the specification does not close it for you. If `Endpoint implements Auditable`, then any object type implementing `Endpoint` must also name `Auditable` in its own implements clause, and satisfy its fields. It is a redundancy the schema author has to type out, and it is a common build failure the first time a team refines an interface hierarchy. ## What is deliberately free Field order does not matter; a schema is a set of named declarations, not a sequence. Descriptions are not inherited — each declaration carries its own, and a good implementer writes one that says something more specific than the interface's. Applied directives are per-declaration too: an interface field is not automatically deprecated because an implementer's is, or vice versa. Deprecating an interface field does not deprecate anything on its implementers, which surprises people who expected an override relationship. ## The repetition tax, and why it is the real interview point The consequence of all this is arithmetic. An interface with 6 fields and 23 implementing object types is 138 field declarations that must stay in agreement, and adding one field to the interface is 23 edits before the schema builds again. Teams handle it two ways: some generate the SDL from server-side types so the repetition is emitted rather than typed, and some keep interfaces deliberately small for exactly this reason. Neither removes the rule — the built schema still contains every repeated declaration, and every client and every code generator still sees them. Knowing that the tax scales with implementer count is what turns this from a syntax question into a design one.
- Why must an argument the implementer adds be of a nullable type?Because a client selecting through the interface will not supply it. A document written against the interface must stay valid whichever concrete type the field returns, so an argument that is required on one implementer would make validity depend on runtime data. Nullable additions are ignorable; non-null ones are not, which is why the specification forbids them.
- If an interface field is marked deprecated, are the implementers' fields deprecated too?No. Directives are applied per declaration and are not inherited in either direction, and the same goes for descriptions. Deprecating an interface field signals the contract is going away, but each implementing type's field carries its own deprecation state, so a full deprecation means editing every implementer as well as the interface.
- What happens if an implementing type omits one field the interface declares?The schema is invalid and the server fails to build it, so the failure lands at startup or in the build rather than on a request. That is the whole value of the rule: a client selecting an interface field never has to defend against an implementer that quietly does not have it.
saying these in an interview costs you the question
- Thinks implementers inherit field declarations automatically
- Allows an implementer to drop an unused argument
- Adds a non-null argument not on the interface
- Loosens a non-null interface field to nullable
- Assumes deprecating an interface field deprecates implementers
- Forgets transitive interfaces must be declared too