In GraphQL SDL, what does the `!` in a field type like `String!` mean, and what does a bare `String` mean?
answer
- Start from what an unmarked type allows
- One character flips a promise
- It wraps a type, it does not annotate
- Output binds the server, input binds the client
- Shape only, never freshness or emptiness
basics
~20 sEvery GraphQL type is nullable by default, so a bare String means a string or null. The ! wraps the type in Non-Null: the field is promised to carry a value and can never be null.
solid answer
~50 sGraphQL inverts the default most typed languages use: a type is nullable until you mark it. `title: String` declares a field whose value is a string **or** null; `title: String!` wraps `String` in the Non-Null type, which has no null among its values. The `!` is a wrapping type, not an annotation or a directive on the field — in an introspection result it appears as a type with kind `NON_NULL` whose `ofType` points at the type it wraps, which is exactly why modifiers nest and are read from the inside out. Where the modifier sits decides who it binds: on an output field it is a promise the server makes to every caller, while on an argument or input field it is a requirement the client must satisfy. It constrains shape only — never emptiness, freshness or correctness.
code
graphql · 13 linestype JobPosting {
id: ID!
title: String!
company: Company!
salaryMax: Int
closesAt: String
applicantCount: Int!
}
type Company {
id: ID!
name: String!
}go deeper
Be ready to say it in one line: everything is nullable unless it carries !, and ! means the value will never be null. Being able to read title: String and title: String! correctly off a schema is the whole bar here.
Explain the mechanics: ! is a wrapping type around a named type, not an annotation, which is why it shows up in introspection as NON_NULL with an ofType. Be able to say who the promise binds — the server on output, the client on input.
Show where the guarantee stops. Non-Null constrains shape only: it does not mean non-empty, fresh, accurate or backed by a database constraint. Be able to explain what happens when a resolver breaks the promise and why there is no escape hatch.
Own the consequence for consumers: a ! is a contract every generated client bakes into its types, so the set of Non-Null fields in a published schema is a platform-level commitment, not a per-field style choice made by whoever wrote the resolver.
## Nullable is the default, and `!` is the exception In GraphQL's type system every type is nullable until you say otherwise. Writing `title: String` in SDL declares a field whose value may be a string or may be `null`. Adding the exclamation mark — `title: String!` — wraps that type in the **Non-Null** type, and a Non-Null type has no null among its possible values. This is the opposite default from most statically typed languages candidates arrive from, and it is a deliberate choice by the specification: the permissive shape is the baseline, and strictness is opt-in one position at a time. The `!` is not a decoration, an annotation, or a validation attribute hung on the field. It is a **wrapping type** — one of the two the specification defines, the other being the list type written with square brackets. `String!` is not "the `String` type in strict mode"; it is a different type that wraps `String`. You can see this structurally in an introspection result, where a Non-Null appears as a type with `kind: NON_NULL` and `name: null`, whose `ofType` points at the named type inside it. That nesting is why type modifiers are read from the inside out, and why `[String!]!` is three types deep rather than one string type with two flags. ## Two sides of one modifier Where the modifier sits decides whose obligation it is. On an **output position** — a field of an object type or an interface — Non-Null is a promise the *server* makes to every caller: this key will appear in the response carrying a value that is not null. A typed client generator reads that promise and emits a non-optional property, which is the practical payoff: callers stop writing defensive null checks for that field. On an **input position** — a field argument, an input object field, or a variable definition — Non-Null is a requirement on the *client*: a value must be present, and the literal `null` is never acceptable there. That is checked before execution begins rather than inside a resolver. Same syntax, opposite direction of obligation. Interviewers probe this pair because candidates who have only ever consumed a schema tend to know the output half and have never thought about the input half. ## A job board, and what `!` does not promise Take a job-board graph whose `JobPosting` type has grown to 37 fields. A slice of it declares `id: ID!`, `title: String!`, `company: Company!`, `salaryMax: Int`, `closesAt: String` and `applicantCount: Int!`. Read the promises off it. Every posting has an `id`, a `title` and a `company`. `salaryMax` may be null, and that is meaningful information rather than an oversight: plenty of postings publish no maximum, and null is how the schema says "there is no value here". `closesAt` may be null because a posting can be open-ended. Now the limits, which are exactly where weak candidates over-claim: - **`!` is not "non-empty".** `title: String!` is fully satisfied by `""`. A field typed `[Skill!]!` is fully satisfied by `[]`. Non-emptiness is not expressible in the type system at all; it is a runtime concern. - **`!` is not "fresh" or "correct".** If `applicantCount: Int!` is served from a counter that lagged the last 90 seconds of applications, the response is stale — and completely valid against the schema. The type system constrains the shape of the value, never its truth. Saying this out loud is worth marks, because "it is `Int!`, so it must be right" is a real reasoning error in incident reviews. - **`!` is not a storage constraint.** Nothing forces the database behind the field to agree with it. The modifier describes the wire, and it is the server's job to make the wire true. ## When the promise cannot be kept If the code behind a Non-Null output field produces null, the server cannot serialize a null in that position — doing so would contradict the schema it published. Instead the field is treated as having errored, an entry appears in the response's `errors` list, and the null is pushed outward to the nearest position that permits one. The mechanics of that outward push are a subject of their own; the narrower point here is that **a Non-Null field has no "just send null" escape hatch.** Writing `!` chooses that consequence. ## Reading a type back to yourself A habit worth building for both interviews and code review: read a type aloud from the inside out, one modifier at a time. `String` — a string, or null. `String!` — a string, never null. `[String!]!` — a list, never null, whose elements are strings, never null. Each modifier you peel off describes exactly one position. Candidates who read the whole thing as one blob ("the strict string list") are the ones who get list variants wrong under pressure, because they never separated the promise about the container from the promise about what is inside it.
- Does declaring a field `String!` guarantee the client never receives an empty string?No. Non-Null removes only null from the set of allowed values; `""` is a perfectly good string and satisfies `String!`. The same trap applies to lists: `[Skill!]!` is satisfied by `[]`. GraphQL's type system cannot express "non-empty", "at least three characters" or any other value constraint — those are enforced by the server at execution time, or by a custom scalar's coercion, and they are invisible to a caller reading the SDL.
- A field declared `Int!` returned a count that was 90 seconds out of date. Did the schema lie?No. Non-Null is a promise about the shape of the response — the key is present and its value is not null — and says nothing about whether the value is current, accurate or read from an authoritative source. A field backed by a stale cache still satisfies `Int!`. If freshness matters to callers, it has to be communicated some other way, because no modifier in the type system can carry it.
- How does a Non-Null type appear when a client introspects the schema?As a type with `kind: NON_NULL` and a `name` of null, whose `ofType` field points at the type it wraps. Wrapping types have no names of their own; only named types — objects, interfaces, unions, enums, input objects and scalars — do. A tool reconstructs the printed form by walking `ofType` down to the named type and re-applying each wrapper on the way back out.
A Non-Null marker is a delivery guarantee that the box will arrive with something in it — it says nothing about whether the contents are fresh, correct, or more than a scrap of packing paper.
saying these in an interview costs you the question
- Says types are non-null by default unless marked optional
- Calls `!` a directive or an annotation on the field
- Claims `String!` also forbids an empty string
- Treats `Int!` as a promise the value is fresh or accurate
- Thinks `!` on an output field constrains what clients send
- Assumes `!` mirrors a NOT NULL column in the database