Is an array of operations in one GraphQL request body part of the specification?
answer
- Two specifications, neither mentions arrays
- Convention that predates the HTTP draft
- Pairing is positional, not by name
- One status code covers many outcomes
- Per-request controls divide by member count
basics
~20 sNo. The GraphQL specification describes one request carrying one document and never mentions HTTP, and the GraphQL over HTTP working draft describes a single request per HTTP request. Array batching is a widespread convention, not a specified feature.
solid answer
~50 sThe core specification defines a request as a document plus variables plus an optional operation name; the GraphQL over HTTP specification, still a working draft, defines how one such request is carried as a JSON object or as URL parameters. Neither defines an array. The convention that grew up anyway is: post a JSON array of ordinary request objects, receive a JSON array of ordinary results paired **by position**, each with its own `data` and `errors`. Because it is a convention, nothing pins down ordering, concurrency, atomicity, or how a member the server refuses is reported — and support varies from on-by-default to absent. The security consequence is mechanical: any control whose unit is the HTTP request — a rate limit, a cost budget, an attempt counter, a log line — is divided by the member count unless it is applied per member.
code
json · 12 lines[
{
"operationName": "SegmentPage",
"query": "query SegmentPage($p: Int!) { segments(page: $p, size: 8400) { segmentId source } }",
"variables": { "p": 3 }
},
{
"operationName": "CommitTranslation",
"query": "mutation CommitTranslation($id: ID!, $text: String!) { commitTranslation(segmentId: $id, target: $text) { revision } }",
"variables": { "id": "s-31447", "text": "Avviso di liquidazione trimestrale" }
}
]go deeper
Recall the shape: an array of ordinary request objects in, an array of ordinary results out, matched by position, with one HTTP status for the whole body. Know that it is optional server behaviour you cannot assume.
Explain that neither the core specification nor the GraphQL over HTTP draft defines it, and list what is therefore undefined — ordering, concurrency, atomicity, and how a refused member is reported. Be precise about spec versus convention rather than hedging.
Show that accepting arrays multiplies every per-request budget, and describe the retry hazard where a timed-out batch re-runs mutations that already committed. Say whether you would enable it at all and what member cap you would set.
Frame it as a contract decision: batching trades round trips for a weaker unit of accounting and auditing. Decide whether the latency win is real for your clients, and if it is, mandate per-member limits and idempotency keys before enabling it.
## What the specifications actually describe The GraphQL specification defines a *request* as a document, an optional operation name, and a set of variable values, and it says nothing whatever about HTTP. The rules for expressing that request as an HTTP request live in the separate GraphQL over HTTP specification, which is a working draft; it describes a single GraphQL request per HTTP request, carried as a JSON object with `query`, `variables`, `operationName` and `extensions`, or as URL parameters for a GET. Neither document defines an array. So array batching is a **convention**. It arose because early client libraries wanted to collapse the several documents a page fires on load into one round trip, servers added support, and the shape became folklore. It is well established and widely implemented — and it is not a feature you can assume of an arbitrary GraphQL endpoint, nor one whose behaviour is pinned down anywhere you can cite. ## The conventional shape A batched body is a JSON array whose members are ordinary request objects. The response is a JSON array of ordinary results, paired with the requests **by position**. Each member gets its own `data` and its own `errors`; there is one HTTP status code, one set of response headers, and one connection for the whole array. Because none of this is specified, every property you might want to lean on is a per-server decision: - **Order and concurrency.** Nothing says the members run in order, or that they run one at a time. A server may execute them concurrently. - **Isolation.** There is no transaction around the array. Two mutations in one batch are two independent operations that happen to share a socket. - **Partial failure.** A member that fails produces a result object with an `errors` array like any other operation; the HTTP status still describes the *transport*, not the outcomes. But a body that fails to parse, or a member the server refuses, may collapse the whole array into a single request error — which shape you get is implementation-defined. - **Size.** Some servers cap the member count; many disable batching by default; some never supported it. ## Why the convention question is a security question Every control that counts *HTTP requests* divides by the batch factor. A per-IP limit of 30 requests per minute becomes 30 × N operations per minute the moment N-member arrays are accepted. A per-request cost budget applied once to the body lets each member spend the whole budget. A log pipeline that emits one line per request now hides N operations behind it. And an authorization check performed once at the edge covers operations it never inspected. The mirror-image trap is *retries*. A client posts a five-member batch against a translation-memory graph: three reads and two `commitTranslation` mutations writing new target text for segments in an 8,400-row review page. The server executes members three and four, then the request exceeds the client's socket timeout and the client — reasonably, by its own lights — retries the whole array. The two commits run a second time. If `commitTranslation` appends a revision rather than setting one, the retry leaves **a duplicate write** that no HTTP status ever reported, because the first attempt's status never arrived. Nothing about batching creates this hazard by itself; batching enlarges it, by making a single retriable unit out of operations that have different idempotency properties. ## Deciding what to do about it Three defensible positions, in rough order of how often you will see them: 1. **Reject arrays.** If your clients do not need it, an endpoint that accepts only a JSON object is one fewer multiplier to reason about, and rejecting it is a content-shape check that costs nothing. 2. **Accept with a member cap, and apply limits per member.** Parse the array, then run every downstream control — cost scoring, field-occurrence caps, attempt counters, logging — once per member rather than once per body. The cap bounds the parse-time work; the per-member accounting bounds everything after it. 3. **Accept for reads only.** Mutations in a batch are the part that carries the retry and ordering hazards; reads are merely expensive. ## What an interviewer is listening for The crisp answer is: "No, it is not specified — the core spec has no HTTP, and the HTTP spec describes one request per request. Array batching is a convention, an array in and an array of results out matched by position, with no atomicity and no defined ordering." The follow-through that separates a good answer from a recited one is noticing that every per-request control silently becomes a per-N-operations control, and that a retried batch re-runs whatever already succeeded.
- A batched POST times out after two of its five members have already committed, and the client retries the whole array. What happens?The two committed operations run again, because there is no transaction around the array and no member-level acknowledgement the client could have received. If those mutations are not idempotent, the retry leaves a duplicate write that no HTTP status ever reported. Batching does not create the hazard, but it makes one retriable unit out of operations with different idempotency properties.
- How does one failing member of a batch surface to the caller?Conventionally as an ordinary result object in its slot, with its own `errors` array and whatever `data` completed — the HTTP status still describes the transport, not the outcomes. But because none of this is specified, a body that fails to parse or a member the server refuses may instead collapse the whole array into a single request error.
- If you accept batched arrays, what must change about your limits?Every downstream control moves from per-body to per-member: cost scoring, field-occurrence caps, credential-attempt counters, authorization context, and log lines. Add a hard cap on member count so the parse itself is bounded, and consider refusing arrays outright on endpoints that expose authentication or other non-idempotent mutations.
saying these in an interview costs you the question
- Array batching is defined by the GraphQL specification
- A batch executes atomically, all or nothing
- HTTP 200 on a batch means every member succeeded
- Batch members are guaranteed to execute in order
- Results are matched to requests by operation name
- Batching is safe because it is just one request