skip to content

Why do clients select __typename alongside inline fragments in GraphQL?

level: middleimportance: should knowfreq 58%

answer

  1. The flat response hides which branch ran
  2. A meta-field the schema never declares
  3. Always the concrete name, not the abstract one
  4. Caches need more than an identifier
  5. Clients often inject it for you

basics

~10 s

Because matched inline-fragment fields arrive flat, the response alone does not say which branch applied. __typename is a meta-field returning the concrete object type's name, so it is the discriminator a client branches on.

solid answer

~50 s

`__typename` is a **meta-field** available on every object, interface and union type, typed `String!`, and it always resolves to the name of the **concrete object type** being resolved at that position — never the interface or union name written in the schema. It matters with inline fragments because matched branches merge their fields flat into one object: the JSON for a matched `Pallet` looks structurally like any other object, so without a discriminator the client is left inferring the type from which keys happen to be present. Selecting `__typename` next to the branches makes that explicit and cheap. It is also why many client libraries and typed code generators add it automatically — a normalized client cache needs the type name plus an identifier to key an entry safely, since an identifier alone can collide across types. It is implicit, so it never appears in a type's declared field list.

code

graphql · 9 lines
graphql
query BinContents($binId: ID!) {
  bin(id: $binId) {
    contents {
      __typename
      ... on Pallet { sscc }
      ... on LooseCase { caseGtin }
    }
  }
}

go deeper

for a junior

Know that __typename is available on any object, interface or union field, returns a string, and tells you which concrete type came back. Being able to add it to a query and read the result is enough at this level.

for a middle

Explain why a flat, merged response needs an explicit discriminator, that the value is always the concrete object type's name, and that the field is implicit rather than declared — so introspection's field lists do not contain it.

for a senior

Connect it to what consumes it: generated discriminated types and normalized caches that key on type plus identifier. Be ready to describe a cache-collision failure caused by keying on the identifier alone and how you would diagnose it.

for a principal

Own the coupling it creates: every branching client compares against a type-name string, so renaming an object type is a client-visible change even though the schema stays valid. Decide how your organisation detects and stages that.

## Why a discriminator is needed at all Inline fragments narrow an abstract field, but the result they produce is **flat**. A union-typed `contents` field selected with two branches returns objects whose keys are simply the fields that matched — there is no wrapper naming the branch, and there is no marker distinguishing "this key is missing because the branch did not apply" from "this key is missing because I did not ask for it". The document knows what it asked for; the runtime shape is the only thing that came back. So a consumer that wants to render one row per member type has three options: guess from which keys are present, ask for something that is guaranteed present on one branch only, or ask the server directly. The third is what `__typename` is for. ## What `__typename` actually is It is a **meta-field**: implicit on every object, interface and union type, typed `String!`, and not declared anywhere in the schema. It does not appear in a type's field list, which is why it will never show up in a hand-written SDL file or in a generated type's declared members. Its value is the **name of the concrete object type** currently being resolved. Selected on a field declared as an interface or a union, it does not return `InventoryNode` or `BinContent`; it returns `Pallet`, `LooseCase` or `ReturnTote` — whatever the server actually resolved the abstract type to. Being a leaf field of type `String!`, it never carries a selection set of its own, and it may be requested anywhere a composite type sits: at the top level of an operation, inside every inline fragment, or once beside them. The specification carves out one place it may not be used — as a root field of a subscription operation. ## Selecting it once, not per branch Because matched branches merge into the same object, `__typename` selected once outside the branches is enough: ```graphql contents { __typename ... on Pallet { sscc grossWeightKg } ... on LooseCase { caseGtin quantity } } ``` Repeating it inside each branch is legal and harmless — identical fields merge — but it buys nothing. Putting it *only* inside the branches is the actual mistake: an object of an unhandled member type then carries no keys at all, and the consumer has nothing to switch on. ## Why client tooling adds it for you Two layers want it. A **typed client generator** uses it to emit a discriminated union in the target language, so the compiler can force the consumer to handle every branch. A **normalized client cache** uses it as part of the cache key: such caches flatten a response into per-object entries and need a stable identity for each one, and an identifier alone is not stable across types. That second use has a sharp failure mode. A 4-person platform team on a warehouse inventory graph ran a normalized cache keyed on the `id` field alone. `Bin` 4127 and `Pallet` 4127 were different objects with the same numeric identifier from two different legacy systems, so both wrote to the same cache entry — and one operator's picking screen rendered a row that belonged to another operator's open request, because whichever query resolved last had overwritten the entry. Nothing was wrong on the server, and the responses were correct in isolation. Keying on the type name together with the identifier is the standard fix, and it is exactly why cache-aware clients inject `__typename` into every selection set on a composite type before sending the document. ## Things it is not * It is **not** an identifier. Two objects of the same type share a `__typename`; it says what something is, not which one. * It is **not** the abstract type's name, ever — a frequent wrong answer, and easy to check. * It is **not** part of introspection's field lists, so code that enumerates a type's fields will not find it and must special-case it. * It carries no guarantee of stability across schema versions in the way a documented identifier does: renaming an object type changes the string every branching client compares against. ## What an interviewer is checking That you can say why a flat response needs a discriminator at all; that you know `__typename` yields the concrete type name and is implicit rather than declared; and, at the stronger end, that you can connect it to how client caches and generated types consume an abstract field. Candidates who have only ever read `__typename` in a debugging session tend to describe it as "a debug field", which misses the whole reason clients ship it in production documents.

  • If a field is declared as an interface, what does __typename return for it?
    The name of the concrete object type the server resolved, not the interface name. Abstract types exist in the schema, but every value in a response is an instance of an object type, and the meta-field reports that. Expecting the interface name back is one of the most common wrong answers on this topic.
  • Is it worth selecting __typename inside each inline fragment as well as beside them?
    No. Matched branches merge into the same object and identical field selections merge with them, so the extra copies resolve to the same value and add nothing but noise. The one placement that matters is having it outside the branches, so an object matching no branch still carries a discriminator.
  • Why can code that enumerates a type's fields from introspection miss __typename?
    Because it is implicit: it is not part of any type's declared field list, so it does not appear when you walk a type's fields. Tooling that generates selections or validates them against a field list has to special-case it, which is a routine source of bugs in home-grown query builders.

saying these in an interview costs you the question

  • Saying __typename returns the interface or union name
  • Treating __typename as an object identifier
  • Calling it a debugging-only field with no production use
  • Expecting to find it in a type's declared fields
  • Selecting it only inside branches, never beside them

context