skip to content

questions

4

What is GraphQL transport-level batching, where one HTTP body holds an array of operations?

level: juniorimportance: should knowfreq 42%

answer

  1. A convenience at the transport layer
  2. One body, several operations
  3. The reply comes back as an array too
  4. Matched by position, never by id
  5. The serving draft defines a map body

basics

~20 s

Transport-level batching puts several GraphQL operations in one HTTP body as a JSON array; the server replies with an array of results in the same order. It is a convention - the GraphQL over HTTP specification defines the body as one map.

solid answer

~50 s

Transport-level batching is a client-and-server convention in which the HTTP request body is a JSON **array** of the usual operation maps - each with its own `query`, and optionally `variables` and `operationName` - instead of a single map. The server executes each element and replies with a JSON array of result envelopes in **the same order**, so a result is matched to its operation by position, not by any id. One HTTP response means one status line and one set of response headers covering all of them. The motive is round-trip cost: a client library collects the operations several components issued within a few milliseconds and sends them once. None of this appears in the GraphQL specification or in the GraphQL over HTTP working draft, which defines the request body as a map, so a server has to opt in and a client that batches against one that has not will get a request error.

code

json · 10 lines
json
[
  {
    "query": "query CampaignBanner($slug: String!) { campaign(slug: $slug) { title goalPence } }",
    "variables": { "slug": "winter-appeal" }
  },
  {
    "query": "query DonorProfile($id: ID!) { donor(id: $id) { displayName totalGivenPence } }",
    "variables": { "id": "dnr_8814" }
  }
]

go deeper

for a junior

Be ready to describe the shape in one sentence: an array of operation maps in, an array of results out, in the same order. Know that this is a convention and not something the GraphQL specification defines.

for a middle

Explain the mechanics that follow from the shape: results are matched by index because nothing else identifies them, each member is still validated and executed independently, and one HTTP response means one status for all of them.

for a senior

Distinguish the three things called batching without being prompted, and be able to say what a server has to decide once it accepts an array body - concurrency, a size cap, context sharing and the order guarantee clients silently depend on.

for a principal

Own whether the convention earns its place at all. On a multiplexed transport the saving is header and framing overhead, paid for with coupled latency and lost per-operation attribution, so argue the tradeoff rather than inheriting the client default.

## The shape on the wire An ordinary GraphQL request over HTTP is a POST whose body is a JSON **map**: `query` carries the executable document, `variables` a map of values, `operationName` the name of the operation to run when the document defines more than one, and `extensions` anything else the server has agreed to read. Transport-level batching changes exactly one thing about that picture: the body is a JSON **array** whose elements are those same maps. ```json [ { "query": "query CampaignBanner($slug: String!) { campaign(slug: $slug) { title goalPence } }", "variables": { "slug": "winter-appeal" } }, { "query": "query DonorProfile($id: ID!) { donor(id: $id) { displayName totalGivenPence } }", "variables": { "id": "dnr_8814" } } ] ``` The server executes each element and answers with a JSON array of the same length in the same order. Everything inside a member is ordinary: it is parsed, validated and executed as an independent operation against the same schema, and it produces its own complete result envelope. The batch is a wrapper around several requests, not a way of fusing them into one bigger operation. ## Matched by position, because nothing else can match them The convention carries no correlation identifier. There is no `id` key on a member and no echo of the operation name in the result, so the only thing tying a result to the operation that produced it is its index in the array. A batching client library keeps the pending callers in a list and resolves the caller at index *k* with element *k* of the response. That makes ordering a hard contract rather than a nicety: a server that reordered members, dropped one, or collapsed two identical members into a single result would silently hand a donor profile to the code that asked for a campaign banner. ## What the specifications actually say about it: nothing This is the part interviewers are usually probing. The GraphQL specification describes the type system, documents and the execution algorithm, and deliberately says nothing about HTTP. The GraphQL over HTTP specification - still a working draft rather than a released edition - is the document that defines the request, and it defines the body as a **map** with those keys, sent by POST, or as URL-encoded parameters on a GET for a query. There is no array form in it and no member-level status semantics, because the array body simply is not in scope. So transport batching is a widespread convention with no normative text behind it. There is no capability negotiation either: no header advertises it and no introspection field reports it. A client either knows from documentation that the server accepts an array body, or discovers that it does not when the server fails to find `query` in what it took to be a map and answers with a request error. ## Why clients started doing it A component-driven page tends to issue several small operations within a few milliseconds of each other as its tree mounts. Each one costs request and response headers, a serialization pass and, on a transport that cannot carry requests in parallel, a place in a queue. Collecting whatever was issued inside a short window - single-digit milliseconds is typical - and sending it as one body removes most of that per-request overhead. The argument was much stronger when connections could not multiplex. Where the transport can carry many requests in parallel, the round-trip saving shrinks to the header and framing overhead, and the costs described elsewhere in this leaf - one status for many operations, one latency for many operations, one cache key for many operations - stop being obviously worth paying. ## Three different things get called "batching" Being precise here is most of the interview value: - **Transport-level batching**: several operations in one HTTP body, as an array. Several results come back. This is the subject here. - **Several operations in one document**: one `query` string that defines more than one named operation, with `operationName` choosing which to run. The server executes exactly one of them and returns one result. - **Batching backend calls inside one operation**: the per-request load-batching pattern that collapses many repeated backend calls made during a single execution. One operation, one result, fewer database round trips. An interviewer who asks "how do you batch in GraphQL?" without qualifying it usually means the third. Saying which one you are answering, and why they are unrelated, is the answer that lands. ## What the server has to decide Accepting an array body forces choices the specification does not make for you: whether members execute concurrently or in sequence; what the maximum member count is and what happens beyond it; whether members share one request-scoped context or each gets a fresh one; and whether the response array is guaranteed to preserve request order, which clients will assume regardless. Authentication is evaluated once for the HTTP request, but authorization is still per member, because each member is its own operation selecting its own fields.

  • How does a client match a result in the response array back to the operation that asked for it?
    By position and nothing else. The response array is in request order and carries no correlation ids, so a batching client keeps its pending callers in an array and resolves caller `k` with element `k`. That makes order-preservation a contract a client must verify against any new server, since a reordered or missing element misassigns results silently rather than failing.
  • Does batching change how each operation is executed on the server?
    No. Every member is parsed, validated and executed as an independent operation with its own document and variables, and produces its own result envelope. Servers commonly run members concurrently, but nothing requires it, and whether members share a request-scoped context or each gets a fresh one is a server choice with no specified answer.
  • What happens when a client batches against a server that does not support it?
    The server reads the body expecting a map, fails to find `query`, and answers with a request error - commonly a 400 carrying an errors array and no data. There is no negotiation for this: no header advertises array support and no introspection field reports it, because the array body is not part of any specification. Support is a documented property of the particular server.

It is an envelope holding several letters rather than a longer letter: the postal service handles one item, but each letter is still read, answered and judged on its own.

saying these in an interview costs you the question

  • Says the GraphQL specification defines batched array request bodies
  • Thinks results come back keyed by operation name
  • Confuses it with several operations inside one document
  • Confuses it with batching backend calls during one execution
  • Assumes every GraphQL server accepts an array body
  • Believes the members are merged into one executed operation

context

open as a page

In a batched GraphQL request, what does the single HTTP status code tell you about each operation?

level: middleimportance: should knowfreq 44%

basics

~20 s

Nothing about any individual operation. One HTTP status covers the whole request, and each element of the response array carries its own result envelope, so a client must inspect every element by position to learn which operations actually succeeded.

open as a page

Why does one slow operation in a batched GraphQL request delay every other result in it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Because all the results travel in one JSON array in one HTTP response, which cannot be delivered until the last member has finished. Running members concurrently shortens the total wait, but the client still sees nothing until the slowest one completes.

open as a page

In a batched GraphQL request, why do per-operation caching and tracing stop working?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

Because the unit the infrastructure sees is the batch, not the operation. One composite POST body means one cache key, one status, one access-log line and one span, so hit rates, error rates and latency can no longer be attributed to individual operations.

open as a page