“Design an API for a file-processing service” sounds smaller than “design a distributed system.” It is not. The endpoint list may fit on a whiteboard, but every line commits future clients to behavior: whether a retry creates a duplicate, whether two edits overwrite each other, whether pagination skips records, whether errors can be handled by code, and whether a migration can happen without forcing every consumer to upgrade on Tuesday.

That is why API design has become such a useful senior-engineering interview. In CoderPad’s report of more than 650 global participants, 38% of recruiters surveyed reported using real-world scenario simulations, equal to the share using system-design exercises. The same report says 66% of respondents who permit AI consider catching and fixing its mistakes evidence of real skill. A contract exercise exposes exactly that kind of judgment: syntax is cheap; consequences are not.

Workflow → contract → failure → evolutionA staff-level answer connects what a user needs to do with the promises the service makes, the ways those promises can fail, and the path for changing them safely.

This guide uses HTTP and JSON because they make the tradeoffs concrete, not because every API should be REST. In an interview, state your assumptions, choose a coherent style, and spend your time on invariants. The strongest answer is not the one with the most endpoints. It is the one whose clients can tell what happened.

Why this is really a contract-evolution interview

Junior answers often start with routes. Senior answers start with promises. Staff answers also ask who owns each promise, how it will be observed, and how it can evolve. That difference matters because an API sits between release schedules. The server can change hourly; a mobile client may remain installed for years; an enterprise integration may be updated only after a procurement cycle.

Frame four categories before drawing. Functional behavior says what users can accomplish. Data invariants say what must never become ambiguous or invalid. Operational constraints cover latency, availability, quotas, and recovery. Evolution constraints cover old clients, new fields, deprecation, and migrations. The IETF guidance for building protocols with HTTP makes a similar point at the wire level: applications should reuse HTTP semantics instead of fighting them, while preserving room for the underlying protocol to evolve.

Say the contract aloud: “A client submits one processing job, can safely retry if the response is lost, observes an immutable result or a terminal failure, and can list its own jobs in a stable order.” Now every design choice has a test. If your endpoint list cannot uphold that sentence during timeout, concurrency, or deployment, it is incomplete.

Start with workflows, then name resources and boundaries

Ask who calls the API, what they are trying to complete, and which step needs a durable identity. For a document-processing service, the core nouns might be documents, processing jobs, and results. A job deserves its own resource because it has a lifecycle and can outlive the request that created it. “Process” should not become a vague endpoint that blocks until every downstream system finishes.

Google’s resource-oriented design guidance recommends named resources and a small set of standard methods; it also warns against simply mirroring a database schema. That is a useful interview discipline. Tables are implementation details. API resources should reflect stable concepts that clients understand.

Define ownership next. Is a document globally addressable, organization-scoped, or user-scoped? Can a result exist independently of a job? Who may cancel a job? Resource names should make boundaries visible: /v1/organizations/{org}/jobs/{job} carries more authorization context than /v1/jobs/{job}, although the server must still enforce access rather than trust the path.

End the section with three workflows, not twenty routes: create a job, inspect or cancel it, and list recent jobs. Mention the unhappy paths—duplicate submission, invalid input, lost response, partial downstream failure—because they will shape the contract more than the happy-path JSON.

Use HTTP semantics deliberately: safety, idempotency, and caching

HTTP already distinguishes safe methods from idempotent ones. Under RFC 9110, GET, HEAD, OPTIONS, and TRACE are safe; PUT, DELETE, and safe methods are idempotent. POST is not automatically idempotent. This vocabulary lets you explain retry behavior precisely instead of saying “the client can probably try again.”

Use POST when the server assigns an identity or executes a command whose target is not known in advance. Use PUT when the client knows the resource URI and replacing that state repeatedly has the same intended effect. Do not contort an action into GET just to make it convenient: safe methods are expected not to request a state change, and infrastructure may prefetch or cache them.

Caching is also contractual. RFC 9111 defines freshness, validators, conditional requests, and Vary. In an interview, you need not recite the RFC. Explain whether a response is private or shared, how long it can be stale, and which validator allows cheap revalidation. A job list may need Cache-Control: private, no-cache; an immutable result artifact may be cacheable for much longer.

OperationMethod and contractFailure question
Create processing jobPOST with idempotency keyDid a timed-out request create a job?
Read jobGET with validatorHow stale may status be?
Replace client-owned configPUT plus preconditionCould a retry or stale writer corrupt state?
Cancel jobPOST command or state transitionWhat if processing already completed?

Design the contract before the implementation

Sketch one request and one response before naming internal services. A machine-readable description forces decisions about required fields, nullability, enumerations, identifiers, and error shapes. The OpenAPI Specification can describe paths, parameters, responses, security schemes, and schemas; JSON Schema 2020-12 provides reusable structural constraints for JSON documents.

Contract-first does not mean designing a perfect document before learning anything. It means treating ambiguity as a decision. Is completed_at absent, null, or an empty string before completion? Can a status move backward? Are unknown enum values tolerated by clients? Does omission mean “unchanged” in a patch, or “clear this field”? Pick rules and show how you would test them.

Use stable external identifiers and avoid leaking storage layout. Prefer strings when clients must not perform arithmetic or infer sharding. Treat timestamps as instants with an explicit offset. Make money a currency plus integer minor units or a documented decimal representation. For important boundaries, show an example payload and the invariant it preserves.

GitHub publishes both OpenAPI 3.0 and 3.1 descriptions for validation and client generation. That production example supports a useful staff-level point: a schema is not merely documentation. It can become a testable artifact in the delivery pipeline.

Make collections scale: pagination, filtering, and stable order

Never return “all jobs” because the sample tenant has twelve. Google’s pagination guidance calls adding pagination later backward-incompatible. Define it at launch, keep tokens opaque, and remember that a page token is continuation state—not proof that the caller may access the next page.

Cursor pagination usually behaves better than offsets while rows are inserted or deleted. Define a stable total order such as created_at DESC, id DESC; the second key breaks ties. Encode the last observed keys and query context into an opaque token, sign or authenticate it if clients could tamper with it, and re-run authorization on every request. Stripe’s v1 API demonstrates cursor navigation with mutually exclusive starting_after and ending_before. GitHub uses standardized Link headers and endpoint-specific page sizes.

Filtering and sorting are product commitments. A structured filter string can evolve without adding one parameter per field, as described in Google AIP-160, but it requires validation and query-cost limits. Do not promise arbitrary sort combinations your indexes cannot support. State which filters are indexed, which order is default, and whether results represent a snapshot or a moving collection.

Practice the questions behind the endpoints

Interview Copilot helps senior and staff engineers rehearse API, system-design, and architecture follow-ups until retry, consistency, and migration tradeoffs are easy to explain.

Create a free account

Prevent duplicate work and lost updates

A client sends POST, the server commits, and the network drops before the response arrives. “Retry the request” is unsafe unless the contract can distinguish the same intended operation from a new one. Accept an idempotency key, bind it to the caller and normalized request, store the outcome atomically with the mutation, and return the same logical result for a replay. Reject reuse of the key with different parameters.

Stripe documents a concrete version of this design: its v1 API stores the first result for an idempotency key, including a 500 response, and accepts keys up to 255 characters. Its advanced error guidance explains why a timed-out mutation has an indeterminate outcome and why the same key establishes a definitive result. Amazon’s Builders’ Library likewise uses caller-provided request identifiers to distinguish retries from separate intent.

Concurrent updates create a different ambiguity. If two operators edit the same job policy, last-write-wins can silently erase one change. Return an ETag, require If-Match on a mutation, and reject a stale version. Google’s freshness-validation guidance describes this optimistic-concurrency pattern, while RFC 6585 defines 428 Precondition Required specifically to prevent lost updates.

Model long-running work without holding a connection open

If processing can outlast a normal request budget, return a durable operation or job resource. A typical response is 202 Accepted with a location clients can poll. The resource needs an explicit state machine—queued, running, succeeded, failed, canceled—and terminal states should carry either a result link or a structured error.

Google’s long-running-operation guidance uses roughly ten seconds as a rule of thumb for considering an operation resource. The exact threshold is yours; the key is that clients no longer confuse an HTTP timeout with business failure. Say how cancellation races with completion, whether progress is approximate, how long records are retained, and whether a webhook or event can replace aggressive polling.

A staff answer also separates acceptance from execution. Validate cheap syntax and authorization before enqueueing. Persist intent before acknowledging it. Ensure workers can claim jobs safely, retry steps without duplicating side effects, and surface poison work rather than loop forever. If the downstream processor is unavailable, the API should still tell the caller whether the job was accepted and who now owns recovery.

Design errors, limits, retries, and overload as one system

Status codes are the start of error design, not the end. RFC 9457 Problem Details standardizes machine-readable fields such as type, title, status, and detail. Add a stable application code, request identifier, field-level validation details when safe, and documentation link. Keep human prose useful, but never force clients to parse it.

Classify failures by caller action: fix the request, authenticate, wait, resolve a conflict, or contact support. A 429 response can include Retry-After; a 409 or 412 can represent a stale write; a 503 can indicate temporary unavailability. Google’s error guidance emphasizes structured details because shared clients cannot build reliable behavior around unstable messages.

Retries consume capacity. Amazon calls them “selfish” load and recommends designing timeouts, capped exponential backoff, jitter, and retry limits together. Google SRE illustrates the amplification: three attempts per layer can approach three times the original traffic, while a 10% retry budget holds the example near 1.1 times. State which errors are transient, which methods are safe to retry, and where the retry budget lives.

Use real limits to make the design concrete. GitHub documents 60 unauthenticated and 5,000 authenticated requests per hour, plus a 100-concurrent-request secondary limit. Stripe documents a 100-operations-per-second live-mode global limit alongside endpoint and concurrency limiters. Your numbers will differ, but the categories should not: rate, concurrency, payload size, expensive operation count, and tenant fairness.

Plan compatibility and versioning before the first client ships

Say what counts as compatible. Adding an optional response field is usually safe only if clients ignore unknown fields. Adding a required request field is breaking. Renaming an enum value, changing default ordering, narrowing a number range, or repurposing null can break consumers without changing the URL. Write these rules into contract tests.

Prefer additive evolution: new optional fields, new resources, or new operations. When behavior must change, choose a version policy and a migration window. Stripe separates major releases with breaking changes from monthly backward-compatible releases. GitHub uses date-based versions and commits to support a previous version for at least 24 months after a newer one ships. These are examples of explicit contracts, not defaults to copy blindly.

A staff candidate describes the migration mechanism: measure version usage, publish a changelog and deadline, offer a compatibility mode or dual-read path, canary the new behavior, and contact owners of remaining traffic. Do not claim that putting /v1 in a URL solves evolution. A version label without compatibility rules, telemetry, and retirement ownership is decoration.

Build authorization into every resource boundary

Authentication answers who the caller is. Authorization answers whether that caller may perform this action on this object. OWASP ranks broken object-level authorization first in its 2023 API Security Top 10 and recommends checks on every endpoint that receives an object identifier. “The ID is hard to guess” is not a control.

Describe the policy in resource terms: a user acts within an organization; a service principal receives a limited scope; administrators have separately audited powers. Enforce the object check after lookup or through a query that is already tenant-scoped. Page tokens, webhooks, and export URLs must not bypass the same boundary.

For OAuth, follow current profiles rather than remembered snippets. RFC 9700 updates OAuth 2.0 security practice, including defenses such as PKCE and deprecation of unsafe patterns. Bearer-token guidance requires TLS and warns against putting tokens in page URLs. Validate issuer, audience, expiry, and allowed algorithms; scope credentials narrowly; redact secrets from logs.

NIST’s Secure Software Development Framework recommends peer review and analysis against secure-coding standards, with findings triaged in the team’s workflow. In an interview, translate that into controls: schema validation, dependency and static analysis, threat-focused review, abuse tests, and a named owner for remediation.

Make the API operable: request IDs, traces, and retry visibility

Before declaring the design done, explain how an operator answers: What is failing? For whom? Since when? At which dependency? Did retries help or amplify the incident? Return a request ID, propagate context across services, and log the stable operation and error code without recording tokens or sensitive bodies.

The W3C Trace Context recommendation standardizes traceparent and tracestate across vendors. OpenTelemetry’s HTTP semantic conventions define shared attributes for spans and metrics. Mentioning them is useful only if you connect them to a debugging question: trace a job from acceptance through the queue and processor, correlate its attempts, and preserve the original request identity.

Choose service-level indicators tied to the contract: accepted-request availability, latency by operation, terminal-job success rate, queue age, duplicate-suppression hits, stale-write conflicts, retry volume, and webhook delivery lag. Track quotas by tenant and operation. Alert on symptoms users feel and on exhaustion signals that predict them.

Finally, connect rollout to compatibility. Deploy additive storage and readers first, dual-write only when its failure semantics are understood, expose the new field behind a controlled path, and verify both old and new clients. The API design interview is complete when you can explain not just the steady-state diagram, but the next change and the rollback from it.

A 45-minute senior/staff API design interview walkthrough

Minutes 0–5: clarify. Name callers, workflows, scale, data sensitivity, latency expectations, and consistency requirements. State assumptions instead of silently inventing them. Write one sentence describing the contract.

Minutes 5–12: model resources. Identify durable nouns, ownership boundaries, IDs, lifecycle states, and the three central operations. Sketch request and response shapes. Explain why the model is not a copy of the database.

Minutes 12–20: cover reads and collections. Define stable ordering, cursor pagination, filters, cache behavior, and freshness. Show one page token flow and repeat the authorization check.

Minutes 20–29: cover writes and concurrency. Walk through the lost-response case using an idempotency key. Walk through two writers using an ETag and precondition. Define the long-running operation state machine and cancellation race.

Minutes 29–36: design failure. Give one validation error, one conflict, one throttle, and one transient failure in a consistent problem format. Add timeouts, bounded backoff with jitter, retry eligibility, and tenant-aware limits.

Minutes 36–41: secure and operate. State authentication, object authorization, data-minimization, request tracing, core metrics, and what cannot enter logs. Name one abuse case and one dependency failure.

Minutes 41–45: evolve. Add a requirement—perhaps batch processing or a new status—without breaking an old client. Describe contract tests, a canary, adoption telemetry, rollback, and deprecation ownership. Close with the largest unresolved tradeoff.

Staff-level scorecardCan a client distinguish accepted, rejected, duplicated, conflicted, throttled, and indeterminate work? Can an operator trace it? Can the next version ship without a coordinated flag day?

Practice by changing one assumption at a time: ten jobs per day becomes ten thousand per second; one trusted internal caller becomes third-party developers; synchronous work becomes a minutes-long pipeline; one region becomes several. Do not redesign everything immediately. Identify which promise breaks first and evolve the contract around that pressure.

Turn API tradeoffs into a clear interview narrative

Use Interview Copilot to practice a realistic API design round, generate staff-level follow-up questions, and tighten the places where your contract becomes ambiguous under failure.

Start practicing free