API Contracts and Schema Design Questions
Defining the interface contract between producers and consumers: request/response payload shapes, data models, field-level validation, nullability, and enum/typing decisions (including safely evolving an enum's allowed values without breaking clients). Covers contract-first design with OpenAPI and JSON Schema (authoring specs, generating SDKs and mock servers, catching breaking changes in CI), mapping internal domain models to external DTOs as both evolve independently, and designing a stable error-response contract (structured error codes, correlation IDs, retryable classification). Also covers the leadership and behavioral practice of establishing and governing contract standards across teams. The contract is the durable artifact clients depend on.
Draft a concise API contract (endpoints, request/response examples, and audit behavior) for an internal feature-flagging service used by multiple teams. Include endpoints for creating a flag, evaluating a flag on a low-latency path, toggling a flag, and retrieving audit logs. Note the performance and safety considerations your schema choices need to account for.
Sample Answer
A feature-flag service's contract centers on one asymmetry: the evaluate path is read constantly, on a hot path, by every service that checks a flag, while create/toggle/audit are low-volume management operations. The contract should reflect that asymmetry directly in its shape, not just in the implementation behind it.
The four endpoints
Create a flag (POST /flags): accepts a flag key, a human-readable description, and a default value, and returns the created flag's identifier and current state. This is a low-frequency, administrative operation, so its response can afford to be verbose (full flag metadata) without any latency concern.
Evaluate a flag (GET /flags/{key}/evaluate?context=...): the hot path. The request carries an evaluation context (user ID, environment, or other targeting attributes); the response should be as small as possible, essentially just the resolved boolean or variant value, because this call happens on every request path that checks the flag and any extra payload weight is paid millions of times over.
Toggle a flag (PATCH /flags/{key}): flips a flag on or off (or changes its targeting rules), and should return the new state plus a version or timestamp so a caller can confirm the toggle actually took effect and see what it changed from.
Retrieve audit logs (GET /flags/{key}/audit): returns a paginated history of who changed the flag, when, and what the before/after state was, which is the accountability trail for a service that can change application behavior without a code deploy.
Worked example: the evaluate response shape
{
"flag_key": "new_checkout_flow",
"value": true,
"reason": "targeting_rule_match"
}
Compare that to a create response, which can be much richer:
{
"flag_key": "new_checkout_flow",
"description": "Enables the redesigned checkout flow for eligible users",
"default_value": false,
"created_at": "2026-07-28T12:00:00Z",
"version": 1
}
The evaluate response is deliberately minimal (three small fields); the create response can afford ten times the payload because it runs orders of magnitude less often.
Performance and safety considerations the schema has to account for
- Evaluate must be cacheable and fast to parse, so its schema stays flat and small; nesting a full targeting-rule explanation into every evaluate call would add latency to the busiest path in the system for the sake of a debugging convenience almost nobody needs on every call.
- Toggle must be auditable by construction, not as an afterthought: every toggle response including a version number means a caller (and the audit log) can always answer "what did this flag look like immediately before this specific change."
- The evaluation context in the evaluate request needs a stable, documented shape (which attributes are supported for targeting) so that adding a new targeting dimension later is an additive, backward-compatible schema change, not a breaking one for every existing caller.
Trade-offs and pitfalls
The most common mistake is putting all four operations behind one uniform, verbose response schema for consistency's sake, which quietly taxes the highest-traffic endpoint (evaluate) to make the lowest-traffic ones (create, audit) marginally easier to read. A feature-flag contract earns its keep specifically by treating "how often is this called" as a first-class input into how big its response is allowed to be.
Tell me about a time you had to push back on an API or data-model design that seemed simpler for short-term delivery but would have created long-term pain for downstream consumers. How did you make the case, and what was the final decision?
Sample Answer
The strongest version of this story is not "I was right and they were wrong," it's showing the concrete, specific downstream cost the simpler design would have created, translated into terms the other side of the table actually cared about (a delivery deadline, a support burden, a migration cost six months out), and then describing how the actual decision got made once that cost was visible to everyone.
What a strong answer walks through
The situation: name the specific design choice under pressure (a schema shortcut, a field reused for two purposes, a response shape copy-pasted from an unrelated endpoint) and the delivery pressure driving it, honestly, without caricaturing the other side's position as simply wrong.
The case you made: the strongest version of this case is concrete, not principled in the abstract. "This will be hard to maintain" rarely moves a deadline; "this specific field reuse means every future consumer has to special-case whether this response came from path A or path B, and we already have three planned features that will need to tell them apart" is something a stakeholder can actually weigh against the deadline.
How the decision actually got made: did you get the design changed outright, negotiate a smaller fix with a follow-up ticket to do it properly, or lose the argument and later have to deal with the consequence you predicted? All three are legitimate answers; the weakest version of this story claims a clean win with no friction, which reads as either an easy problem or a polished retelling.
The outcome: what happened afterward, concretely, that validates (or complicates) the case you made at the time.
Worked example shape
"We were under a two-week deadline to ship a partner integration, and the plan was to reuse an existing internal user_id field on the response as the partner-facing identifier, saving us from adding a new field and updating a few internal services. I pushed back because I could see two features already on the roadmap that would need a distinct partner-facing identifier separate from the internal one (partner-scoped API keys, and a planned data-residency requirement that needed to know which records were partner-visible). I proposed adding a new partner_ref field instead, which took two extra days but meant we did not have to do a breaking migration eight weeks later when the partner-scoped-API-keys feature landed and genuinely needed that separation. The team agreed to the two extra days once the specific upcoming conflict was visible, not because of a general 'good practice' argument."
Trade-offs and pitfalls
A common weak point in this story is framing it as pure technical purism ("clean code" or "best practices") rather than a concrete, forecastable cost; interviewers are listening for whether you can translate a technical concern into terms a non-technical stakeholder, or a deadline-driven engineering lead, would actually act on. Equally common: telling this story as an unqualified win when the honest answer is more nuanced (you got a smaller compromise, or the team shipped the shortcut anyway and you were right six months later); either honest version is stronger than a suspiciously frictionless win.
How do you lead API and schema governance across multiple engineering teams without becoming a bottleneck? Describe the standards, review rituals, exception process, and coaching mechanisms you would put in place so teams can move quickly while still protecting contract quality and data integrity.
Sample Answer
The mechanism that actually scales is making the RIGHT PATH the fast path: most schema changes should be able to pass through lightweight, automated checks with no human review bottleneck at all, reserving deliberate human review for the genuinely risky category of changes, and building a clear, fast exception process for the inevitable case that does not fit the standard.
Standards
Write down, concretely, what counts as additive (safe, no review needed beyond automated checks) versus what needs review (removing or retyping a field, tightening a validation rule, anything that could break an existing consumer). Vague standards ("use good judgment") do not scale past a handful of teams; specific, automatable rules do.
Review rituals
Reserve actual human review time for the standard's genuinely risky category, not for every schema change. A short, regular forum (a 30-minute weekly API-design review, not a per-PR gate) where teams bring proposed breaking changes or genuinely novel contract designs keeps the review load proportional to actual risk instead of proportional to total change volume.
The exception process
Every governance model eventually meets a team with a legitimate reason to deviate (a genuine deadline, a design the standard did not anticipate). The exception process needs to be fast and lightweight, or teams will route around the standard entirely rather than use it; a same-day escalation path to a small, named decision-making group, with a requirement to document the exception and revisit it later, keeps the standard's credibility intact without becoming an unconditional blocker.
Coaching mechanisms
Governance that only shows up as a gate at review time teaches people to satisfy the gate, not to internalize the underlying judgment. Pairing the standard with office hours, a small set of worked examples showing WHY a rule exists (not just what it says), and reviewing an early draft with a team before their PR is nearly done all shift the standard from "the thing that blocks my merge" to "something that helped me design this well before I'd invested a week in a shape that needed to change."
Worked example
An organization adopts an automated OpenAPI-diff check as the default gate: additive changes merge with zero human involvement, and the CI (continuous integration) check specifically flags anything it classifies as breaking, routing that PR to a lightweight, asynchronous review queue rather than a scheduled meeting. A team proposing a genuine breaking change (removing a deprecated field two quarters after announcing the deprecation) posts it to the weekly review forum with the automated diff attached; the forum approves it in five minutes because the actual analysis (is this really safe, has the deprecation window passed) was mostly done automatically already, and the meeting exists to catch judgment calls the automation cannot make, not to re-derive facts the CI check already established.
Trade-offs and pitfalls
The classic failure mode is a governance model that reviews everything with equal weight, which either becomes a bottleneck teams learn to route around (shipping through side channels, or simply not asking) or burns out the reviewers, who end up rubber-stamping routine changes because there is too much volume to give genuinely risky ones real attention. The other common failure: an exception process so slow or so poorly documented that teams stop using it and just break the rule quietly instead, which is worse than either following it or having a visible, tracked exception.
Write a JSON Schema (draft 2020-12 or later) for a POST endpoint /v1/apps/{app_id}/events that accepts an event payload with properties: eventType (enum), timestamp (ISO 8601 string), actor (object with id and type), metadata (optional object with string values), and items (array of objects with id and quantity). Include validation rules, required fields, and example success and validation-error responses.
Sample Answer
The schema needs five things: a type constraint on every field, an enum for eventType so only known event kinds validate, a format constraint on timestamp, an object shape for actor with its own required sub-fields, and an array shape for items with per-item structure. Below is a complete, valid JSON Schema (draft 2020-12) for the event.
The schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://api.example.com/schemas/app-event.json",
"title": "AppEvent",
"type": "object",
"properties": {
"eventType": {
"type": "string",
"enum": ["item_created", "item_updated", "item_deleted"]
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"actor": {
"type": "object",
"properties": {
"id": { "type": "string" },
"type": { "type": "string", "enum": ["user", "service"] }
},
"required": ["id", "type"],
"additionalProperties": false
},
"metadata": {
"type": "object",
"additionalProperties": { "type": "string" }
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"quantity": { "type": "integer", "minimum": 1 }
},
"required": ["id", "quantity"],
"additionalProperties": false
},
"minItems": 1
}
},
"required": ["eventType", "timestamp", "actor", "items"],
"additionalProperties": false
}
Design decisions worth calling out
metadatais genuinely optional (absent fromrequired), and itsadditionalPropertiesconstraint restricts VALUES to strings without naming the specific keys, which keeps the schema honest about what "optional object with string values" means without over-constraining future keys.itemsrequires at least one entry (minItems: 1): an event with an empty items array is treated as invalid here, a deliberate validation rule rather than an oversight.additionalProperties: falseon the top level and on each nested object rejects unrecognized fields, which catches client typos early but is itself a compatibility decision: it means adding a genuinely new field later requires updating this schema in lockstep with any producer that starts sending it, rather than silently tolerating it.
Worked example: validating example payloads against this schema
A valid event:
{
"eventType": "item_created",
"timestamp": "2026-07-28T12:00:00Z",
"actor": { "id": "usr_123", "type": "user" },
"metadata": { "source": "mobile_app" },
"items": [{ "id": "sku_1", "quantity": 2 }]
}
This validates cleanly: every required field is present, eventType is one of the three enum values, and items has one well-formed entry.
An invalid event (empty items):
{
"eventType": "item_created",
"timestamp": "2026-07-28T12:00:00Z",
"actor": { "id": "usr_123", "type": "user" },
"items": []
}
This is rejected, specifically because items has zero entries and the schema's minItems: 1 constraint requires at least one. Both examples above were run against this exact schema with a JSON Schema validator; the valid example passed and the empty-items example failed with the minItems violation, confirming the schema behaves as written.
Example success response: {"event_id": "evt_9a1b", "accepted": true} (HTTP 201). Example validation-error response: {"error_code": "validation_error", "message": "items must contain at least 1 item"} (HTTP 400).
Trade-offs and pitfalls
additionalProperties: false is a real trade-off, not a free safety net: it makes typos in client requests fail loudly (good), but it also means the schema and every producer must be updated together the moment a genuinely new, backward-compatible field needs to be added, which is slower than a looser schema that simply ignores unknown fields. Choosing strict validation here is defensible for an internal event contract with few, coordinated producers; a public-facing schema with many independent producers might reasonably relax this to allow additive fields to pass through unvalidated.
Your team wants to add a computed field to an existing API response, but the value is expensive to calculate and could materially change the response time. How would you decide whether to compute it synchronously, cache it, materialize it in storage ahead of time, or expose it through a separate endpoint instead?
Sample Answer
The decision hinges on two independent questions: how often is this field actually read relative to how often the underlying data changes, and how expensive is the computation relative to the latency budget of the endpoint it would live on. Getting the pairing right (cheap-and-frequent versus expensive-and-rare) is what separates a good answer from a checklist of four generic options.
The four options, and when each wins
Compute synchronously, inline in the response. Only viable if the computation is genuinely cheap relative to the endpoint's existing latency budget; adding a moderately expensive computation directly into a hot-path response risks making every caller pay a cost that only some of them actually need.
Cache the computed value. A strong fit when the underlying data changes far less often than the field is read (a product's aggregate rating computed from reviews that arrive far less frequently than the product page is viewed): compute once, serve many times, with a clear invalidation trigger (recompute when a new review lands, or on a time-based TTL (time-to-live) if slight staleness is acceptable).
Materialize it in storage ahead of time. The right choice when the computation is too expensive to redo per-cache-miss and the read pattern is frequent and predictable enough to justify pre-computing and storing the result as data, updated by a background job or an event trigger rather than computed on any request path at all.
Expose it through a separate endpoint. The right choice when only a MINORITY of callers actually need the field, since folding an expensive computation into the primary response penalizes every caller (including the majority who never wanted it) to serve the few who do; a separate, explicitly-named endpoint lets the cost be paid only by the consumers who ask for it.
Worked example
A product page's response currently includes basic fields (name, price, description) served in under 50ms. The team wants to add a computed similar_products field requiring a moderately expensive similarity computation across the catalog, roughly 200ms.
- If most callers of this endpoint (analytics jobs, internal tooling) never use
similar_products, but the customer-facing web page always renders it: expose it through a separate endpoint (GET /products/{id}/similar) so the majority of callers keep their fast response, and the web page issues a second, parallel request specifically for the expensive field. - If the underlying catalog data changes only a few times a day but the product page is viewed millions of times: cache the computed value, recomputing on a catalog-change event rather than per-request, so the 200ms cost is paid rarely instead of on every page view.
Trade-offs and pitfalls
Folding an expensive field directly into the primary response "for convenience" is the most common mistake: it looks simpler in the short term (one endpoint, one call) but silently taxes every caller of that endpoint with the new field's cost, including callers who will never read it. The opposite mistake, splitting out a separate endpoint for a field nearly every caller actually needs, just adds an extra round trip for the common case; the decision genuinely depends on measuring who calls this endpoint and how they use the response, not on a general preference for either simplicity or slimness.
Unlock Full Question Bank
Get access to all 16 API Contracts and Schema Design interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.