skip to content

Schema Definition & Extensions

The schema definition that names the root operation types, the extend keyword, and descriptions the schema carries itself. Interviewers probe it once a schema spans several files or teams.

part ofGraphQLoverview, primer and where to startread it →
on this pageshow

questions

4

What does the `schema` definition in GraphQL SDL declare, and what happens if you omit it?

level: juniorimportance: must knowfreq 63%

answer

  1. Where does an operation begin?
  2. Three operation kinds, three entry points
  3. One optional block in the SDL
  4. Type names matter when it is absent

basics

~20 s

A schema definition names the object types that serve as entry points for the three operation kinds: query, mutation and subscription. Omit it and the roots default to types literally named Query, Mutation and Subscription.

solid answer

~50 s

GraphQL recognises three operation kinds, and each needs a **root operation type** — an ordinary object type where a client's selection set starts. The `schema { query: SeatMapQuery mutation: SeatMapMutation }` block is what maps kind to type. The query root is mandatory; mutation and subscription roots are optional, and a read-only service correctly declares neither. The definition may be omitted entirely, in which case the specification says the roots are found by name: `Query`, `Mutation`, `Subscription`. That is why most real schemas carry no `schema` block. The half people forget is that the defaults apply **only** when the block is absent — once you write `schema { query: SeatMapQuery }`, a type also called `Query` is just an ordinary object type with no special role. A type system may contain at most one schema definition, and it can carry a description and directives of its own.

code

graphql · 12 lines
graphql
schema {
  query: SeatMapQuery
  mutation: SeatMapMutation
}

type SeatMapQuery {
  seatMap(flightId: ID!): SeatMap
}

type SeatMapMutation {
  assignSeat(flightId: ID!, seatNumber: String!, passengerId: ID!): SeatAssignment
}

go deeper

for a junior

Be ready to name the three operation kinds, say that each needs a root object type, and state the default names used when the schema block is left out. That much is the whole screening question.

for a middle

Explain the mechanics: which root is mandatory, that roots are plain object types chosen by the block, and that the naming defaults apply only when the block is absent. Know why an empty root type fails to build.

for a senior

An interviewer expects you to spot the failure mode: a renamed root leaving a stale type behind, which builds cleanly and then rejects every old document at validation. Be able to say what introspection will and will not show about that.

for a principal

Own the convention decision. Custom root names buy nothing except a schema block every reader must check, while defaults keep every tool and every newcomer on the same footing. Decide it once and lint for it rather than leaving each team to choose.

## Where an operation starts Every GraphQL operation is a selection set that has to be resolved against *some* object type. A client sending `{ seatMap(flightId: "LH1071-2026-03-14") { cabins { rows { seats { number } } } } }` selects fields on a type it never names anywhere in the document. The **schema definition** is the construct that tells the service which type that is. GraphQL recognises three kinds of operation — query, mutation and subscription — and each kind needs its own entry point, called a **root operation type**. The schema definition maps kind to type: ```graphql schema { query: SeatMapQuery mutation: SeatMapMutation subscription: SeatMapSubscription } ``` Those three named types are ordinary object types. There is no special keyword, no marker interface, no separate category in the type system. `SeatMapQuery` is declared exactly the way `Seat` or `Cabin` is, and the only thing that makes it a root is that the schema definition points at it. ## Which roots are required The **query root is mandatory**. A schema without one is invalid — there would be no way to read anything, and the introspection meta-fields hang off the query root. **Mutation and subscription roots are optional.** A read-only seat-map service that exposes no writes and no streams legitimately declares only a query root, and omitting the other two is not laziness; it is the correct way to state that this service has no mutations and no subscriptions. A trap catches people scaffolding a new schema: an object type must declare at least one field. Writing `type Mutation` with an empty body, intending to fill it in next sprint, does not build. Leave the root out until you have a field to put in it. ## Omitting the schema definition The schema definition may be left out altogether. When it is, the roots are located by **name**: a type called `Query` becomes the query root, `Mutation` the mutation root, `Subscription` the subscription root. This is not a community habit that servers happen to share — it is written into the specification's type-system definition language, which is why every implementation behaves identically here. It is also why the large majority of real schemas contain no `schema { ... }` block at all: you only need one when a root carries a different name. ## The half of the rule people forget The defaults apply **only when the schema definition is omitted**. Once the block is written it is the sole authority. Given: ```graphql schema { query: SeatMapQuery } type SeatMapQuery { seatMap(flightId: ID!): SeatMap } type Query { legacySeatMap: SeatMap } ``` `Query` here is an object type like any other. It is not a root, it is not merged into `SeatMapQuery`, and `legacySeatMap` is unreachable unless some field somewhere returns a `Query`. This bites a team that renames its root and leaves the old type behind: the schema still builds, the old type is still visible through introspection, and every request that starts at the old field fails validation with a message about a field that does not exist on `SeatMapQuery`. The type exists; it is simply no longer an entry point. ## Roots are not sealed off Nothing in the type system treats a root as a special case, so nothing stops a root type from being referenced as a field's type elsewhere in the schema. You almost never want that, and it is worth knowing only because it explains why roots must be declared at all: the type system has no way to infer which of the object types is an entry point, so either the block says so or the naming convention does. ## One per schema, extensible afterwards A type system may contain at most one schema definition. When a root has to be added later — a subscription root arriving with a feature written by another team, in another document — the extension form exists precisely for that, and reaching for it is better than editing a definition somebody else owns. The schema definition can itself carry a description and directive applications, which is where a schema-wide annotation goes when a directive is declared to allow that location. ## What this looks like in an interview This is asked early because it separates people who have written SDL from people who have only sent documents. The complete answer is four sentences: three operation kinds, three root object types, query required and the other two optional, and default names by type when the block is omitted — only then. The usual follow-up is the trap above, and the correct response to “what is a type named `Query` in a schema whose block names something else” is simply: nothing special.

  • Which root operation types must a GraphQL schema provide, and which may it leave out?
    The query root is required. Mutation and subscription roots are optional and should be omitted when the service has no writes or no streams — declaring an empty root type is invalid anyway, because an object type must have at least one field.
  • If the schema definition names `query: SeatMapQuery`, what is a type called `Query` in the same schema?
    An ordinary object type with no special role. The default-name rule applies only when the schema definition is omitted; once the block exists it is the only thing that decides the roots, so `Query` is reachable only if some field returns it.
  • Can a subscription root be added later without editing the original schema definition?
    Yes — the schema definition has an extension form, so a later document can add a root operation type to the existing schema rather than rewriting a block another team owns. Adding a root that is already declared is a validation error.

saying these in an interview costs you the question

  • Believes every schema must contain a schema block
  • Claims a query root must literally be named Query
  • Thinks mutation and subscription roots are required
  • Treats root types as a special kind rather than object types
  • Scaffolds an empty type Mutation and expects it to build
  • Assumes a type named Query is the root despite an explicit block

context

open as a page

What does `extend type` do in GraphQL SDL, and what may an extension not do?

level: middleimportance: should knowfreq 47%

basics

~20 s

It adds fields, interfaces or directives to a type already defined elsewhere in the same type system, without touching the original definition. Extensions may only add: redeclaring an existing field is a validation error, never an override.

open as a page

When a GraphQL schema is assembled from several SDL documents, what actually depends on their order?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Meaning does not: extensions only add, and a duplicate field is a validation error rather than an override, so any legal document set yields the same type system. Only the printed field order changes — so diff schemas semantically, not as text.

open as a page

How do you document a type or field in GraphQL SDL, and why is a `#` comment not enough?

level: juniorimportance: nice to knowfreq 21%

basics

~20 s

Put a string literal — usually a triple-quoted block string — immediately before the definition. That description is part of the schema and readable through introspection. A # comment is ignored by the parser and never reaches a client.

open as a page