skip to content

In GraphQL, what happens when @skip(if: true) and @include(if: true) sit on one field?

level: middleimportance: should knowfreq 45%

answer

  1. Work the four-row truth table
  2. Only one row keeps the field
  3. Skip false AND include true
  4. The spec says neither takes precedence
  5. Neither directive is repeatable

basics

~20 s

The field is excluded. When both directives apply to one selection, it survives only if the @skip condition is false and the @include condition is true, so a true @skip removes it whatever @include says.

solid answer

~50 s

Both may legally sit on the same selection, and the rule combining them is a conjunction: the selection is kept only when `@skip`'s condition is **false** and `@include`'s condition is **true**. So `@skip(if: true) @include(if: true)` yields nothing in the response — the mnemonic is that skip wins, though the specification is careful to say neither directive formally takes precedence over the other; the combined condition simply happens to be `!skip && include`. Order in the document text does not matter, and neither directive is repeatable, so two `@skip`s on one field is a validation error rather than a stacked condition. In practice, writing both on one selection is a smell: it produces a four-row truth table that a reader has to work out, and one directive with a correctly-oriented variable expresses the same intent.

code

graphql · 6 lines
graphql
query TicketRow($ticketId: ID!, $hideBarcode: Boolean!, $showBarcode: Boolean!) {
  ticket(id: $ticketId) {
    seatLabel
    barcodeSvg @skip(if: $hideBarcode) @include(if: $showBarcode)
  }
}

go deeper

for a junior

Learn the single combining rule: the selection survives only when the @skip condition is false and the @include condition is true. If you remember nothing else, remember that a true @skip always removes the field.

for a middle

Reproduce the four-row truth table on the spot and state that the order of the two directives in the text is irrelevant. Know that neither is repeatable, so two @skip directives in one location is a validation error.

for a senior

Say plainly that the specification frames this as a conjunction rather than a precedence, and be ready to argue that one conditional per selection is the maintainable style — the combining rule is for reading other people's documents.

for a principal

Own the convention that a variable name states the positive case, so reviewers never have to solve a truth table to know what a screen renders. Drifting paired flags are a real class of client bug worth linting for.

## The situation Nothing stops a client writing both conditionals on one selection: ```graphql query TicketRow($ticketId: ID!, $hideBarcode: Boolean!, $showBarcode: Boolean!) { ticket(id: $ticketId) { seatLabel barcodeSvg @skip(if: $hideBarcode) @include(if: $showBarcode) } } ``` This is valid GraphQL. The question is what the server does with it, and the answer is one line: **the selection is kept only if the `@skip` condition is false and the `@include` condition is true.** Everything else follows from that. ## The truth table | `@skip(if:)` | `@include(if:)` | in the response? | |---|---|---| | false | true | yes | | false | false | no | | true | true | **no** | | true | false | no | The only row that produces the field is the first. Three of four rows drop it, and the interesting row is the third: both conditions are `true`, which reads like a contradiction, and the resolution is that the field disappears. Hence the mnemonic **skip wins**. ## Say the rule precisely, not as a precedence An interviewer probing this will often push on the wording, because "skip has higher precedence" is a slightly wrong description of a right answer. The specification's own note is that neither `@skip` nor `@include` has precedence over the other; when both are provided on the same field or fragment, the selection must be queried only if the `@skip` condition is `false` **and** the `@include` condition is `true`. It is a single conjunction evaluated over two independent conditions, not a conflict that one directive resolves in its favour. The observable outcome is identical — a true `@skip` always removes the selection — but the reasoning is what distinguishes a candidate who has read the rule from one who has inferred it. A useful consequence of it being a conjunction: **the order of the two directives in the document text is irrelevant.** `@skip(if: $a) @include(if: $b)` and `@include(if: $b) @skip(if: $a)` behave identically. There is no left-to-right short-circuit whose result you could change by reordering. ## Contradictory literals are not a validation error `@skip(if: true) @include(if: true)` is perfectly valid — the document parses, validates, and executes, and the field is quietly absent. Validation checks that the directives exist, that they are used in a legal location, and that `if` is supplied with a `Boolean!`; it does not evaluate the conditions or look for logical contradictions. This matters because the condition is normally a *variable*, and validation happens without knowing variable values. A server cannot reject an impossible combination it will only discover at execution time, so it does not try to reject the literal case either. ## Neither directive is repeatable A directive definition may be marked repeatable, meaning it can appear more than once in the same location. `@skip` and `@include` are not. So this is a validation error, not an implicit AND of two conditions: ```graphql # invalid: @skip appears twice in one location barcodeSvg @skip(if: $hideForPrint) @skip(if: $hideForKiosk) ``` If you genuinely need two reasons to hide something, the boolean has to be combined **on the client, before the request** — compute `hide = hideForPrint || hideForKiosk` and pass one variable. GraphQL has no expression language in the document; every condition is a value the caller has already decided. ## Why writing both is usually a defect A ticketing client's event page carried `barcodeSvg @skip(if: $hideBarcode) @include(if: $showBarcode)` because two teams added a flag a year apart. Nobody could state the behaviour without the truth table, and the two variables drifted: some screens sent `hideBarcode: false, showBarcode: false` and lost the barcode without meaning to. The fix was one variable and one directive. That is the practical advice worth giving in the interview — the combining rule is worth knowing precisely so you can *read* a document someone else wrote, but a document you write should carry one conditional per selection, oriented so the variable name reads true-means-shown. ## Where the pairing does belong There is one shape where using both directives *in the same document* is idiomatic, and it is worth not confusing with this case: putting `@include(if: $x)` on one selection and `@skip(if: $x)` on a **different** selection, against the same variable, to get an either/or. Two directives on two selections is the branching idiom; two directives on one selection is the truth table.

  • Does swapping the order of the two directives in the document text change the outcome?
    No. The rule is a single conjunction over two independent conditions — kept only when skip is false and include is true — so there is no left-to-right evaluation to reorder. `@skip(if: $a) @include(if: $b)` and `@include(if: $b) @skip(if: $a)` produce the same response for the same variables.
  • How would you express "hide this field if either of two flags is set"?
    Combine the booleans on the client and pass one variable, because `@skip` is not repeatable and the document has no expression syntax. Compute `hide = flagA || flagB` before sending and write a single `@skip(if: $hide)`. Two `@skip` directives in one location is a validation error, not an implicit AND.
  • Is `@skip(if: true) @include(if: true)` rejected by validation as contradictory?
    No. Validation checks that each directive exists, sits in a legal location and receives its required `Boolean!` argument; it never evaluates the conditions. Conditions are usually variables whose values are unknown at validation time, so no conforming implementation looks for logical contradictions. The document executes and the field is quietly absent.

saying these in an interview costs you the question

  • Says @include overrides @skip when both are true
  • Claims the document is rejected as contradictory
  • Thinks the directives evaluate left to right
  • Believes two @skip directives on one field stack
  • Expects the field to arrive as null rather than absent
  • Describes the rule as an OR of the two conditions

context