Skip to main content

Quiz Calibration

Attempt the quiz before reading this page. Preserve the original answer, correction and help received. These explanations calibrate retrieval; successful recall alone does not complete the module or its practical evidence gates. For context such as shared schemas, use the question page.

Question 1: Contract vs Implementation​

In one paragraph, distinguish the API contract from the implementation. Give one concrete thing that belongs in each, and one thing that is easy to get wrong.

Calibration: The contract is what consumers observe on the wire and can depend on: URL, verb, request and response schemas, status codes, error shape, auth rules, idempotency, ordering. The implementation is everything behind it: database schema, caching strategy, queue topology, class hierarchy. A thing that belongs in the contract: "201 Created on successful POST, with Location header pointing at the new resource." A thing that belongs in implementation: "we use Postgres with a trigger to emit an outbox row." Easy mistake: promising observable behavior like stack traces in debug responses or the specific order of fields inside JSON - consumers build on anything they see, whether you meant them to or not.

Question 2: Design This Endpoint and Defend - Cancel an Order​

Design the endpoint used by a client to cancel an order. Give URL, verb, request body, success response, one conflict response. Defend the URL choice against DELETE /orders/{id} and against PATCH /orders/{id}.

Calibration: ``` POST /orders/ord_1:cancel Content-Type: application/json Idempotency-Key: 4b2e-...

{ "reason": "customer_request" }

HTTP/1.1 200 OK { "id": "ord_1", "status": "cancelled", "cancelled_at": "2026-04-10T..." }

HTTP/1.1 409 Conflict { "code": "ALREADY_CANCELLED", "message": "..." }


Not `DELETE /orders/{id}` because deletion means "remove from addressable state"; the order still exists as a cancelled record with history. Not `PATCH /orders/{id}` because cancellation is a state transition with business rules ("cannot cancel shipped orders") and side effects (refund emitted), not a field edit; a custom method makes validation, authorization, and audit trails explicit.

### Question 3: HTTP Verbs and Idempotency

Rank `GET`, `POST`, `PUT`, `PATCH`, `DELETE` by idempotency as defined in the HTTP spec, and explain one technique to make a non-idempotent operation retryable.

**Calibration:** Safe and idempotent: `GET`. Idempotent: `PUT`, `DELETE`. Not idempotent: `POST`, `PATCH`. To make `POST` idempotent, clients send an `Idempotency-Key` header; the server stores `(key, request_hash, response)` and returns the cached response on a repeat with the same key and body, `409 Conflict` on a repeat with a different body, and only processes once before TTL expiry. `PATCH` can be made idempotent via `If-Match: "etag"` preconditions so retries either succeed once or fail with `412`.

### Question 4: Design This Endpoint and Defend - Paginated List With Filter

Design `GET /tickets` supporting pagination, filtering on `status` and `priority`, sorting by `created_at`, and sparse fieldsets. Write one example request and the response envelope. Defend your pagination choice.

**Calibration:** ```
GET /tickets?filter=status%20eq%20%22open%22%20and%20priority%20eq%20%22P1%22
&order_by=created_at%20desc,id%20asc
&page_size=100
&page_token=<opaque>
&fields=id,subject,status,priority,created_at

Response:

{
"items": [ { "id": "t_1", "subject": "...", "status": "open",
"priority": "P1", "created_at": "..." } ],
"next_page_token": "eyJhZnRl..."
}

Cursor/token pagination defended: offset pagination skips or repeats items when inserts/deletes happen between pages and becomes slow as depth grows (server must scan and discard offset rows). Opaque cursors point at a stable position and let the server change the encoding without breaking clients. id as tiebreaker prevents duplicates across pages when two items share created_at.

Question 5: Breaking vs Non-Breaking​

A response currently returns { "total": 4599 } (integer minor units). Your PM wants to add currency info. Propose a non-breaking change. Then propose a breaking alternative and explain why it is breaking.

Calibration: Non-breaking: add a new field currency at the top level, or add a total_money: { amount_minor: 4599, currency: "USD" } object alongside the existing total field, marking total deprecated. Old clients still read total; new clients use the richer field. Breaking alternative: replace { "total": 4599 } with { "total": { "amount_minor": 4599, "currency": "USD" } }. This changes the type of total from integer to object; any client reading total as a number will crash or silently get wrong data.

Question 6: REST vs gRPC Decision​

A new internal service with 6 methods will be called by 3 other internal teams, all using Go. Pick REST or gRPC and defend.

Calibration: gRPC. Polyglot risk is zero (one language), typed clients eliminate a category of wire bugs, .proto review doubles as contract review, and HTTP caching is irrelevant for internal service-to-service traffic. REST is fine if the team has no prior gRPC operational experience or if the API will eventually be exposed externally; otherwise the gRPC tradeoffs favor it here.

Question 7: GraphQL N+1​

Sketch a GraphQL query whose naive execution triggers N+1, then explain the mitigation.

Calibration: ```graphql { orders(first: 50) { id customer { name } } }


Naive execution: 1 query for `orders`, then 50 separate `customer(id)` queries = 51 total. Mitigation: the root resolver for `orders` fetches rows; the `customer` resolver uses a per-request DataLoader that batches all 50 `customer(id)` lookups within one tick into a single `SELECT * FROM customers WHERE id IN (...)`, producing 2 total queries. Without DataLoader, GraphQL is not production-ready at list scale.

### Question 8: Webhook Delivery Guarantees

A webhook consumer receives `payment.succeeded` events. List the three guarantees they must design around and the client-side mechanism for each.

**Calibration:** (1) At-least-once delivery - events may arrive multiple times; the consumer dedupes using the event `id` against a store of recently-seen IDs. (2) Out-of-order delivery - events may arrive with `refunded` before `succeeded`; the consumer either checks the current authoritative state via a `GET` on the resource or stores `(id, occurred_at)` to decide which to accept. (3) Eventual delivery under failure - consumers must expect retries and signature mismatches; `X-Event-Id`, `X-Timestamp`, and `X-Signature` must be verified on every request, and the consumer returns `2xx` within a few seconds or the producer will retry.

### Question 9: AsyncAPI and Event Compatibility

A team wants to rename `customer_id` to `customerId` in their `orders.paid.v1` event payload. Safe or breaking? What do you do?

**Calibration:** Breaking. Consumers parse the old name and will not find the new one. Correct path: emit both fields for the compatibility window, announce `customer_id` deprecated in the AsyncAPI document, then release `orders.paid.v2` that drops the old name. The event *type* version (`v1` -> `v2`) is the right unit of change, not the topic name. Publish both versions on the topic during the runway, with a sunset date for consumers to switch.

### Question 10: Deprecation Plan

Write a mini deprecation plan for removing `GET /v1/orders/{id}/legacy_status`. Include headers, runway, and the removal response.

**Calibration:** Announce day 0: response includes `Deprecation: @<epoch>` and `Sunset: Wed, 01 Oct 2026 23:59:59 GMT`, plus `Link: <https://docs.example.com/migration/legacy-status>; rel="sunset"`. Runway: 6 months for paid consumers. Month 5: email every caller identified by traffic logs, second `Link` in the docs. After sunset: endpoint returns `410 Gone` with `application/problem+json` body carrying `code: "ENDPOINT_REMOVED"`, `detail: "Use GET /v1/orders/{id} with field mask 'status'"`. Not `404` - that would suggest a wrong URL; `410` tells the client the URL is intentionally gone.

### Question 11: Design This Endpoint and Defend - Bulk Import

Design the endpoint a consumer calls to import 2 million orders from a CSV file. Defend every design decision.

**Calibration:** Not a batch endpoint: too large for one request body. Use file-based import as an LRO.

POST /orders:import { "format": "ndjson.gz", "source": { "bucket": "client-acme", "key": "imports/2026-04.ndjson.gz" }, "id_collision": "fail" }

HTTP/1.1 202 Accepted Location: /operations/op_import_17

GET /operations/op_import_17 -> 200 OK { "done": false, "metadata": { "progress_percent": 37, "processed": 740000 } }


Defense: (1) file-based because request bodies of GB scale are hostile to proxies and resumption; (2) LRO because minutes-to-hours of work do not fit a synchronous HTTP call; (3) NDJSON gzipped because it is row-parseable and cheap over the wire; (4) `id_collision` is an explicit contract decision - consumers must not have to guess whether imports overwrite or skip; (5) Separate `operations/{id}` endpoint for progress because consumers need to observe without assumptions about internal polling cadence.

### Question 12: Versioning Strategy Defense

Your API supports `/v1/` URL versioning. A colleague proposes switching to header-based date versioning "because Stripe does it." Should you?

**Calibration:** Usually no, and not because Stripe's choice is wrong. Switching versioning strategy mid-life is itself a breaking change: every client has to change how they select a version. Only switch if (a) the audience actually needs per-account pinning to a snapshot date, typically payment or long-lived enterprise integrations; (b) you have the server infrastructure to maintain N dated versions behind one URL; (c) you can afford the operational complexity of `Vary: API-Version` caches and date-aware routing. For most APIs, URL path versioning is sufficient, visible in logs and supports gateways trivially, and does not block you from adding a date header later for specific customers.

### Question 13: Status Code Discipline

A validation fails on `POST /orders` because `items` is empty. The team proposes `200 OK { "success": false, "error": "items required" }`. Argue against.

**Calibration:** HTTP-aware infrastructure treats `2xx` as success: client retry libraries, proxies, CDNs, gateways, observability, and monitoring all interpret status codes mechanically. A `200 OK` with an error body defeats every one of them - client libraries return "ok", dashboards show 100% success rate, retry logic does not fire, monitoring does not alert. The correct response is `400 Bad Request` (or `422 Unprocessable Entity`) with `application/problem+json` body carrying a stable `code` like `VALIDATION_FAILED` and per-field errors. The status tells infrastructure and dashboards "this failed"; the body tells the client *why*.

### Question 14: Governance Tradeoff

Your org has 8 teams shipping APIs independently. What is the single cheapest governance step that materially improves consumer experience?

**Calibration:** A one-page style guide with *three* enforceable rules and a Spectral-or-equivalent linter in CI that fails builds on violation. Good first three rules: one error envelope (RFC 7807), one pagination scheme (cursor), and one casing convention. Cheap because it is one page and one config file; material because it eliminates the three inconsistencies consumers feel most. Anything more elaborate (review council, exception templates, multi-page style guides) is worth doing but tends to rot if introduced all at once before there is cultural buy-in.

### Question 15: Design This Endpoint and Defend - Search

A client needs to run complex order queries with up to 20 filter predicates, free-text search, and custom facets. Design the endpoint. Defend against `GET /orders?filter=...`.

**Calibration:** ```
POST /orders:search
Content-Type: application/json

{
"query": "broken laptop",
"filters": { "status": ["paid", "fulfilled"], "total_gte": 10000,
"created_between": ["2026-01-01", "2026-03-31"] },
"facets": ["status", "customer_region"],
"order_by": "relevance desc, created_at desc",
"page_size": 50
}

Defense: (1) POST because the query will not fit in a URL safely - URL length caps vary across proxies (often ~8KB) and URL-encoded JSON is painful to debug; (2) GET with a request body works on some servers but is formally allowed to be ignored by others, so it is unsafe across infrastructure; (3) :search as a custom method makes it clearly not a list-all (GET /orders) and signals the richer contract; (4) facets and complex filter objects are harder to express in query-string form; (5) idempotency is trivially preserved because searches are naturally idempotent, but marking POST still triggers the right retry semantics in most clients.

Prior Module Question 1 (S7M1: Architecture quality attributes)​

Name four architecture quality attributes and relate each to one API design decision in this module.

Calibration: Reliability -> idempotency keys; availability -> LRO and async patterns replacing long synchronous calls; scalability -> cursor pagination and file-based import; maintainability -> contract-first versioning and deprecation discipline. Quality attributes are not separate from API design - every API decision is a quality-attribute decision in disguise.

Prior Module Question 2 (S7M3: Bounded contexts)​

How does a bounded context inform where API boundaries go?

Calibration: A bounded context defines a consistent model and ubiquitous language within a domain. API boundaries should align with bounded contexts: each service owns one context, and integration between services happens via a deliberate contract (Published Language or Open Host Service). Putting two bounded contexts behind one API mixes languages and forces translation into clients; putting one bounded context across two APIs splits the model and invites inconsistency.

Prior Module Question 3 (S6M3: Consistency)​

How does CAP theorem affect the ordering guarantees you can offer in a webhook feed?

Calibration: A distributed producer that partitions for scale cannot offer global ordering without sacrificing availability; realistic systems offer per-key ordering at best. Webhook contracts must document this: "events ordered within a given subject, not across subjects." Consumers that need a total order must reconstruct state from the resource snapshot rather than trust event ordering alone.

Prior Module Question 4 (S6M4: Transactions)​

A consumer calls POST /orders, the request succeeds, and the client's connection drops before the 201 arrives. What should the client do and what should the contract promise?

Calibration: The client should retry with the same Idempotency-Key. The contract promises that a repeat with the same key and body returns the previously-produced response (the same Order), not a duplicate creation. This is the HTTP-level analog of transactional exactly-once: exactly-once observable behavior even though the network made the call at-least-once. Without this promise, retries produce duplicate orders.

Prior Module Question 5 (S7M2: Modular monolith vs microservices)​

You are splitting a modular monolith into two services. What does API design owe the split?

Calibration: A written contract at the new boundary, with the compatibility and versioning discipline of Cluster 5. Inside a monolith, modules can be refactored together; across a service boundary, the contract survives refactors and must evolve with deprecation. The API design decides how much coupling remains (a chatty REST API keeps the services coupled; a coarse-grained one with events decouples them) and how much latency and failure is exposed.