skip to content

Tooling & Observability

What a typed, introspectable contract makes possible — and the operational blindness one endpoint creates. The second half separates teams who have run a graph in production from those who have not.

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

questions

page 1 of 2

What inputs does a GraphQL typed client generator need, and what does it emit?

level: juniorimportance: must knowfreq 58%

answer

  1. Two inputs, not one
  2. The schema is only half of it
  3. Your own documents are the other half
  4. Result shape follows the selection set
  5. Per operation: variables plus result

basics

~20 s

Two inputs: the schema, as an SDL file or as an introspection result, and the client's own operation documents. It emits, per operation, a variables type and a result type shaped by that operation's selection set.

solid answer

~50 s

A typed client generator takes two things. First the schema, supplied either as SDL text or as the result of an introspection query against a running server. Second the client's *own* operation documents — the queries, mutations, subscriptions and named fragments this application actually sends. From the pair it emits, per operation, a variables type derived from the operation's variable definitions and a result type derived from its selection set, plus shared types for the enums and input objects those operations touch. The result type mirrors the **selection set**, not the schema's object type: an operation asking a hospital appointment graph for `appointment { id clinician { fullName } }` yields a type with exactly those two branches, and a different operation over the same `Appointment` type yields a different type. All of this happens at build time; nothing in the generated code re-checks the response at runtime.

code

graphql · 10 lines
graphql
query ClinicianDayView($clinicianId: ID!, $day: Date!) {
  clinician(id: $clinicianId) {
    fullName
    appointments(day: $day) {
      id
      startsAt
      patient { displayName }
    }
  }
}

go deeper

for a junior

Be ready to name both inputs — the schema and your own operation documents — and to say that the result type follows the selection set. Interviewers ask this to check you understand why a client cannot be typed from the schema alone.

for a middle

Explain the mechanics: response keys and aliases decide member names, variable definitions decide the variables type, and enums and input objects come across by reference. Be able to say why two operations on one type produce two unrelated shapes.

for a senior

Show you treat the generated artefact as a snapshot with an expiry date. Talk about pinning the schema input, running generation in the build rather than by hand, and what actually happens on the day the schema moved and the artefact did not.

for a principal

Own the policy question: where the schema snapshot comes from, who publishes it, and whether every client team regenerates from the same pinned artefact. The tradeoff is coupling client builds to a schema release versus letting each team drift on its own copy.

## Why two inputs and not one It is tempting to think of a typed client generator as a schema-to-types translator, the way a tool that reads a database schema emits a row class per table. GraphQL does not work that way, and the reason is the whole point of the query language: **the server declares a graph, the client declares a slice of it.** The schema says an `Appointment` has fourteen fields. The document your screen actually sends asks for three of them. A type that carried all fourteen would be a lie — the other eleven are simply not keys in the response object. So the generator needs both halves. The schema tells it what is legal, what each field's type and nullability are, and what the enums and input objects look like. The documents tell it which of that legal surface this application asked for. Only the intersection can be typed honestly. ## The schema half The schema arrives in one of two forms. As **SDL** — a text file of type definitions, usually committed to the repository or published as a build artefact. Or as an **introspection result** — the JSON a server returns when queried through the introspection meta-fields, which a generator can fetch by pointing at a running endpoint. Both describe the same type system; they are not equally complete, and which one you feed the tool has consequences, but for the basic shape of the output either will do. ## The document half The second input is a set of executable documents: named operations and the named fragments they spread. Anonymous documents are awkward for a generator, because the operation name is what the generated type gets named after — this is why codegen setups almost always require every operation to carry a name. ## What comes out For each operation the generator emits a pair: * A **variables type**, built from the operation's variable definitions. A variable declared non-null with no default becomes a required member; a nullable variable becomes an optional one. * A **result type**, built by walking the selection set. Every selected field becomes a member; the member's name is the **response key** — the alias when one is written, otherwise the field name — and its type is the schema type of the field, recursively expanded for object-typed fields. Alongside those it emits shared declarations for the schema's enums and input object types, because those appear inside variables and results by reference rather than by selection. ```graphql query ClinicianDayView($clinicianId: ID!, $day: Date!) { clinician(id: $clinicianId) { fullName appointments(day: $day) { id startsAt patient { displayName } } } } ``` That single document produces one variables type with a required `clinicianId` and a required `day`, and one result type nested exactly three levels deep — clinician, appointments, patient — with `displayName` and nothing else under `patient`, even though the schema's `Patient` type has a dozen more fields. ## Two consequences people miss **The result type is per-operation, not per-type.** A second query that selects `patient { displayName dateOfBirth }` gets a *different*, unrelated generated shape. Some generators let a named fragment become a reusable named type, which is why fragment colocation is a common house style: it is the only way to get a shareable, non-duplicated piece of a result type. **Aliases move the key.** Writing `morning: appointments(day: $day)` renames the member in the response and therefore in the generated type. The generated code follows the document text, not the schema. ## What generation is not Generated code is a **compile-time assertion**, not a runtime guard. Nothing in it validates the bytes that come back. If the schema changed after generation — a field removed, a type narrowed — the compiler still sees the old promise and the code still compiles; you find out when a member you were told exists is missing at runtime. That gap is the whole reason a small platform team wires regeneration into the build rather than running it by hand: the artefact is only as true as the schema snapshot it was produced from. It is also not a validator of your documents. Most generators will refuse to emit code for a document the schema rejects, so in practice they catch invalid selections early — but that is a side effect of needing to resolve every field's type, not a substitute for validating the shipped documents against the schema the server is actually running.

  • Two screens select different fields of the same object type. Why does the generator emit two unrelated result types rather than one?
    Because the result type describes a response, and the two responses genuinely have different keys. A shape carrying the union of both would let code read a field the second query never asked for. The usual way to share is a named fragment: many generators emit one named type per fragment definition and compose the operation types out of those, so the shared slice is written once and both operations spread it.
  • What happens to the generated member name when a field is aliased in the document?
    The member takes the alias. The response key is the alias when one is present and the field name otherwise, and the generated type follows the response key because that is what will actually be in the payload. So `morning: appointments(day: $day)` produces a member called `morning`; the schema field name appears nowhere in the generated result type.
  • If the schema changes after generation, when does the mismatch surface?
    Not until you regenerate, or until runtime. The generated code is a build-time snapshot with no runtime checks, so a removed field still type-checks against yesterday's artefact and simply is not in the payload. That is why regeneration belongs in the build and why the schema input has to be pinned to a known version rather than fetched from whatever environment happens to answer.

The schema is the whole menu; your documents are the orders your kitchen actually places. A generator types the orders, not the menu.

saying these in an interview costs you the question

  • Says the generator only needs the schema
  • Thinks the result type mirrors the schema's object type
  • Expects generated code to validate responses at runtime
  • Assumes an alias leaves the generated member name unchanged
  • Believes generated types stay true after a schema change

context

open as a page

What does an in-browser GraphQL explorer read to build its docs pane and autocomplete?

level: juniorimportance: must knowfreq 62%

basics

~20 s

An introspection response from the same endpoint, fetched once when the tab loads. Every type, field, argument, default value and description shown in the docs pane and offered by the completion list comes from that single reply.

open as a page

Why is one GraphQL endpoint's overall request latency metric not actionable?

level: juniorimportance: must knowfreq 64%

basics

~20 s

Because every operation shares one route, so the metric blends unrelated workloads: a few-millisecond name lookup and a multi-second report land in the same series. The number tracks the traffic mix, not any operation's health.

open as a page

What is a schema-derived GraphQL mock server, and where do its field values come from?

level: juniorimportance: must knowfreq 52%

basics

~20 s

A schema-derived mock is a GraphQL server built from the schema alone, with no resolvers behind it. Every field returns a value invented from its declared type - a placeholder string, a number, an enum member, a short list.

open as a page

What does executing a test document against the real GraphQL schema catch that calling a field resolver function cannot?

level: juniorimportance: must knowfreq 66%

basics

~20 s

Everything the executor does around a resolver: coercing variables, applying aliases and fragments, checking each returned value against the field's declared type, propagating non-null failures, and recording every field error with its path in the errors list.

open as a page

How should GraphQL request metrics count errors returned in a successful response?

level: middleimportance: must knowfreq 57%

basics

~20 s

Meter the response body, not the transport outcome. Count request errors — parse or validation failures, where no data entry is produced — separately from field errors, where data is present with nulls and each entry carries a path.

open as a page

In a per-field GraphQL trace, what do a resolver span's self time and elapsed time each measure?

level: middleimportance: must knowfreq 57%

basics

~20 s

Elapsed is wall clock from the moment the executor invokes the field's resolver until that field's value is complete, so it includes every child field. Self time subtracts the children, leaving the field's own work. Rank by self time.

open as a page

Field usage analytics reports zero resolutions of Listing.priceHistory for 87 days - what could make that reading wrong?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Zero recorded usage proves the collector saw nothing, not that nobody used the field. Sampling, uninstrumented instances, cached responses, unattributed traffic, rare or seasonal callers and a retention window shorter than the claim can all produce a zero for a live field.

open as a page

A GraphQL response returns 200 with a populated errors array - what do you log?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Log every entry in the errors array with its path, or its document locations when it has no path, plus the error count, whether data came back partial or null, the document hash, and the internal cause.

open as a page

What does a per-operation GraphQL log line record, and what stays off it?

level: juniorimportance: should knowfreq 44%

basics

~20 s

A per-operation GraphQL log line records the operation type and name, a hash of the executed document, the duration, and the path of every error the response carried. Raw variable values and the full document text stay off it.

open as a page

How does a typed client generator map GraphQL nullability onto generated types?

level: middleimportance: should knowfreq 52%

basics

~20 s

Each nullability modifier maps independently: a nullable field becomes an optional member, a non-null field a required one, and a list's own nullability is separate from its items'. Non-null variables without defaults become required arguments.

open as a page

Why does a typed client generator inject __typename into a selection on a union?

level: middleimportance: should knowfreq 44%

basics

~10 s

A union's members share no fields, so each response entry carries only that member's keys. __typename is the runtime discriminant, letting the generator emit a tagged union the caller can match on exhaustively.

open as a page

Why does a GraphQL explorer keep variables and HTTP headers in panes separate from the document?

level: middleimportance: should knowfreq 52%

basics

~20 s

Because they belong to two different layers. The document and its variables are separate members of the GraphQL request itself, while headers are HTTP metadata carried outside it. The panes mirror the request the explorer is about to build.

open as a page

Why is counting GraphQL field usage from request document text unreliable?

level: middleimportance: should knowfreq 42%

basics

~20 s

Document text shows what a client selected, not what the server resolved. Skipped branches, unmatched type conditions, null parents and rejected operations mean a selected field may never run, and a client sending only a document hash sends no text at all.

open as a page

How do you make a schema-derived GraphQL mock return domain-shaped, repeatable values?

level: middleimportance: should knowfreq 34%

basics

~20 s

Supply override functions keyed by type name - the mock merges what you return with type-derived defaults for the fields you left out - and remove randomness at the source, with a seeded generator or pinned values.

open as a page

Why do schema linters require a reason on every @deprecated field?

level: middleimportance: should knowfreq 46%

basics

~10 s

Because the specification makes the argument optional and defaults it to "No longer supported", which tells a consumer nothing. The string is the only migration instruction that reaches clients, and it travels through introspection.

open as a page

Why does a schema lint rule flag the nullable item type in a [Leg] field?

level: middleimportance: should knowfreq 39%

basics

~20 s

Because [Leg] permits a null in every element slot, and a null element has no natural meaning in a collection. Every consumer must branch on a hole nobody put there on purpose, so linters push for [Leg!].

open as a page

A document that succeeds in a GraphQL explorer fails from the application. How do you diagnose it?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Stop comparing documents and compare requests. Capture both calls in full — body, headers, credentials, target environment, variable magnitudes — then replay the application's exact request from the explorer. The difference is almost always identity or scale, not the document.

open as a page

Why is a client-supplied operation name a risky GraphQL metric dimension?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Because the caller authors it. Nothing bounds the value set, so a client that generates a fresh name per build multiplies your time series; and nothing verifies it, so an expensive document can arrive labelled as a cheap operation.

open as a page

A schema-derived GraphQL mock keeps a client suite green - which real bugs does it hide?

level: seniorimportance: should knowfreq 41%

basics

~20 s

Everything the schema does not encode: who may read a field, which value combinations can actually occur, what a failed response looks like, and what the request costs. A mock derives values from types, so shape is all it can be right about.

open as a page

Per-field GraphQL tracing emits thousands of spans per document; how do you afford that in production?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Sample whole requests rather than individual fields, so a kept trace is complete. Skip spans for fields with no real resolver, collapse sibling list items into one aggregated span, and take steady-state numbers from per-coordinate aggregates instead of spans.

open as a page

How do you catch, before release, that a client's GraphQL documents fail against the deployed schema?

level: seniorimportance: should knowfreq 47%

basics

~20 s

Extract every executable document from the client's source, validate each against the schema currently deployed to the target environment, and fail the build on any error. Validating against your own branch's schema proves only self-consistency.

open as a page

How do you bound the cost of GraphQL field usage collection without losing deletion evidence?

level: principalimportance: should knowfreq 38%

basics

~20 s

Split the signal in two. Capture the distinct coordinate set per execution unsampled and roll it into last-seen rows bounded by schema size; sample the volume and latency stream freely. Then bound the client dimension, because self-reported versions are unbounded.

open as a page

How do you set latency and availability SLOs for one shared GraphQL endpoint?

level: principalimportance: should knowfreq 38%

basics

~10 s

Not at the endpoint. Choose a small set of user-facing operations from a known document set, give each its own objective, and write down what counts as success when a response is partial.

open as a page

Why can a deprecated field be missing from a GraphQL explorer's docs pane entirely?

level: juniorimportance: nice to knowfreq 20%

basics

~20 s

Introspection hides deprecated entries by default: the field and enum-value lists on a type take an includeDeprecated argument that defaults to false. An explorer whose introspection request omits it never receives the field, so the pane cannot show it.

open as a page

What does GraphQL field usage analytics record that an HTTP access log cannot show?

level: juniorimportance: nice to knowfreq 26%

basics

~20 s

Field usage analytics records the schema coordinates - Type.field pairs such as Listing.priceHistory - that an execution actually resolved, and which client resolved them. An HTTP access log sees one URL, one method and one status for every operation alike.

open as a page

Where in a GraphQL response do per-field timings go, and is their format specified?

level: juniorimportance: nice to knowfreq 19%

basics

~20 s

Under the response's extensions entry — besides data and errors, the only top-level entry a GraphQL response may carry. The specification requires a map and defines nothing about its contents, so every timing format there is a convention.

open as a page

Why is a GraphQL operation name an unreliable key for grouping log lines?

level: middleimportance: nice to knowfreq 28%

basics

~10 s

The operation name is arbitrary text the client writes in its own document. Nothing binds it to that document, different documents can share one name, and an operation may have no name at all.

open as a page

What does committing a GraphQL schema's printed SDL as a CI snapshot actually catch?

level: middleimportance: nice to knowfreq 31%

basics

~20 s

Any change to the schema's printed shape, turned into a reviewable diff. It is most valuable with a code-first schema, where SDL is a byproduct of code and a rename or a nullability change is otherwise invisible in review.

open as a page

showing 1–30 of 32