Prompt and Scope
A B2B analytics platform needs a report-export API. One export takes 5 minutes to 3 hours and can produce a file as large as 20 GiB. The service may receive 10,000 submissions around 09:00 each day. Callers include browsers and services operated by other companies. The API gateway closes a synchronous request after 30 seconds, so callers may retry when they do not receive a response.
Users must see whether work is queued, running, successful, or failed. They also need to request cancellation while it is possible and download a successful result. The durations, file size, peak, and timeout are interview-case assumptions, not industry thresholds. Queue partitioning is outside the center of this question. The core task is to define an asynchronous HTTP contract that remains understandable through retries, process crashes, and state races.
RFC 9110 says that 202 Accepted means a request has been accepted for processing but processing is incomplete and might never occur. The response ought to describe the current status and point to a status monitor. A current senior REST API interview guide also asks candidates to design long-running operations, including status, failure, cancellation, and idempotency. The question fits backend engineers, API platform engineers, and senior full-stack engineers who design service contracts, so its core category is backend.
What the Interviewer Evaluates
First, does the candidate understand the boundary of 202? It does not mean “the background job will succeed,” and unfinished work is not a final 200 OK result. A strong answer rejects immediately detectable invalid requests synchronously, then returns a stable operation resource after durable acceptance and exposes execution outcome as later state.
Second, can the candidate model long-running work as a resource rather than return only a queue message ID? An operation needs a tenant owner, request fingerprint, state, progress, result or error, version, creation time, and expiration. Its state machine must define permitted transitions, terminal states, and cancellation races.
Third, is there a gap in which the server has returned 202 but the work is never enqueued? The operation record and work-to-publish should be persisted in one transaction, then an outbox publisher can enqueue it. Consumers and execution stages still need idempotency because at-least-once delivery, worker crashes, and lease takeover can duplicate work.
Fourth, can the client contract survive real traffic? Location tells a client where to inspect status. Retry-After, backoff, jitter, and conditional requests control polling. Signed webhooks can notify server-to-server callers, and SSE can update a browser, but neither replaces a queryable operation resource for recovery.
Finally, the interviewer should hear a security and verification story. An unguessable ID is not object-level authorization, and a result URL must not bypass tenant boundaries. A strong answer tests lost acceptance responses, duplicate submissions, publication failure, worker crashes, cancellation-completion races, polling bursts, and expiration cleanup.
Questions to Clarify First
- What must be validated synchronously? Identity, tenant permission, request shape, input existence, and obvious quota violations should be checked before acceptance. If data completeness is knowable only after a multi-hour scan, that is an asynchronous execution failure, not a promise made at acceptance.
- Does a duplicate request mean a retry or a second independent operation? When callers provide an
Idempotency-Key, the same tenant, endpoint, key, and request fingerprint should replay one operation. A caller that intentionally needs two identical exports must use two keys. - Is progress measurable? If the total number of partitions is known, report completed and total units. If it is not estimable, report the phase and last heartbeat instead of inventing a percentage that drifts to 99%.
- What kind of resource is the result? A small result can be embedded in the operation response. A large file should be a separate protected resource. This case uses short-lived download credentials and separate retention periods for operation metadata, result objects, and download credentials.
- What does cancellation promise? Does it stop only future work, or must it undo side effects already committed? If steps are irreversible, the contract must define best-effort cancellation, compensation, partial output, and the possible final states.
- Which notification channels can callers receive? Browsers usually cannot host callback endpoints, so polling or SSE fits. Server-to-server callers can use webhooks. Network constraints and latency goals change the notification channel, but the operation resource remains the source of truth.
- Can parallel operations conflict? Can one report configuration be exported concurrently? What happens when that configuration is updated or deleted during execution? The answer determines whether to serialize, snapshot inputs, reject conflicts, or let the old version finish.
- How long is state retained? The case retains terminal operations for 7 days, result objects for 24 hours, and each download credential for 15 minutes. These are product-contract choices that should change with audit needs, cost, and the ability to regenerate results.
30-Second Answer Framework
“I would separate execution from the HTTP request, but I would not return only a job ID. The submission endpoint validates authorization and immediately detectable errors, then writes an operation and an outbox record in one transaction. Once committed, it returns 202, Location, and a suggested polling interval. An authorized operation resource exposes stable state, real progress, structured errors, and the result link. The same idempotency key and request replay the same operation. Workers process by operation ID idempotently, while version conditions protect state transitions. Polling uses Retry-After, backoff, and jitter; server callers can add signed webhooks; cancellation enters cancel_requested and resolves its race with completion. I would then inject lost responses, duplicate messages, worker crashes, cancellation races, and expiration to prove there are no ghost jobs, duplicate visible results, or unauthorized downloads.”
Step-by-Step Deep Dive
First, define two resources. An export request expresses the result the user wants to create, while an operation resource represents the lifecycle of this execution. Submission can be POST /v1/report-exports, and status can be GET /v1/report-operations/{operation_id}. The acceptance response can be:
HTTP/1.1 202 Accepted
Location: /v1/report-operations/op_7f3a
Retry-After: 5
Content-Type: application/json
{
"id": "op_7f3a",
"status": "queued",
"statusUrl": "/v1/report-operations/op_7f3a",
"cancelUrl": "/v1/report-operations/op_7f3a"
}202 promises only that processing was accepted. Reject a malformed request, unauthorized caller, or nonexistent input with the appropriate 4xx and do not create an operation. Persist a business failure that requires expensive computation in the operation resource. Location and Retry-After are part of this API's client contract; RFC 9110 does not require every 202 response to use both headers.
Second, define the operation record and state machine. A minimum record contains id, tenant_id, idempotency_key, request_fingerprint, status, progress, a result reference, a structured error, version, creation and update timestamps, and expires_at. A recommended transition set is:
queued -> running -> succeeded
-> failed
queued -> cancel_requested -> canceled
running -> cancel_requested -> canceled | succeeded | failedCancellation and completion can race, so cancel_requested is not terminal. A worker commits a result with a conditional update over version and an allowed prior state; only one transition wins. If a side effect is already irreversible, cancellation might eventually become succeeded or failed. Do not fabricate canceled merely to match the button label. A status representation can look like this:
{
"id": "op_7f3a",
"status": "running",
"progress": {
"completedUnits": 37,
"totalUnits": 100
},
"result": null,
"error": null,
"lastUpdatedAt": "2026-07-18T23:18:11Z",
"expiresAt": "2026-07-25T23:08:11Z"
}Return this progress only when work units have a real denominator. A failed execution can still return 200 when the operation itself is read successfully, with failure represented by terminal state and a structured error: the status read succeeded while the represented execution failed. A team that instead maps execution failure to a 4xx from the status endpoint must use that convention consistently across every SDK rather than mix both meanings.
Third, make acceptance, retries, and execution correct. Put a unique constraint on (tenant_id, route, idempotency_key) and store a fingerprint of the canonical request. The same key and fingerprint return the existing operation and current state. The same key with a different fingerprint returns an explicit conflict, preventing accidental key reuse for another report. Retain the idempotency record at least as long as clients can legitimately retry and coordinate it with operation retention.
Insert the operation and outbox event in one database transaction, then return 202. A separate publisher sends the outbox event to the queue and may send it more than once. Consumers deduplicate by operation ID. Each execution stage also needs idempotent writes or a fencing token so that a worker crash after an external write, followed by takeover, does not produce two visible results. The API answer needs to prove the acceptance-to-queue boundary; it does not need to reproduce a complete scheduler design.
Fourth, control status and notification traffic. Initial and later status responses provide a reasonable Retry-After. Clients use exponential backoff with a cap and jitter, while the server supports ETag and conditional requests to avoid resending an unchanged body. If status reads exceed quota, return rate-limit information instead of allowing 10,000 callers to poll every second.
A browser that needs low-latency progress can subscribe to SSE and still query by operation ID after disconnecting. A server caller can register a signed webhook; the sender retries, and the receiver deduplicates. Both push channels can be lost, delayed, or duplicated, so the operation resource remains the recovery and reconciliation source of truth. On success, the operation body can link to the result. If the API instead redirects to a distinct result resource, document the 303 semantics and verify that SDKs do not replay the original POST at the result location.
Fifth, handle authorization, cancellation, and retention. Every status read, cancellation, and result retrieval performs object-level authorization over tenant_id, caller identity, and operation permission. Random IDs make enumeration harder but are not authorization. The download service verifies result ownership again, then issues the 15-minute credential selected for this case. The operation response never stores a long-lived public URL.
DELETE /v1/report-operations/{id} can express a cancellation request. If cancellation is possible, return the current cancel_requested representation to show it was accepted. If it is impossible or the operation is already terminal, return a stable response that is safe to retry. Workers inspect the cancellation marker at stage boundaries, skip future steps, and remove temporary objects. Already committed external effects follow a predefined compensation rule. Delete a terminal operation after 7 days. A known expired ID can return 410 Gone, while an unknown or unauthorized ID can return 404 according to the disclosure policy.
Sixth, verify failures rather than only the happy path. Cover at least these cases: the server commits but loses the 202 response, and a retry can retrieve only the same operation; the outbox publisher crashes before or after sending, and work eventually exists with one visible result; a worker loses its acknowledgement after writing the result, and its successor cannot overwrite the terminal state; cancellation and completion arrive together, and only one legal terminal state appears; unchanged status polling follows backoff and conditional requests; cross-tenant status, cancellation, and download all fail; metadata, results, and idempotency keys expire according to the contract.
The reusable decision rule is: 202 solves connection waiting, the operation resource solves observability, and atomic acceptance plus an idempotent state machine solves correctness.
High-Quality Sample Answer
“I would first separate acceptance success from execution success. The operation can take 3 hours and cannot occupy a gateway connection that lasts 30 seconds. POST /v1/report-exports therefore checks identity, tenant permission, request shape, input existence, and obvious quota violations. It then creates the operation and outbox record in one transaction and returns 202 only after commit. The response includes Location for the operation resource and Retry-After for the first status read. A 202 does not promise that the report will succeed.
The operation stores tenant ownership, idempotency key, request fingerprint, state, verifiable progress, result or error, version, and expiration. State moves from queued to running and then to succeeded or failed. Cancellation first enters cancel_requested because the worker might be committing a result at the same time. Under the same tenant and endpoint, the same idempotency key and request return one operation; the same key with a different request is a conflict. A lost 202 response therefore cannot create a second report when the client retries.
I assume at-least-once queue delivery. The outbox may publish twice, consumers deduplicate on operation ID, and external writes in each stage are idempotent or fenced. State updates include version conditions so a stale worker cannot overwrite the result after its lease is taken over. A failure before acceptance returns a 4xx immediately. A failure during execution is stored as terminal state and a structured error, which lets clients distinguish network failure, status-read failure, and report-execution failure.
Clients poll according to Retry-After with exponential backoff and jitter, and the status endpoint supports ETag. A browser can use SSE for live progress and a partner service can use a signed webhook, but both recover through the operation resource after a disconnect or duplicate notification. The result file never has a public URL. The download endpoint authorizes again and issues a 15-minute credential. In this case, terminal operations live for 7 days and files for 24 hours, and those expiration rules are public contract.
Finally, I would test a response lost after commit, repeated outbox delivery, a worker crash after writing a result, a cancellation-completion race, 10,000 callers polling together, and cross-tenant access. Passing means more than completing once in the background: these failures must not create ghost jobs, duplicate visible results, illegal transitions, or unauthorized downloads.”
Common Mistakes
- Starting an in-memory thread after returning 202 → a process restart leaves work that can never be found, and acceptance is not atomic with launch → persist the operation and outbox before acknowledging acceptance.
- Treating 202 as final success → HTTP explicitly allows processing never to occur or to fail → expose final result, errors, and a status monitor in the later contract.
- Returning only a queue message ID → it lacks tenant ownership, stable state, errors, results, and retention → create a separate authorized operation resource.
- Polling once per second forever → a submission peak becomes a sustained read peak → provide Retry-After and use backoff, jitter, ETag, and quotas.
- Using a random operation ID as authorization → a leaked log, browser history entry, or internal link still grants cross-tenant access → authorize every status read, cancellation, and result retrieval.
- Keeping an idempotency key only in a short Redis lock → lock expiration, crashes, and result replay can still create duplicate operations → use a durable uniqueness constraint, request fingerprint, and replayable response.
- Marking canceled as soon as the user clicks → the worker might already have committed an irreversible effect → enter cancel_requested first and let conditional transitions plus compensation determine a legal terminal state.
- Always inventing a percentage → unpredictable phases stall at 99% and mislead callers → report completed units when measurable, otherwise report phase and update time.
- Deleting the operation after a webhook succeeds → the notification can be lost, duplicated, or accepted by a temporarily broken receiver → retain the operation as a time-bounded recovery source of truth.
Follow-Up Questions
Follow-up 1: The database transaction committed, but the 202 response was lost. What happens when the client submits again?
The client reuses the original Idempotency-Key. The server locates the operation by tenant, endpoint, and key, confirms that the request fingerprint matches, and replays the current representation and Location without inserting another operation or outbox record. If the key matches but the request differs, return a conflict and require a new key. The test must prove that there is one operation row and one visible result even if queue delivery is duplicated.
Follow-up 2: The operation is 90% complete. Cancellation and result commit arrive together. Which state wins?
Define legal transitions in advance and use a version-conditional update to select one winner. If the result transaction commits running to succeeded first, the later cancellation reads and returns the terminal succeeded state. If cancellation reaches cancel_requested first, the worker checks whether completion is still permitted before commit. An irreversible stage can make cancel_requested legally end in succeeded or failed; the contract cannot promise absolute rollback.
Follow-up 3: Progress is not estimable, but product insists on a percentage. What do you return?
Explain that a fabricated percentage creates a false expectation. Show completed phases, the current phase, the last heartbeat, and a nonbinding range derived from historical runs. Return completedUnits / totalUnits only when the total workload is stable. If individual phases are measurable, show progress within each phase instead of averaging phases with different costs.
Follow-up 4: A partner refuses to poll. Should the API expose only a webhook?
A webhook can reduce normal-path latency and reads, but it cannot be the only recovery mechanism. Callbacks encounter DNS, certificate, firewall, signing-key rotation, duplicate, and ordering failures. Sign and retry webhook delivery, include operation ID and version, and require receiver deduplication. The partner can reconcile through the operation resource after a missed event. Browsers without a stable callback endpoint continue to use polling or SSE.
Follow-up 5: One report produces a 20 GiB file. Should the operation API return a download URL directly?
The operation should return a result-resource reference. Authorize tenant and caller again before issuing the case's 15-minute download credential. Do not persist a long-lived object-store URL in the operation. The file lives for 24 hours while operation metadata lives for 7 days, so after file expiration the operation can still say that execution succeeded, the artifact expired, and regeneration is available.
Follow-up 6: Status-query traffic grows larger than execution traffic. What do you change first?
First confirm that clients obey Retry-After, exponential backoff, a cap, and jitter. Then enable ETag-based conditional requests, tenant quotas, and rate limiting. Browsers that need lower latency can consolidate updates through SSE, and server callers can use webhooks, while low-frequency status reads remain available. Jitter the suggested interval as well so the 09:00 submission peak does not become a synchronized periodic peak on the status endpoint.