Why does GraphQL split types into input and output kinds, and which kinds sit on both sides?
answer
- Values go in, selections come out
- Leaf types have no direction
- Two positions, two families
- Nothing to select from an argument
- No __typename on an incoming value
basics
~20 sA caller supplies complete values while a server returns things a client selects fields from, so the two directions need different type kinds. Scalars and enums work in both directions; object, interface and union types are output-only, and input object types are input-only.
solid answer
~50 sGraphQL has two families of type. **Output types** - scalars, enums, object types, interfaces and unions - describe what a field returns; a client narrows them with a selection set and the server produces them field by field through resolvers. **Input types** - scalars, enums and input object types - describe values a caller supplies: field arguments, directive arguments, the fields of an input object and the declared type of a variable. Scalars and enums are the overlap, because a leaf value means the same thing in both directions. An object type cannot cross over: its fields may take arguments and are produced by resolvers, and abstract types need a runtime `__typename` the server decides, none of which has meaning for a value arriving from a client. So a parcel-locker graph that returns a `Compartment` declares a separate `ReserveCompartmentInput` for the argument side. Using an output type where an input type is required is a schema error caught when the server builds the schema, not at request time.
code
graphql · 18 linesenum CompartmentSize { XS S M L XL }
type Compartment {
id: ID!
size: CompartmentSize!
doorOpen: Boolean!
}
input ReserveCompartmentInput {
lockerId: ID!
size: CompartmentSize!
holdUntilIso: String
}
type Mutation {
# reserve(compartment: Compartment!): Compartment! <- invalid: Compartment is an output type
reserve(input: ReserveCompartmentInput!): Compartment!
}go deeper
Recall the shape of the rule: what a field returns and what a caller supplies are described by different type kinds, and an argument is declared with an input object type rather than the object type the graph returns.
Be ready to list both families, name scalars and enums as the overlap, and give the reason - selection sets, field arguments and runtime type resolution are all server-side ideas with no meaning for a value arriving from a client.
Explain that the check is type-system validation at build time, and that the split forces a parallel input family whose divergence from the output side has to be a deliberate design decision rather than an accident.
Own the consequence at graph scale: the input surface is a second contract with its own review, ownership and lifecycle, and treating it as a mirror of the entity model is how large schemas acquire unusable arguments.
## Two families, and one overlap Every named type in a GraphQL schema belongs to one of these groups: - **Output types**: scalars, enums, object types, interfaces, unions - plus any list or non-null wrapping of those. - **Input types**: scalars, enums, input object types - plus any list or non-null wrapping of those. The overlap is exactly **scalars and enums**. Output-only: object types, interfaces, unions. Input-only: input object types. That asymmetry is the whole subject, and interviewers use it because a candidate who has only ever consumed a schema often has not noticed there are two kinds of "object" in it at all. ## Where an input type is required Four positions in a schema demand an input type, and it is worth being able to list them: 1. the type of a **field argument**, on any field, at any depth; 2. the type of a **directive argument**; 3. the type of a **field of an input object type** - recursively, all the way down; 4. the declared type of a **variable** in an executable document. Everything else - the type of a field on an object type or interface, a union member - is an output position. ## Why an object type cannot cross over The reason is not arbitrary. Three properties of an output object type are meaningless on the way in: **Selection.** A client never receives a whole object type; it receives the fields it selected. An incoming value is the opposite: it is complete as sent, with nothing to select from. There is no notion of "the caller supplied part of this object and would like the rest resolved". **Resolution.** A field on an object type can itself take arguments and is produced by running server code. `Compartment.currentParcel(includeReturned: Boolean)` describes work the server does. A value arriving from a client is data, not work to be run, so field arguments have no meaning there. **Discrimination.** An interface or a union is resolved to a concrete member at runtime, by the server, and the answer is reported to the client as `__typename`. An incoming JSON value carries no such marker, and the specification defines no input-side discriminator, so there is nothing that could decide which member of a union a supplied value is. That is why there is no such thing as an input interface or an input union. ## What an input object type is An input object type is therefore a deliberately plainer construct: a named set of fields, each typed as an input type, with no arguments, no selection sets, no interfaces implemented, no union membership and no resolvers. It is a value tree, and it can nest, be listed, and carry default values and descriptions. ```graphql input ReserveCompartmentInput { lockerId: ID! size: CompartmentSize! holdUntilIso: String notify: NotifyPreferenceInput } ``` `CompartmentSize` here is an enum - legal on this side precisely because it is a leaf value. `NotifyPreferenceInput` is another input object, nested. What you may not write is `compartment: Compartment!`, where `Compartment` is the object type the graph returns. ## The consequence: parallel type families Because a single name defines a single type, you cannot have `Compartment` be both. Every schema of any size therefore grows an input-side family alongside its output side, conventionally suffixed `Input`. That suffix is a convention that tooling and reviewers rely on; nothing in the type system requires it, and nothing enforces that `CompartmentInput` resembles `Compartment` in any way. A useful reframing for an interview: an input object is not "the writable version of an entity". It is the argument shape of one operation that happens to be about that entity, and its fields are chosen for the operation, not copied from the output type. ## When the rule bites The check happens at **schema build time**. A schema that declares an argument typed as an output type is invalid, and a conforming server refuses to build it rather than failing on the first request. So this is a class of mistake you meet while writing SDL or wiring a code-first schema, and it never reaches a client. In a code-first setup the same error usually surfaces as a startup failure complaining that a type is being used in an input position, which is worth recognizing for what it is: the same rule, reported by a different messenger. ## The compact answer Values in, selections out. Leaf types are shared because a leaf value has no direction; anything with fields the client selects, or resolvers, or a runtime type, exists only on the way out.
- Can an interface or a union be used as an input type?No. Both are resolved to a concrete member at runtime by the server and reported through `__typename`; an incoming value carries no such marker and GraphQL defines no input-side discriminator. Alternatives are modelled as a single input object with optional fields, with the exactly-one rule enforced by the server or by a schema marking.
- Can one named type serve as both an object type and an input object type?No - a name defines exactly one type in a schema, and the two kinds are distinct. That is why schemas grow a parallel input family, conventionally suffixed `Input`. The suffix is a convention that tooling and readers rely on, not a rule the type system enforces.
- Besides field arguments, where else must an input type be used?As the type of a directive's arguments, as the type of every field of an input object type - recursively - and as the declared type of a variable in an executable document. All four positions carry values supplied by the caller rather than produced by the server.
- When is using an output type in an input position detected?At schema build time. Type-system validation rejects the schema, so a conforming server refuses to start rather than failing per request. In a code-first setup it usually appears as a startup error saying a type was used in an input position, which is the same rule reported differently.
saying these in an interview costs you the question
- Says an object type is fine as an argument if no fields are selected
- Thinks input object types can implement interfaces
- Believes a union can be used as an argument type
- Assumes the Input name suffix is required by the specification
- Claims scalars are output-only and need input twins
- Thinks the mistake is caught per request rather than at build time