Articles
JSON Schema Interview Questions: oneOf, anyOf, References, and Validation Traps
Answer JSON Schema interview questions: oneOf vs anyOf, references, schema versions, and ambiguity tests that expose validation traps before deployment.
JSON Schema questions are deceptively small. An interviewer may show you two branches and ask why an event was rejected, or ask you to design a contract for a polymorphic API payload. The answer is rarely “add oneOf.” It is to state what each branch accepts, count the successful branches for the specific instance, resolve every reference from the correct base URI, and write tests for ambiguity as deliberately as tests for a valid request.
JSON Schema describes constraints for JSON instances. It can be valuable at API boundaries, configuration loading, event validation, and tool-output handling, but only if everyone agrees on the draft and validator behavior. A schema that is readable to its author can still reject legitimate clients or quietly accept an unwanted shape. Practice the adjacent mechanics with Validate JSON against a protobuf-like schema.
Evidence boundary: Official facts here come from the linked JSON Schema documentation, checked September 2026. It defines anyOf as one-or-more valid branches and oneOf as exactly one valid branch; it describes $id as a base URI and $ref as a URI-reference resolved against it. Candidate-reported evidence is not used to predict any employer's interview. Inference includes the payment events, migrations, and testing advice below.
What is the concise answer to oneOf versus anyOf?
Use anyOf when an instance may satisfy one or more alternatives. Use oneOf when it must satisfy exactly one alternative. That single word, “exactly,” creates the most common interview trap: an instance that matches two branches fails oneOf, even though it looks valid to both branch authors.
Think in a truth table rather than in type names.
| Number of valid branches | anyOf result | oneOf result | Typical implication |
|---|---|---|---|
| 0 | Invalid | Invalid | No contract alternative accepted it |
| 1 | Valid | Valid | The unambiguous case |
| 2 or more | Valid | Invalid | oneOf contract is ambiguous for this instance |
For example, imagine an event is either a card payment or a bank transfer. A naive schema may put a kind property in each oneOf branch but fail to require it. An object containing only amount could satisfy both branches if each branch independently allows that property. oneOf then rejects it. The fix is not to swap in anyOf blindly; decide whether the contract truly permits both shapes or whether each branch needs a required discriminator and constraints that exclude the other form.
{
"oneOf": [
{"properties": {"kind": {"const": "card"}}, "required": ["kind", "last4"]},
{"properties": {"kind": {"const": "bank"}}, "required": ["kind", "routing_number"]}
]
}
This example is editorial. It illustrates a useful answer: point out that kind must be present, then test a card event, a bank event, an event with neither, and an event that tries to include both branch-specific fields. Whether extra properties are allowed is a separate product decision; do not assume one merely because the fields exist in a schema.
Why do overlapping branches cause surprising failures?
oneOf asks the validator to determine that every other branch does not validate. Official guidance notes that this can cost more than anyOf, and it makes loose branches particularly risky. A branch with only type: object, no required fields, and permissive properties may validate nearly every object. Pair it with another broad object branch under oneOf, and most inputs fail because they match both.
In an interview, debug in this order:
- List each branch's
required,const,enum,type, and property constraints. - Validate the failing instance against each branch independently and record the pass/fail result.
- Find the smallest overlapping subset rather than editing the whole schema at once.
- Add a deliberate discriminator or mutually exclusive constraint only if the domain needs exactly one variant.
- Retain a regression test for the overlap.
Do not describe oneOf as a generic inheritance tool. allOf requires all subschemas to validate; it does not override conflicting constraints in a parent-like order. If one allOf member closes an object against unknown properties and another member introduces new properties, the combined instance may be impossible to validate. The official combination guide explicitly cautions that allOf is not object-oriented extension.
How should you reason about $id, $ref, and $defs?
A reference is not just a string you hope the validator can find. A schema document has an identifier; $id establishes a base URI, and a $ref URI-reference resolves against that base. The JSON Schema documentation also notes that identifiers are not necessarily network addresses: implementations commonly load schemas into an internal registry rather than fetch arbitrary HTTPS URLs at validation time.
That distinction matters operationally. A service that validates untrusted schemas should not treat every $ref as permission to make an outbound request. Decide the registry, permitted identifiers, draft versions, cache, and failure behavior. In a normal application contract, pin known schemas in a local registry or bundle them at build time; a missing reference should be an explicit deployment or validation failure, not a surprising production network dependency.
Use $defs for named reusable subschemas that live inside one document. The name makes a complex contract easier to scan and avoids copy-and-paste drift:
{
"$id": "https://contracts.example/payments/event",
"$defs": {
"money": {"type": "object", "required": ["currency", "minor_units"]}
},
"properties": {
"total": {"$ref": "#/$defs/money"}
}
}
When you move a schema, test the reference graph as well as validation examples. A relative $ref can resolve differently after a new $id, a different retrieval URI, or a bundle step. The source documentation recommends absolute URIs for base declarations because a relative base cannot resolve in an anonymous schema. State the draft you support. Reference behavior and allowed sibling keywords have historical differences across drafts, so “my validator accepted it” is not a portable explanation.
What should a validation test suite contain?
A useful test suite is a miniature contract review. It contains positive examples, but its highest value often comes from inputs that are almost valid.
| Test case | Expected result | What it catches |
|---|---|---|
| One valid card event | Passes one branch | Basic discriminator and required fields |
| One valid bank event | Passes one branch | Second variant is reachable |
Missing kind | Fails all branches | Required discriminator is real |
kind: card plus no last4 | Fails | Branch-specific requirement |
| A shape matching two broad branches | Fails under oneOf | Ambiguity regression |
| Unknown schema identifier | Fails predictably | Registry and deployment boundary |
| Previous payload version | Expected pass/fail is documented | Compatibility policy |
Test the error surface too. Clients need a stable way to learn whether a request was malformed, unsupported by the selected contract version, or rejected by a business rule after schema validation. A JSON Schema success does not prove authorization, referential integrity, or inventory availability. Keep those layers separate so a client does not infer that “valid JSON” means “accepted order.”
For event streams, validate where the consumer needs a boundary, then observe rejected-message rate and the reason category. Quarantining an invalid event may be safer than repeatedly retrying it, but that is a processing-policy decision. The schema alone does not decide whether a queue should block, skip, or dead-letter malformed data.
How do schema versions and API evolution change the answer?
First distinguish a JSON Schema draft from your API's contract version. A draft controls keyword semantics and vocabulary; an API version is your product's compatibility promise. Both should be explicit. Publishing a new $id or moving a reference is not the same as allowing an older client to omit a newly required field.
For additive changes, an optional property can be compatible with existing instances but still surprise strict consumers that reject unknown fields. For a new required property, consider a new contract version, a server default with clear semantics, or a staged migration. The right answer depends on who produces and consumes the payload and whether a default could change money, permissions, or records.
Do not quietly broaden a oneOf branch to keep an old payload working. It can turn an unambiguous contract into one where an instance matches multiple branches. Instead, give the old version a named schema and test both versions through the same registry. Track real validation failures before removing a supported variant. This is an inference about safe migration practice; the official syntax does not choose your deprecation policy.
Walk through a debugging follow-up
An interviewer says: “Our validator rejects {\"type\":\"email\",\"address\":\"a@example.com\"}. The oneOf has an email branch and a generic notification branch. How do you debug it?”
Say you will validate the instance against each branch separately. The email branch probably passes. If the generic branch requires only type or permits any object, it may also pass. oneOf therefore rejects the instance because two branches matched. Ask whether generic notifications are meant to include emails. If yes, use anyOf only if accepting both classifications is semantically correct; otherwise make the generic branch exclude the email discriminator or use a single tagged union with mutually exclusive variants.
Then add four tests: email, generic notification, unknown type, and a deliberately ambiguous instance. Include the draft and validator version in the test fixture. That final detail shows you understand that a schema is interpreted by a concrete implementation, not by a screenshot of JSON.
How should you answer a system-design version of the question?
For a public API, separate contract distribution from request validation. Publish stable schemas by immutable identifier, bundle or register the versions the service supports, and validate request bodies before business processing. Emit a structured error that includes a safe path and reason, but avoid echoing sensitive payload data. Collect metrics by schema ID, producer version, and failure reason; do not log entire credential-bearing documents merely to debug validation.
For an internal event platform, decide who owns a schema, how producers propose a change, which compatibility checks run in CI, and how consumers discover support. A registry can reduce duplication, but it does not remove the need for contract tests. Replaying a small corpus of historical events through the proposed validator is often more informative than reading a diff of nested allOf blocks.
If asked about performance, explain the shape: simple property checks are usually straightforward, while many alternatives, deep recursion, or broad oneOf branches require more work. Measure the actual validator and schema corpus under your input limits. Do not invent a universal millisecond number or disable validation only because a schema appears complex.
Practice with connected questions
| Question | Practice focus | Why it helps |
|---|---|---|
| Validate JSON against a protobuf-like schema | Contract rules | Turns required fields and types into explicit checks |
| Implement a JSON parser | Syntax boundary | Separates parsing from semantic validation |
| Design prompts for JSON-only LLM responses | Structured outputs | Clarifies why parseable output still needs validation |
| Reason about nested JSON Schema validation | Reference reasoning | Exercises nested branch and schema logic |
| Design a JSON Schema validator | Implementation trade-offs | Lets you discuss registries, errors, and drafts |
Frequently asked questions
Does anyOf stop after the first matching branch?
Its validity rule is one or more matches. An implementation may optimize evaluation, but application code should not rely on a particular evaluation trace unless its chosen validator documents that behavior. If annotations or errors matter, test the implementation and draft in use.
Can $ref fetch a URL by itself?
The reference identifies a schema after URI resolution. The official guide explains that implementations commonly resolve identifiers from an internal schema database rather than make a network request. Configure loading behavior deliberately; do not treat an identifier as an open outbound-fetch policy.
Should a schema reject unknown properties?
It depends on the contract's compatibility and safety needs. Rejecting unknown fields can catch client mistakes; accepting them can make additive evolution easier. State the choice per object and test it, especially inside alternatives where a permissive branch can create oneOf overlap.
JSON Schema interview questions reward precise counting and boundaries. Name the branch rule, resolve references from a known identifier, pin the dialect, and prove the contract with ambiguous as well as happy-path instances. That is much more useful than reciting a list of keywords.