After one root mutation field errors, do the later root mutation fields still run?
answer
- Serial means ordered, not halting
- Look for a stop condition in the loop
- A field error is confined to its field
- The next write still happens
- No directive aborts an operation
basics
~20 sYes. A field error produces null for that one field and an entry in the errors list, and the executor then moves on to the next root field and runs it. There is no early exit, so later side effects still happen.
solid answer
~50 sThe published execution algorithm has no stop condition. When a root mutation field's resolver raises, the executor records the error, completes that field as `null`, and continues to the next entry in the selection set — which for a mutation means actually performing the next write. So a document that applies a discount, fails to void a line, and then captures payment will still capture the payment. This surprises people who read the serial-execution guarantee as a pipeline that halts on failure; it is an ordering guarantee, nothing more. Whether the failing field was declared Non-Null changes how much of `data` the client can still see, but it never un-runs the writes that already happened. If you need "stop at the first failure", there is no directive and no operation-level flag for it — you get it by putting the sequence inside one root field.
code
graphql · 5 linesmutation CloseOutTable {
applyDiscount(orderId: "ord_48213", code: "STAFF15") { totalCents }
voidLine(orderLineId: "line_9127") { orderId }
capturePayment(orderId: "ord_48213", amountCents: 4183) { receiptId }
}go deeper
Remember the headline: a failing root mutation field does not stop the ones after it. If you take nothing else from this, take that a second write can still happen after the first has already failed.
Explain it from the algorithm: the serial loop resolves each root field in order and has no condition that inspects the error list. Then say what that means concretely — the next write is performed, not skipped.
Show the operational consequence. Describe a document where the surviving write causes the damage, and explain why moving the authorization check ahead of the first write is the real fix rather than reordering the fields.
Frame it as an interface question. Decide whether clients may send multi-write documents at all, and be ready to say how you enforce that decision across client teams instead of documenting a convention nobody reads.
## The question behind the question Interviewers ask this to find out whether you have read the execution algorithm or only absorbed the folklore. The folklore says "mutations run serially, one after another", and people quietly extend that into "so the server stops when one fails". The serial rule is an **ordering** guarantee. It says nothing about stopping. ## What the algorithm actually does Executing a mutation's root selection set is a loop: take the next field in document order, resolve it, complete its value, append any error that was raised, take the next field. The loop has no condition that consults the accumulated error list. Nothing in it says "if errors is non-empty, skip the remainder". A conforming executor therefore runs every root field the client wrote, whatever happened to the ones before. A **field error** — a resolver raising rather than returning — is deliberately local. It is recorded and the field is completed as `null`. Execution of the rest of the request goes on. ## What that costs you in a mutation For a query this is barely interesting: one branch of a read comes back empty. For a mutation it is a real hazard, because "continue" means "perform the next write". Take a restaurant ordering graph closing out a table: ```graphql mutation CloseOutTable { applyDiscount(orderId: "ord_48213", code: "STAFF15") { totalCents } voidLine(orderLineId: "line_9127") { orderId } capturePayment(orderId: "ord_48213", amountCents: 4183) { receiptId } } ``` `voidLine` fails on a permission check that only runs once the resolver has loaded the line — too late to prevent anything. The executor nulls that field, notes the error, and then runs `capturePayment`, which charges 4,183 cents for an order that still contains the line the server just refused to remove. The guest is charged for food they sent back, and the response reporting it looks like a healthy partial result. ## The ordering heuristic that does not save you A common instinct is to sort root fields "riskiest first, most damaging last", on the theory that the dangerous write will never be reached. It does not work, because the executor does not skip. Ordering only decides *which* writes have already committed by the time something fails; it never prevents a later one. The only construct that reliably keeps the capture from happening is a single field whose resolver decides, in code, not to capture. ## Non-Null changes the view, not the history If the failing root field is declared Non-Null, the `null` it produced cannot stay there, so the null propagates up to the nearest nullable position — for a root field, that is `data` itself, which becomes `null`. The client then sees no payloads at all. It is worth being very clear about what that does and does not mean: it changes **what the client can read**, and it does not change **what the server did**. All three writes still ran. The discount is applied and the payment is captured; the client just cannot see the receipt id, which makes the situation strictly harder to recover from, not easier. ## There is no abort switch Candidates sometimes reach for one of these, and none of them exists: * an executable directive that aborts the operation on error — no such directive is specified, and inventing a name for one is a bad sign; * an operation-level flag or argument that makes the document all-or-nothing; * declaring the fields Non-Null so that "the rest is skipped" — as above, that is a response-shaping rule, applied during value completion, not an execution guard; * splitting the writes into several operations in one document — a request executes exactly one operation, so the others simply never run. ## What you do instead You express the sequencing where sequencing decisions can actually be made: inside one resolver. ```pseudocode resolve closeOutTable(input): begin transaction line = load(input.orderLineId) if not caller.mayVoid(line): # check FIRST, before any write rollback; return failure("VOID_NOT_PERMITTED") applyDiscount(input.orderId, input.code) voidLine(line) capturePayment(input.orderId, recomputeTotal(input.orderId)) commit ``` Two things changed, and both matter. The authorization decision moved ahead of the first write, so the request that cannot succeed never starts. And the three writes became one unit that a single piece of code controls, so "stop before capturing" is now expressible — it is an `if` statement, which is exactly the sort of thing an execution algorithm was never going to give you.
- Would ordering the root fields so the dangerous write comes last protect you?No. Ordering decides which writes have already committed when something fails; it never stops a later field from running, because the executor has no skip step. The dangerous write goes ahead regardless. Ordering is worth thinking about only for how much damage is already done at the moment of failure, not as a guard.
- If the failing root field is Non-Null, does that stop the later writes?No. Non-Null affects value completion — the null propagates up and can blank `data` entirely — which changes what the client is able to read. Every root field still executed and every side effect still happened. In practice this is worse, because the client loses the payloads it would have needed to work out what to compensate.
- Could a client put each write in its own operation in one document so that a failure stops the rest?No. A request executes exactly one operation; when a document defines several, the client names the one to run and the others are never executed. Sending them as separate requests does give you a stop point, but only because the client is now the sequencer — and each request is then a separate round trip whose own failure leaves the same partial state.
saying these in an interview costs you the question
- Says the executor stops at the first field error
- Believes serial execution implies halt-on-failure
- Invents a directive that aborts the operation
- Thinks Non-Null prevents later fields from running
- Orders root fields as a safety mechanism
- Assumes a blanked `data` means nothing was written