Module Quiz
Complete this quiz after finishing all concept and practice pages.
Current Module Questions
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.
Answer: 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}.
Answer:
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.
Answer: 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.
Answer:
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.
Answer: 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.
Answer: 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.
Answer:
{ 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.
Answer: (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?
Answer: 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.
Answer: 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.
Answer: 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?
Answer: 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.
Answer: 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?
Answer: 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=....
Answer:
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.
Interleaved Review Questions
Prior Module Question 1 (S7M1: Architecture quality attributes)
Name four architecture quality attributes and relate each to one API design decision in this module.
Answer: 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?
Answer: 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?
Answer: 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?
Answer: 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?
Answer: 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.
Self-Assessment and Remediation
Mastery Level (90-100% correct): Ready to advance to Module 5 with confidence.
Proficient Level (75-89% correct): Review the missed concept pages and redo Practice 1 (REST workshop) or Practice 3 (evolution lab), whichever matches the weakness.
Developing Level (60-74% correct): Rework Clusters 2 and 5 from the concept pages; redo Katas 1 and 4. Expect the weakness to be in verb/status discipline or compatibility classification.
Insufficient Level (<60% correct): Restart from Cluster 1 Concept 1. The most likely issue is treating the API as "the code" rather than "the contract." Until "what is the contract here?" is a reflex question, the rest of the module does not stick.