What are GraphQL's five built-in scalar types, and how is ID different?
answer
- The response tree has to end somewhere
- Five names, one of them odd
- One scalar is asymmetric in and out
- Thirty-two bits, not sixty-four
- String or integer in, string always out
basics
~20 sInt, Float, String, Boolean and ID. ID marks a unique identifier: it accepts either a string or an integer as input, but is always serialized back as a string, and clients should treat it as opaque.
solid answer
~50 sThe specification defines five built-in scalars. `Int` is a signed 32-bit non-fractional value; `Float` is a signed double-precision finite value; `String` is a UTF-8 character sequence; `Boolean` is true or false. `ID` is the odd one. It represents a unique identifier — the value a caller passes back to refetch an object or uses as a cache key — and it is asymmetric on purpose: as an **input** type it accepts either a string or an integer literal, so `vehicle(id: 4712)` and `vehicle(id: "4712")` are both legal; as an **output** type it is always serialized as a string, so a resolver returning the number 4712 puts `"4712"` in the response. The specification also says an ID is not intended to be human-readable, which is a hint to clients not to parse it. Note what is absent: there is no built-in date, decimal, UUID or JSON scalar.
code
graphql · 11 linestype Vehicle {
id: ID!
registrationPlate: String!
axleCount: Int!
lastFuelLevelPercent: Float
isInService: Boolean!
}
type Query {
vehicle(id: ID!): Vehicle
}go deeper
Be able to list the five names without hesitating, and say the one sentence about ID: string or integer in, always string out. Knowing there is no built-in date scalar is the other half of the answer.
Explain each built-in as a pair of coercions rather than as a JSON type name, and be ready for the 32-bit range of Int and the finiteness of Float — those two constraints are where real schemas break.
Show that you treat ID as a contract with clients: opaque, non-parseable, serialized as a string. Expect to discuss what typing a numeric key as ID does to consumers already reading it as a number.
Own the position that the missing built-ins are a schema-wide policy decision: every custom scalar you allow is a mapping every client generator in the organisation must be configured for, so which ones exist is worth deciding once, centrally.
## Scalars are the leaves of the response tree A GraphQL response is a tree, and every branch of that tree has to stop somewhere. It stops at a **leaf type** — a scalar or an enum. These are the only types that carry no selection set: you can write `vehicle { id }`, but you cannot write `id { something }`, because `ID` is a scalar and there is nothing beneath it. A scalar is more than a label pinned on a JSON value. In the type system a scalar is *defined by two coercions*: * **Result coercion** — turning whatever the server holds internally into the value that goes into the response. * **Input coercion** — turning a value a caller supplied, either as a literal written in the document or as a value in the request's variables map, into whatever the server wants to hand a resolver. The five built-ins are simply the five scalars whose coercions the specification writes down for you, so that every server and every client agree on them without negotiating. ## Int `Int` is a **signed 32-bit non-fractional value** — roughly -2.1 billion to +2.1 billion. This is a specified constraint, not an implementation detail, and it is the single most frequently discovered surprise in the list. A fleet telematics graph that types `Vehicle.odometerMetres` as `Int` is fine for years and then is not, because a vehicle's cumulative metres eventually crosses the boundary. `Int` is not "an integer"; it is a 32-bit integer. ## Float `Float` is a **signed double-precision finite value** as described by IEEE 754. "Finite" is doing real work in that sentence: `NaN` and `Infinity` are not representable, so a resolver that computes an average over an empty set of readings and returns `NaN` has produced something the scalar cannot serialize. Input coercion is deliberately forgiving in one direction: an integer literal is a valid `Float` input, so `speedLimitKph: 60` is accepted for a `Float` argument. The reverse is not true — `4.5` is not a valid `Int` input. ## String `String` is textual data represented as a UTF-8 character sequence. It is the default for free text, and also the type every schema reaches for when it has not yet decided whether the value is really an enum, a date or an identifier. ## Boolean `Boolean` is `true` or `false`. Worth saying out loud in an interview: **nullability is a separate axis**. A field typed `Boolean` has three possible response values — `true`, `false` and `null`. If you meant two, you wrote the type wrong; you wanted `Boolean!`. ## ID — the one interviewers actually ask about `ID` represents a unique identifier, typically the value used to refetch an object or as a cache key. Its behaviour is asymmetric, and that asymmetry is the whole question: * **Coming in**, an `ID` argument accepts a string *or* an integer. This exists because backends have numeric primary keys and clients tend to hold strings, and the specification refuses to make you pick. `vehicle(id: 4712)` and `vehicle(id: "4712")` are both valid. * **Going out**, an `ID` is serialized the same way a `String` is. A resolver that returns the integer `4712` for an `ID` field produces `"4712"` in the JSON, not `4712`. A client written against a numeric field will break the day someone types it as `ID`. The specification also states that an `ID` is *not intended to be human-readable*. That is a message to the client: treat the value as opaque, do not parse it, do not sort on it, do not assume it is numeric. It says nothing about uniqueness enforcement — the server guarantees nothing; `ID` is documentation of intent plus a serialization rule, not a constraint. ## What is deliberately missing There is no built-in date, time, decimal, URL, UUID or JSON scalar. Every schema you have seen with a `DateTime` field defined one itself; those names are **widespread convention, not specification**. The absence is intentional: the specification declines to pick a serialization for values whose format is genuinely contested, and leaves you to declare a custom scalar and document it. ## A worked shape A telematics `Vehicle` uses four of the five in one type: an `ID` for the vehicle, a `String` for its plate, an `Int` for a small count, a `Float` for a measured value, and a `Boolean!` for a flag that must never be null. Everything else — a timestamp, a coordinate, a large odometer count — is where a schema starts needing types the specification does not hand you.
- A resolver for an ID field returns the integer 4712. What does the client receive?The string `"4712"`. Result coercion for `ID` serializes the same way `String` does, so the number becomes a JSON string. This is why a client that was reading a numeric field and starts reading an `ID` field breaks on a strict type check even though nothing about the underlying value changed.
- Why is there no built-in DateTime scalar?Because there is no uncontested serialization for a date-time, and the specification declines to impose one. Schemas that expose timestamps declare a custom scalar and document its format — the name `DateTime` is a widespread convention, not something the specification defines. The cost is that every client has to be told what that scalar means.
- Can a client write a selection set beneath a field typed Float?No. Scalars are leaf types: they have no fields, so a selection set on one is a validation error and the document is rejected before execution. Conversely, a field whose type is an object, interface or union *must* carry a selection set — both rules exist so every branch of the response tree terminates in a scalar or enum.
ID is like a coat-check ticket: the desk will take it written on paper or punched as a number, always hands it back as printed text, and expects you not to read anything into the digits.
saying these in an interview costs you the question
- Says Int is a 64-bit integer
- Thinks ID guarantees uniqueness in the server
- Expects an ID field to serialize as a number
- Names DateTime or JSON as a built-in scalar
- Thinks a Boolean field has only two possible values
- Believes Float can carry NaN or Infinity