Is totalCount part of the Relay cursor connection specification?
answer
- Not everything on a connection is specified
- The convention fixes four names only
- Extra fields are allowed, not defined
- Meaning lives in the field description
- Exact, capped or estimated — unspecified
basics
~20 sNo. The Relay cursor connection specification fixes edges, node, cursor and pageInfo; it allows extra fields but defines no totalCount. totalCount is a near-universal server convention whose meaning — exact, capped or estimated — each schema decides for itself.
solid answer
~40 sThe Relay cursor connection specification is small: a connection object type carries `edges` and `pageInfo`, each edge carries `node` and `cursor`, and `PageInfo` carries the has-more booleans and the start and end cursors. Nothing in it mentions a count. The specification does leave room for a connection type to carry additional fields, and `totalCount` is the field almost every server puts there — which is why candidates assume it is standard. Because it is convention rather than specification, nothing fixes its semantics: it may mean the number of rows matching the filter arguments ignoring `first`/`after`, or a count capped at some ceiling, or a statistical estimate, and only the field's description says which. Treat it as an ordinary schema-authored field: name it, type it, document its exactness, and decide whether it is nullable.
code
graphql · 15 linestype TranslationUnitConnection {
# required by the cursor connection convention
edges: [TranslationUnitEdge!]!
pageInfo: PageInfo!
# additional field: convention only, semantics chosen here
"""Units matching the filter arguments, ignoring first/after.
Exact at read time. Null if the count exceeds its time budget."""
totalCount: Int
}
type TranslationUnitEdge {
node: TranslationUnit!
cursor: String!
}go deeper
Remember the four names the connection convention actually fixes — edges, node, cursor, pageInfo — and that totalCount is not among them. Be able to say it is a widespread convention added as an extra field.
Explain what the convention permits versus what it defines, and list the decisions it leaves to you: what the number counts, whether it is exact, and whether the field is nullable.
Show that unspecified semantics are an operational problem: an unauthorized count leaks set sizes, a separate read skews against the page, and no tooling will ever warn a caller which flavour they got.
Own the fact that a convention with no definition becomes whatever the first team ships. Be ready to say how you would fix meaning in review — naming rules, mandatory descriptions, lint — rather than hoping for consistency.
## What the connection convention actually fixes The Relay cursor connection specification is often described as "how GraphQL does pagination", but it is worth noticing how little it actually mandates. A field that returns a connection must return an object type that has an `edges` field and a `pageInfo` field. Each edge has a `node` (the object you wanted) and a `cursor` (an opaque string marking that object's position). `PageInfo` carries `hasNextPage` and `hasPreviousPage` as non-null booleans plus a start and an end cursor. Arguments `first`/`after` and `last`/`before` slice the list. That is essentially the whole contract. Two things follow. First, it is a **convention document**, not part of the GraphQL specification itself — the language specification knows nothing about connections, edges or cursors, and a perfectly valid GraphQL server may paginate with `offset`/`limit` and never mention any of this. Second, the connection convention deliberately leaves room for a connection type to carry **additional fields**. This is where every extra you have ever seen lives: aggregate summaries, facets, and above all `totalCount`. ## Why everyone thinks totalCount is standard Because it is on nearly every connection type in the wild. Product teams want a "1,438 results" headline and numbered pages, and the natural place to hang the number is on the connection object next to the page it describes. Over time the field name `totalCount` became so consistent that people cite it as part of the spec. It is not, and the practical consequence is that **its meaning is unspecified**. Two schemas can both expose `totalCount: Int!` on a connection and mean different things. Consider a translation-memory graph, where a project holds translation units — a source segment paired with its translations. A project of 4,318,772 units is queried for the German fuzzy matches: ```graphql query { project(id: "prj_7742") { translationUnits( first: 25 filter: { targetLocale: "de-DE", status: FUZZY } ) { totalCount edges { cursor node { id sourceText } } pageInfo { hasNextPage endCursor } } } } ``` What should `totalCount` return? The overwhelmingly common reading is **218,431** — the number of units matching the filter, ignoring the page slice. But nothing enforces that reading. A server could plausibly return the number of edges in this page (25), the project's whole unit count (4,318,772), or a value capped at 10,000. Each of those has shipped somewhere. Because the convention is silent, the only place the answer lives is the field's description in the schema, which is exactly why you should write one. ## The questions the convention does not answer for you **Counted before or after authorization filtering?** If a viewer may see only the units of the locales they are assigned, a count computed from the raw predicate leaks the size of data the caller cannot read. The count must go through the same filtering as the page, or it becomes an oracle. **Exact as of when?** The page and the count are usually two separate reads. Unless they run in one snapshot, a client can receive `totalCount: 218431` alongside a page whose contents imply a different total, because a batch import committed between the two reads. Small skew is almost always acceptable; the point is that "exact" means exact at a moment, not exact forever. **Nullable or not?** `totalCount: Int!` is a promise that the number is always available. `totalCount: Int` lets the server decline — returning null when computing it would blow a budget — without failing the whole field. Once shipped, moving from non-null to nullable is a breaking change for clients, so the decision is worth making deliberately on day one. **Exact, capped or estimated?** If the number is an estimate, the honest move is to name the field so callers cannot mistake it, rather than reusing the conventional name for something the convention's users do not expect. ## How to answer this in an interview Say plainly: the connection convention specifies `edges`, `pageInfo`, `node` and `cursor`; `totalCount` is an additional field that convention has made near-universal but that no specification defines. Then show you know what that costs: unspecified semantics, an unspecified price, and no composition or tooling check anywhere that will tell a caller which flavour of count they received. Candidates who assert "the spec requires totalCount" are revealing that they learned connections from one server's generated schema rather than from the convention.
- If totalCount is not specified, what stops two teams from giving the same field name two different meanings?Nothing mechanical. Composition and validation check the field's name, type and nullability, never its semantics, so one team's exact count and another's capped estimate compose without complaint. The only controls are social: a written naming rule (an estimate gets a different field name), a mandatory description stating exactness, and a schema lint in CI that rejects a count field with no description. A client cannot introspect the difference.
- Should totalCount be counted before or after per-viewer authorization filtering?After — the count must apply exactly the filtering that produced the page. A count taken from the raw predicate tells the caller how many rows exist that they are not allowed to see, which turns a convenience field into an information-disclosure oracle. It also confuses clients, who see a total far larger than the pages they can actually walk through.
- Why declare totalCount as nullable rather than non-null?Nullability is the server's escape hatch. A nullable count lets the resolver return null when the count would exceed its budget, or when a downstream aggregate is unavailable, while the page itself still returns normally. With `Int!` the only ways out are failing the field — which under non-null propagation takes the connection with it — or returning a fabricated number. Loosening `Int!` to `Int` later breaks existing clients, so choose early.
It is like the tare weight printed on a shipping crate: everyone prints one and everyone reads it, but no standard says it must be there or exactly what it includes.
saying these in an interview costs you the question
- Claiming the connection specification requires totalCount
- Assuming totalCount means the number of edges returned
- Thinking totalCount has one fixed meaning everywhere
- Counting rows the viewer is not allowed to see
- Declaring totalCount non-null without considering cost
- Confusing the connection convention with the GraphQL specification