skip to content

In an OpenAPI schema, what is the difference between allOf, anyOf and oneOf?

level: middleimportance: must knowfreq 68%

answer

  1. Three keywords, three match counts
  2. AND, OR, exclusive OR
  3. Composition versus alternatives
  4. Overlapping branches break the strictest one
  5. Branches must be provably disjoint

basics

~20 s

In an OpenAPI schema, allOf requires the value to satisfy every subschema, anyOf requires at least one, and oneOf requires exactly one. allOf is used for composition, oneOf for alternatives, and anyOf for overlapping permissive unions.

solid answer

~50 s

All three take an array of subschemas and differ in how many must match. `allOf` means the instance must validate against **every** branch — it is set intersection, used to compose a base schema with extra properties, which generators often render as inheritance. `oneOf` means **exactly one** branch may match; it models genuine alternatives such as `CardPayment` or `BankTransfer`, and generators emit a union or sealed type. `anyOf` means **at least one** branch matches, so overlaps are fine — it is the permissive union and is the weakest signal for tooling. There is also `not`, which inverts a subschema and is rarely worth the trouble. The practical trap is `oneOf` with loose branches: if two of them are plain objects with no `required` and no `additionalProperties: false`, a payload matches both, "exactly one" fails, and validation rejects data the design intended to allow. Discriminating branches need distinguishing constraints.

code

yaml · 29 lines
yaml
components:
  schemas:
    Animal:
      type: object
      required: [name]
      properties:
        name: { type: string }

    Dog:
      allOf:
        - $ref: '#/components/schemas/Animal'
        - type: object
          required: [breed]
          properties:
            breed: { type: string }

    PaymentMethod:
      oneOf:
        - $ref: '#/components/schemas/CardPayment'
        - $ref: '#/components/schemas/BankTransfer'

    CardPayment:
      type: object
      required: [kind, last4]
      properties:
        kind:
          type: string
          enum: [card]      # keeps the oneOf branches disjoint
        last4: { type: string }

go deeper

for a junior

Recall the three match counts — all, at least one, exactly one — and that allOf is the one used to build a schema from a shared base.

for a middle

Explain the validation semantics precisely, why allOf is intersection rather than inheritance, and why generators still render it as extends.

for a senior

Show the failure modes you have hit in production: non-disjoint oneOf branches rejecting valid payloads, and additionalProperties: false poisoning an allOf composition.

for a principal

Own when composition is worth its cost — how deep nesting is allowed to go, whether the toolchain across all target languages supports the keyword, and when a flatter payload design beats a clever schema.

## The three keywords Each takes an array of subschemas and is a rule about how many of them the instance must satisfy. - **`allOf`** — the value must validate against *all* subschemas. Logical AND, an intersection of constraints. - **`anyOf`** — the value must validate against *at least one*. Logical OR; matching several is fine. - **`oneOf`** — the value must validate against *exactly one*. Exclusive OR; matching two or more is a validation **failure**. A fourth, `not`, negates a single subschema: the value must *not* validate against it. ## allOf: composition, not inheritance `allOf` is what people reach for to express "a Dog is an Animal plus these extra fields": ``` Dog: allOf: - $ref: '#/components/schemas/Animal' - type: object properties: breed: { type: string } required: [breed] ``` Read precisely, this says nothing about inheritance — it says the instance must satisfy both constraint sets simultaneously. That distinction matters because of the classic footgun: putting `additionalProperties: false` inside one branch. Each subschema is evaluated independently and only sees the properties *it* declares, so the branch that knows about `breed` rejects `name` inherited from `Animal`, and nothing validates. If you need strictness with composition, apply it in a single flattened schema rather than inside an `allOf` branch. Code generators, by convention, do treat a single-`$ref`-plus-object `allOf` as inheritance and emit `class Dog extends Animal`. That is generator convention, not specification semantics. ## oneOf: real alternatives `oneOf` is the right keyword when a payload is one of several distinct shapes: ``` PaymentMethod: oneOf: - $ref: '#/components/schemas/CardPayment' - $ref: '#/components/schemas/BankTransfer' ``` Generators turn this into a union, a sealed class hierarchy, or a wrapper type, depending on the target language. The exclusivity is where documents break. Suppose `CardPayment` and `BankTransfer` are both `type: object` with all-optional properties and no `additionalProperties` restriction. Then `{"last4": "4242"}` validates against both — because a JSON object with an unknown-but-permitted extra property satisfies a permissive object schema — and `oneOf` fails on "more than one matched". The fix is to make the branches genuinely disjoint: give each a `required` discriminating property, often with a single-value `enum` or `const`, so exactly one can ever match. ## anyOf: the permissive union `anyOf` accepts overlap. Use it when the alternatives are not mutually exclusive by nature — "either an email-shaped string or a phone-shaped string, and something might qualify as both" — or when you deliberately want validation to pass on the first success without worrying about exclusivity. Its weakness is tooling. Generators often collapse `anyOf` to a loose type, or to the same union they would build for `oneOf`, losing the distinction. Documentation renderers show it less clearly. Prefer `oneOf` with disjoint branches when you can, because it carries more information. ## Choosing between them A useful decision order: 1. Am I adding constraints to an existing shape? → `allOf`. 2. Are the shapes mutually exclusive alternatives? → `oneOf`, and make the branches provably disjoint. 3. Do the alternatives genuinely overlap? → `anyOf`, and accept weaker codegen. 4. Am I excluding a shape? → `not`, and expect poor tooling support. ## Version notes Swagger 2.0 supported only `allOf`, which is why old documents fake alternatives with a loose object and prose. OpenAPI 3.0 added `oneOf`, `anyOf` and `not`. OpenAPI 3.1, aligned with JSON Schema 2020-12, keeps all four and additionally offers conditional keywords `if`/`then`/`else` and `const`, the last of which is the cleanest way to pin a discriminating property to a single value. `const` is not available in 3.0, where a single-value `enum` is the idiom. ## Nesting and readability All four compose, and a deeply nested `allOf` inside `oneOf` inside `anyOf` quickly becomes unreadable and unsupported by half your toolchain. In production specs, one level of composition is usually enough — flatten aggressively, and if a schema needs three levels of logic to express, the payload design is likely the real problem.

  • Why can a oneOf with two plain object branches fail validation on a payload that looks fine?
    Because a permissive object schema — no `required`, no `additionalProperties: false` — matches almost any object. If both branches match, `oneOf` requires exactly one and therefore fails. Give each branch a required discriminating property, so at most one can ever validate.
  • Why does additionalProperties: false inside an allOf branch usually break composition?
    Each subschema is validated independently and only knows the properties it declares itself. The strict branch sees the properties contributed by its siblings as unknown and rejects them, so nothing validates. Apply strictness to a single flattened schema instead.
  • Does allOf actually mean inheritance?
    No. It means the instance must satisfy every subschema — an intersection of constraints. Code generators conventionally render a `$ref` plus an inline object as `extends`, which is why it feels like inheritance, but the specification defines only the validation rule.
  • When would you prefer anyOf over oneOf?
    When the alternatives genuinely overlap and a value legitimately satisfying two branches should still be accepted. Accept that generators and docs treat `anyOf` less precisely, so if you can make the branches disjoint, `oneOf` conveys more to the toolchain.

saying these in an interview costs you the question

  • Says allOf is real schema inheritance
  • Uses oneOf for branches that can both match
  • Puts additionalProperties: false inside an allOf branch
  • Treats anyOf and oneOf as interchangeable
  • Thinks Swagger 2.0 supported oneOf

context