Senior REST API Developer Interview Questions: Advanced Topics and Answers

Senior REST API developer interview questions and answers, from HTTP caching and ETags to idempotency keys, versioning, RFC 9457 errors and rate limiting.
标签
作者
GreatFrontEnd Team
36 分钟阅读
Sep 8, 2026
Senior REST API Developer Interview Questions: Advanced Topics and Answers

Senior REST API developer interview questions rarely test whether you can name the HTTP verbs or explain what a status code is. At senior level, and for developers with around 3 years of experience who are being tested on the same material, the questions shift to judgment: how you version an API without breaking every existing client, what actually makes a request safe to retry, how you structure an error response so a caller can act on it, what the browser and the network do with your responses, and when REST is the wrong choice entirely. This guide puts both layers in one place, 19 questions with a worked answer for each. It replaces having separate pages for 3 years and senior level, because the questions overlap far more than they differ.

What separates senior from mid-level REST API candidates

A mid-level candidate can build CRUD endpoints, return the right status codes, and explain what idempotent means in the dictionary sense. A senior candidate is expected to make real trade-off calls, URI versioning versus header versioning, cursor pagination versus offset, when to add HATEOAS versus when it is not worth the complexity, and to know which parts of "REST best practice" are settled convention versus still-evolving standards.

How to use this guide: Questions 1 to 8 are the design-judgment layer, which separates senior from mid-level answers. Questions 9 to 19 are the HTTP behavior that any developer with about 3 years of experience should be able to apply without being told: caching, conditional requests, patch formats, long-running work, cancellation, uploads, batches, optimistic updates, live updates, non-CRUD actions and JSON pitfalls. Each of those answers is tied to the RFC, MDN page or guideline it comes from. For definitions and everyday scenarios below this level, see GreatFrontEnd's REST API interview questions for frontend developers.

Question 1: How do you make a POST request safe to retry after a network failure?

How to approach it

The scenario: a client sends a payment or order-creation request, the server processes it successfully, but the network drops before the response reaches the client. The client, seeing no response, retries. Without protection, that retry creates a second charge or a second order.

This is where idempotency actually matters in production, not as a dictionary definition. PUT and DELETE are idempotent by the HTTP spec: calling them multiple times with the same input produces the same end state as calling them once. POST is not, by definition, each call is generally treated as creating a new resource or triggering a new action. PATCH's idempotency depends entirely on how it is implemented, a PATCH that sets a field to an absolute value is idempotent, one that increments a counter is not. Knowing this classification is table stakes. The senior-level answer is what you do about the POST case specifically: the established pattern, popularized by Stripe, is an idempotency key: the client generates a unique key per logical operation and sends it in a header, the server stores the key alongside the result of the first request once processing begins, and subsequent retries with the same key return that stored result instead of executing the operation again. Depending on the implementation, this can include failed responses as well as successful ones, which prevents a retry from accidentally performing the side effect twice.

POST /v1/charges HTTP/1.1
Host: api.example.com
Idempotency-Key: 8f14e45f-ceea-4d7a-9c3f-2a4b1e3d5c6a
Content-Type: application/json
{ "amount": 2000, "currency": "usd", "customer": "cus_9s6XKzkNRiz8i3" }
If the network drops the response and the client retries with the same Idempotency-Key, the server recognizes the key, skips reprocessing, and returns the original response instead of creating a second charge. This pattern has also been discussed by the IETF through draft-ietf-httpapi-idempotency-key-header, which proposed a standardized Idempotency-Key header field. As of this guide's publication, that Internet-Draft has expired without becoming a finalized RFC, so candidates should treat idempotency keys as an established industry pattern rather than a standardized HTTP header defined by a final RFC.

Question 2: How do you version a REST API, and what are the real trade-offs?

How to approach it

There is no single correct answer here, which is exactly why it is a senior-level question. URI versioning (/v1/users, /v2/users) remains a common pragmatic choice for public APIs because it is simple to route, easy for a client to pin, and visible directly in the URL. Twilio is one example of an API that includes version information in its request paths, while GitHub takes a different approach and versions its REST API through the X-GitHub-Api-Version request header. Header or media-type versioning (an Accept header naming a version) keeps URLs stable but is harder for a client to discover and test from a browser, and adds a routing layer that has to inspect headers rather than the path. Stripe uses date-based API versions, with accounts pinned to a particular API version unless a request explicitly overrides it. Its current versioning model also groups versions into named major releases, allowing backwards-compatible changes within a release while reserving breaking changes for new major releases.

A fourth option, increasingly discussed for internal APIs, is not versioning at all: designing every change to be strictly additive (new optional fields, new endpoints) so existing clients never break, and reserving versioning for genuinely breaking changes. The senior-level answer names the trade-off explicitly: URI versioning is simplest to reason about but forces a visible major-version jump for every breaking change, header versioning is more flexible but less discoverable, and an additive-only strategy minimizes version churn but requires real discipline about what counts as a breaking change.

Question 3: Why do almost no production APIs implement HATEOAS despite it being part of REST's original definition?

How to approach it

HATEOAS, hypermedia as the engine of application state, is the top level of the Richardson Maturity Model: a response includes links describing what the client can do next, so the client does not need to hardcode URL structures. It is level 3 of 3, sitting above using proper HTTP verbs (level 2) and having distinct resource URIs at all (level 1). Being able to define it is the mid-level version of this answer.

The senior-level answer is naming the actual cost that makes teams skip it: adding hypermedia links generally increases payload size and client complexity without a proportional benefit for most internal or mobile-first APIs, where the client and server are usually deployed and versioned together anyway, so the coupling HATEOAS is designed to avoid was never really a problem for them. HATEOAS is asked about specifically to check whether a candidate understands REST's original design intent, and a strong answer states both halves: what it actually is and why most real systems do not implement it, rather than either dismissing it as irrelevant trivia or treating it as something every REST API should have.

Question 4: How should a REST API structure its error responses?

How to approach it

Returning a bare string message or an inconsistent shape per endpoint is the most common mistake here. The current standard is RFC 9457, "Problem Details for HTTP APIs," which supersedes RFC 7807. It defines a common set of members that problem responses can use: type identifies the problem type, title gives a short human-readable summary, status can repeat the HTTP status code, detail describes this particular occurrence, and instance can identify the specific problem occurrence. These members are not all mandatory; for example, omitting type gives it the default value about:blank.

{
"type": "https://api.example.com/errors/insufficient-funds",
"title": "Insufficient Funds",
"status": 400,
"detail": "Account balance of 30 is not sufficient to cover the charge of 50.",
"instance": "/v1/charges/ch_1a2b3c",
"balance": 30,
"requested": 50
}
The response uses the application/problem+json media type so clients know that the body follows the Problem Details format. The HTTP status code still carries the response's HTTP semantics, while the problem document provides structured, machine-readable details about the error. Note the extra balance and requested fields, RFC 9457 explicitly allows extension members beyond the core five, which is what makes this format useful for a specific error rather than just a generic wrapper.

The senior-level point is not memorizing the field names, it is explaining why a structured, machine-parseable error format matters: a client integrating against the API can branch on type programmatically instead of pattern-matching a message string that might change wording between releases.

Question 5: How do you implement rate limiting for a public API, and what does the client see?

How to approach it

The mechanism most commonly discussed is the token bucket: each client has a bucket that refills at a fixed rate, and a request is allowed if a token is available, rejected otherwise. A sliding window log or counter is the alternative, trading a small amount of memory overhead for smoother enforcement at the boundary between windows, since a fixed window can allow a burst of nearly double the limit right at the window edge.

On the wire, a rate-limited request returns 429 Too Many Requests with a Retry-After header telling the client how long to wait, a stable, long-established convention:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Policy: "default";q=100;w=60
RateLimit: "default";r=0;t=30
Content-Type: application/problem+json
{ "type": "https://api.example.com/errors/rate-limited", "title": "Too Many Requests", "status": 429 }
The RateLimit and RateLimit-Policy headers above come from the active IETF draft-ietf-httpapi-ratelimit-headers, which is working toward standardized rate-limit metadata that clients can use to understand quota policy and remaining capacity. It is still an Internet-Draft rather than a finalized RFC as of this guide's publication. This differs from the proposed Idempotency-Key specification discussed earlier, whose Internet-Draft has expired.

Question 6: How do you handle authentication and authorization in a REST API?

How to approach it

For a public or third-party-facing API, OAuth 2.0 is the framework most commonly reached for, and the senior-level distinction is knowing which grant type fits which client. The authorization code flow (with PKCE for public clients like mobile or single-page apps) is for a user delegating access to a third-party application. The client credentials flow is for service-to-service access with no user in the loop. Naming the resource owner password credentials grant as a good modern choice is a red flag. It requires the client to handle the user's credentials directly, and current OAuth security guidance says the grant must not be used. Existing legacy systems may still contain it, but it should not be selected when designing a new OAuth integration.

The second half of this question is how token state is validated. A self-contained token such as a signed JWT can often be validated locally by the resource server without contacting the authorization server on every request, which reduces lookup overhead but makes immediate revocation harder unless additional server-side state or checks are introduced. Opaque tokens are commonly validated against centralized token state or through token introspection, which makes revocation easier to enforce but can introduce lookup overhead, although those results may also be cached. A senior answer states this trade-off rather than treating JWT as a strictly superior default, and separately addresses refresh token rotation: issuing a new refresh token on every use and invalidating the previous one, so reuse of an older token can signal theft and trigger revocation of the token family.

Question 7: How do you design pagination for a large, growing REST resource?

How to approach it

Offset-based pagination (?page=3&limit=20 or ?offset=40&limit=20) is simple but breaks under concurrent writes, an item inserted before the current offset shifts every subsequent page, producing duplicates or skipped items depending on the direction of the shift. Cursor-based pagination can avoid this offset-shift problem when the cursor is based on a stable, deterministic ordering. Instead of paging from a numeric position that moves as rows are inserted or removed, the next request continues relative to a specific item or ordering value, at the cost of not naturally supporting a direct jump to an arbitrary page number.

A cursor-based response typically returns the cursor for the next page alongside the data, rather than expecting the client to compute it:

{
"data": [{ "id": "post_881" }, { "id": "post_880" }],
"next_cursor": "eyJpZCI6InBvc3RfODgwIn0="
}
For discoverability, the Link header defined in RFC 8288 lets a response advertise rel="next" and rel="prev" URLs directly:

Link: <https://api.example.com/posts?cursor=eyJpZCI6InBvc3RfODgwIn0%3D>; rel="next"
so a well-behaved client does not need to construct pagination URLs by hand, it follows the link the server provided. The senior-level answer picks based on the actual access pattern: a frequently updated feed is usually better served by cursor or keyset pagination when stable traversal matters, while a rarely changing, admin-facing table where jumping to page 7 genuinely matters can reasonably use offset.

Question 8: When would you choose REST over GraphQL or gRPC for a new API?

How to approach it

Current guidance across API design teams has converged on a hybrid-by-default answer rather than declaring one winner. REST remains the pragmatic default for public and partner-facing APIs, its tooling, caching behavior, and the fact that nearly every developer already understands HTTP semantics make it the lowest-friction choice for external consumers who do not control both ends of the integration. GraphQL earns its complexity as an aggregation layer in front of multiple services when a frontend genuinely needs to shape its own queries and avoid over-fetching or under-fetching across many resource types. gRPC is often a strong choice for internal service-to-service calls where both ends are controlled, thanks to schema-first Protobuf contracts, generated clients, compact binary serialization, and streaming support. Native gRPC is not directly exposed through standard browser networking APIs in the same way as it is to backend clients, although browser applications can communicate with gRPC services through gRPC-Web or similar gateways.

The trap in this question is presenting it as one-size-fits-all. A senior answer names the actual constraint each format solves and explains why a system frequently runs all three at different layers rather than standardizing on one.

Question 9: How does HTTP caching decide whether to reuse a response, and what do no-cache, no-store, private and Vary change?

How to approach it

Start with the two mechanisms. Freshness decides whether a stored response can be reused without asking the server. Validation asks the server whether the stored copy is still current. Per RFC 9111, max-age means the response is considered stale after its age exceeds the given number of seconds. Validation uses a validator the server supplied: the client sends If-None-Match with the stored ETag, and if it still matches the server answers 304 Not Modified without a body, per RFC 9110.

With no explicit freshness information, the cache does not necessarily treat the response as uncacheable. RFC 9111 allows a cache to assign a heuristic expiration time when none is given, and says that if Last-Modified is present a typical setting is no more than about 10 percent of the interval since that time. MDN's HTTP caching guide adds the practical advice that basically all responses should explicitly specify Cache-Control.

Then cover the reload distinction, which interviewers like because it explains confusing behavior in DevTools. Per MDN, a normal navigation reuses a fresh stored response, or makes a conditional request if it is stale. A normal reload sends Cache-Control: max-age=0 with the validators, so it revalidates. A force reload sends Cache-Control: no-cache without validators, so you get a full 200. The fetch equivalents are the cache modes: "no-cache" for the reload behavior and "reload" for the force reload.

The mid-level answer is "set Cache-Control explicitly and give the response a validator". The mistake to avoid is saying a response without Cache-Control is never cached.

These directives are confused so often that naming each one's actual effect is a strong answer. All wording below is from RFC 9111.

  • no-cache does not mean "do not store". Per section 5.2.2.4, a cache must not use the stored response for another request without forwarding it for validation first. MDN states it plainly: if what you want is "do not store", the directive to use is no-store.
  • no-store (section 5.2.2.5) means a cache must not store any part of the request or response, and must not use the response to satisfy another request.
  • private (section 5.2.2.7) means a shared cache, such as a CDN or proxy, must not store the response, while a private cache, such as the browser's, may.
  • must-revalidate (section 5.2.2.2) applies only after the response has become stale: the cache must not reuse it until it is validated.

The security consequence is the one to volunteer. Per MDN's Cache-Control reference, personalized content sent without private can be stored in a shared cache and reused for multiple users, which can leak personal information.

Vary is the other half of cache correctness. Per RFC 9111 section 4.1, a cache must not reuse a stored response unless the request header fields named by Vary match those of the original request. If an API returns different JSON based on Accept-Language or Accept-Encoding and does not say so in Vary, a shared cache may serve the wrong variant. MDN adds that the same Vary value should be used on all responses for a URL, including 304 responses.

One precise point: stale-while-revalidate and stale-if-error are not defined in RFC 9111. They come from RFC 5861, an Informational RFC, which lets a cache serve a stale response while it revalidates in the background, or when the origin returns a 500, 502, 503 or 504. Saying "RFC 9111 defines stale-while-revalidate" is a small error that a careful interviewer will notice.

Question 10: Two users edit the same record. How does the API stop the second save from silently overwriting the first?

How to approach it

This is the lost update problem, and HTTP has a built-in answer: conditional requests. The server sends an ETag with the representation. The client sends it back with the update in an If-Match header. Per RFC 9110, If-Match is most often used with state-changing methods to prevent accidental overwrites when multiple user agents might be acting in parallel on the same resource, and a server that evaluates a false If-Match condition must not perform the method.

GET /articles/42
HTTP/1.1 200 OK
ETag: "v7"
PUT /articles/42
If-Match: "v7"
...
HTTP/1.1 412 Precondition Failed

Know which status code goes where, because three candidates get mixed up:

  • 412 Precondition Failed is what the RFC suggests when the If-Match condition is false (section 15.5.13). The client had a precondition and it did not hold.
  • 428 Precondition Required is not in RFC 9110. It comes from RFC 6585 and is for a server that requires the request to be conditional, with the lost update problem named as its typical use. It means the client forgot to send the header.
  • 409 Conflict (section 15.5.10) means the request conflicts with the current state of the resource, in a way the user might be able to resolve. It fits a state conflict that is not tied to a validator, such as an invalid transition.

One detail separates good answers: If-Match uses the strong comparison function, per RFC 9110, so a weak validator (W/"v7") cannot satisfy it, while If-None-Match uses weak comparison. Also describe what the UI does on a 412: reload the latest version, show the difference, and let the user decide, instead of failing silently.

Question 11: When do you use PUT, PATCH with a merge document, or PATCH with JSON Patch?

How to approach it

Question 1 covered idempotency from the retry side; this question covers what the methods mean. In RFC 5789, a PUT body is a modified version of the stored resource, a full replacement. A PATCH body is a set of instructions describing how the resource should be modified. RFC 9110 lists PUT as idempotent. For PATCH, RFC 5789 says it is neither safe nor idempotent, and that a request can be issued in such a way as to be idempotent. So both "PATCH is idempotent" and "PATCH is never idempotent" are oversimplifications; the accurate version is that the method does not guarantee it.

Then name the two standard patch formats, because "PATCH with a partial object" is not itself a format:

  • JSON Merge Patch (RFC 7396, media type application/merge-patch+json): send the fields you want to change. A null value removes the field. Arrays are replaced whole, since the RFC says you cannot patch part of an array. It is therefore a poor fit for documents that use explicit null values.
  • JSON Patch (RFC 6902, media type application/json-patch+json): an array of operations such as { "op": "replace", "path": "/a/b/c", "value": 42 }, including a test operation. It can edit one array element and it is atomic, so a failed test leaves the document unchanged.

// Merge patch: remove "nickname", change "city"
{ "nickname": null, "address": { "city": "Pune" } }
// JSON Patch: change one array element only if the old value matches
[
{ "op": "test", "path": "/tags/1", "value": "draft" },
{ "op": "replace", "path": "/tags/1", "value": "published" }
]

RFC 5789 also lists the error cases: 400 for a malformed patch, 415 for an unsupported patch format, and 422 or 409 for a patch that cannot be applied. A good closing point: combine PATCH with If-Match from Question 10, because a patch applied to a changed document can produce a result nobody intended.

Question 12: A request will take 30 seconds to process. How should the API respond?

How to approach it

A 30-second operation does not automatically require an asynchronous API. If keeping the request open is acceptable for the client, server, and infrastructure, the API can return the final response when the work completes. If the operation should continue independently of the request connection, accept the work asynchronously with 202 Accepted and return a way for the client to track its status. RFC 9110 defines 202 Accepted as meaning the request has been accepted for processing but processing has not completed, and notes that HTTP has no facility to re-send a status code from an asynchronous operation. The response body ought to describe the current status and point to a status monitor.

A concrete documented shape comes from the Azure REST API guidelines. They say to return 202 Accepted when a PUT, POST or DELETE completes asynchronously, include an operation-location header with the URL of a status monitor, and have the monitor return 200 OK with a status such as Running, Succeeded or Failed, plus a retry-after value while the operation is not finished. Azure's guidelines use operation-location to identify the status monitor. This is an Azure API convention, not a requirement of HTTP 202; other APIs may use Location or include the status URL in the response body.

The client side of the answer matters as much:

  • Poll the status URL with a delay that respects retry-after and that grows when the job is slow, instead of a tight loop.
  • Show a pending state and make the page survive a reload by keeping the status URL.
  • Handle the terminal states, including failure, with a retry path.

A second design is documented too: Google's AIP-151 has long-running methods return an Operation object that tracks progress and holds the eventual result. HTTP also has 303 See Other for redirecting to the finished resource, and some APIs use it on completion, but the two vendor guidelines above do not describe that pattern, so present it as a design option, not a standard.

State the limit honestly: 202 makes no promise the work will eventually succeed, so the status resource needs to expose failure, not only completion.

Question 13: A user types quickly into a search box and results appear in the wrong order. How do you fix it?

How to approach it

This is a race condition: responses can arrive in a different order than the requests were sent, so a slow response for an older query overwrites the newer one. The browser answer is cancellation with AbortController.

let controller;
async function search(query) {
controller?.abort(); // cancel the previous request
controller = new AbortController();
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
signal: AbortSignal.any([controller.signal, AbortSignal.timeout(5000)]),
});
render(await res.json());
} catch (err) {
if (err.name === 'AbortError') return; // superseded, not a failure
if (err.name === 'TimeoutError') return showTimeoutMessage();
throw err;
}
}

The facts behind it, from MDN's AbortSignal documentation: when abort() is called, the fetch() promise rejects with a DOMException named AbortError. A signal can only be used once, and after it aborts any fetch using it rejects immediately, which is why the code creates a new controller per request. AbortSignal.timeout() aborts with a TimeoutError, so you can distinguish a timeout from a deliberate cancel by err.name. AbortSignal.any() combines signals so either one can end the request.

Two points of judgement. First, aborting stops the client from waiting; it does not by itself guarantee the server stops working, which is an inference from how HTTP works, not a sentence in the MDN pages, so say it as reasoning. If the server must stop expensive work, it has to notice the closed connection. Second, cancellation is not the only defense: debounce input so fewer requests start, and in libraries that manage this for you, check that a stale response cannot overwrite newer state.

Question 14: How do you upload a large file from the browser, and what goes wrong?

How to approach it

Name the options and what each costs.

Multipart through your API. FormData with fetch sends a multipart/form-data body. The trap, from MDN's FormData guide, is that you must not set the Content-Type header yourself: doing so prevents the browser from adding the boundary expression that separates the form fields. Leave it unset and the browser writes it correctly.

Progress. XMLHttpRequest exposes upload events through xhr.upload, which is how progress bars are built. fetch has no equivalent upload-progress event. Streaming a request body with fetch requires duplex: 'half', per MDN's RequestInit reference. Browser support for streaming request bodies remains uneven, and MDN still marks the feature as limited availability. Treat it as a progressive enhancement rather than something a general-audience upload flow can depend on, and check current support before using it in production.

Presigned URLs. The server hands the browser a time-limited URL so it uploads straight to object storage. The AWS S3 documentation describes presigned URLs as allowing an upload without the other party having AWS credentials, notes they are bearer tokens that grant access to anyone who holds them, and warns that uploading to an existing key replaces the object. So generate a unique key per upload and keep expiry short.

Resumable uploads. The tus protocol (version 1.0.0) resumes by sending a HEAD request to learn the Upload-Offset and then a PATCH from that offset. An IETF effort, draft-ietf-httpbis-resumable-upload, was still an Internet-Draft, not an RFC, when this was checked on 8 October 2026.

What goes wrong, in an interview-friendly list: files larger than the server body limit, a lost connection at 90 percent, and trusting the filename or MIME type the browser sends. Validate type and size on the server.

Question 15: A bulk endpoint creates 50 records and 3 fail. What does it return?

How to approach it

First, decide whether partial success is acceptable. Google's AIP-233 says a synchronous batch create must be atomic, while an asynchronous one may support atomic or partial success. Atomic means all 50 succeed or none do, and the failure response can be a single clear error. If the business allows partial success, the response has to say which items failed.

There are three designs, and it helps to know the trade-offs:

  1. Atomic, all or nothing. Simplest for clients. The cost is that one bad row blocks the other 49.
  2. Per-item results in a normal response. Return 200 with an array such as { "index": 7, "status": "failed", "error": { ... } }. This is a common design choice, but I found no vendor guideline that recommends it, so present it as a choice and not a standard. The client must read the body, not only the status code.
  3. Accept the batch as a job. Return 202 Accepted and a status monitor, as in Question 12, with per-item outcomes in the final result.

Be ready for 207 Multi-Status. It exists, but it comes from WebDAV: RFC 4918 defines it for status on multiple independent operations, specifies an XML body with a multistatus root element, and says the recipient must consult the body for success or failure. It is not defined in RFC 9110, and MDN notes it is used in the context of WebDAV. Because the outer status tells the client little and the defined body is XML, many JSON APIs choose a plain 200 or 202 instead. That reasoning is a synthesis of those two facts, not a quoted guideline.

For retries, consider giving each item a client-supplied identifier or idempotency key and have the server enforce deduplication around it. The identifier by itself does not make a retry safe; the server must define what happens when the same logical operation is submitted again. How the server deduplicates safely is the subject of Question 1.

Question 16: How do you implement an optimistic update, and what happens when it fails?

How to approach it

An optimistic update changes the UI before the server confirms the mutation. If the request succeeds, the optimistic state becomes the confirmed state. If it fails, the UI must either roll back to the previous state or refetch the authoritative value from the server. The important part is preserving enough information to recover from failure and making sure an older response cannot overwrite a newer change.

A library such as TanStack Query packages this pattern into mutation and cache APIs. Its documentation describes two common approaches.

Via the UI. Render the pending value from the mutation's variables while the mutation is in progress. On success, the normal server data replaces it. On error, the variables remain available, so the UI can show the failed item and offer a retry. Because the shared cache was never modified, there is no cached state to roll back.

Via the cache. In onMutate, cancel relevant outgoing refetches so stale server data cannot overwrite the optimistic write, snapshot the current cached value, and write the optimistic value. If the mutation fails, restore the snapshot in onError. When the mutation settles, invalidate or refetch the query so the cache is reconciled with the server.

The choice depends on scope. If only one place needs to display the optimistic result, rendering from the mutation state is usually simpler. If several parts of the application need to see the optimistic value immediately, updating the shared cache is more useful. In some failure cases a refetch is enough to recover; an explicit rollback matters when refetching is unavailable or cannot reliably reconstruct the previous state.

The part that earns credit at 3 years is naming the race conditions. An in-flight GET can return old data after the optimistic write, which is why relevant refetches may need to be cancelled. Two rapid mutations on the same item can also finish out of order, so the application needs a clear policy for sequencing or reconciling them. And a failed optimistic update should be visible to the user rather than leaving the UI silently different from the server.

If you show TanStack Query code in the answer, check the version-specific mutation callback signatures rather than copying an older tutorial, because those APIs have changed over time.

Question 17: The page needs live updates. When do you choose polling, Server-Sent Events or WebSockets?

How to approach it

Match the mechanism to the direction and rate of the data.

  • Polling is a repeated GET. It is the simplest and fits REST naturally, and it works with the caching and conditional requests from Questions 9 and 10, but it adds latency and wasted requests.
  • Server-Sent Events (SSE) use EventSource. Per MDN's EventSource page, SSE is unidirectional: the server sends events to the client over a persistent HTTP connection in text/event-stream format. The server can set a retry interval for reconnection, and the WHATWG HTML specification describes a Last-Event-ID request header that the browser sends when it reconnects, so the server can resume from the last event the client saw.
  • WebSockets give a two-way session, per MDN. The same page notes that the WebSocket interface does not support backpressure.

An often-cited SSE limit is the connection cap. MDN states that when SSE is not used over HTTP/2 there is a limit on open connections per browser and domain, set to a very low number (6), while with HTTP/2 the number of simultaneous streams is negotiated and defaults to 100. In practice that means several tabs open to the same domain can hit the limit over HTTP/1.1.

A sensible default answer: if updates flow only from server to client, such as notifications or progress, SSE is often enough and stays plain HTTP. If the client also sends frequent messages, such as a collaborative editor or a game, WebSockets fit. If updates are rare or a delay is acceptable, polling with a sensible interval and ETag support is the least machinery. Webhooks are a related idea but run server to server, so they do not push to a browser.

Question 18: How do you design an endpoint for "cancel this order" when it does not fit CRUD?

How to approach it

There are three common shapes, and the honest answer is that guidelines disagree on details, so you argue trade-offs.

  1. PATCH /orders/42 with { "status": "cancelled" }. It stays inside standard methods. The costs: PATCH is not guaranteed idempotent (Question 11), and the server must reject invalid transitions, for which 409 Conflict (RFC 9110) fits a state conflict.
  2. A custom action with POST. Google's AIP-136 says custom methods should be used only for functionality that cannot be easily expressed with standard methods, and that POST must be used when a method has side effects, with a colon verb such as :archive. The Azure guidelines also use POST for actions at <resource>:<action> and add a "do not" rule: do not use an action when the behavior can reasonably be defined as a standard create, read, update, delete or list operation.
  3. DELETE /orders/42. Per RFC 9110, DELETE removes the association between the resource and its current functionality, and it does not require destroying the data. For an order that must stay readable after cancellation, it is usually the wrong signal to clients, so avoid it unless the order really goes away.

Neither guideline mentions order cancellation specifically, and neither endorses the slash-style POST /orders/42/cancel, though it is a common spelling of the same idea. So the defensible position is to say which constraint decides it. If cancelling is a simple state change, PATCH is permitted by the Azure rule. If cancelling triggers side effects such as refunds and emails, and has its own rules and error cases, a POST action communicates that better.

Whichever you choose, document the allowed state transitions and define the retry behavior explicitly. PATCH itself does not guarantee idempotency, but a transition such as setting an order's state to cancelled can be designed to be idempotent: repeating the same request leaves the order in the same final state rather than triggering the cancellation side effects twice.

Question 19: The API returns "id": 9007199254740993 and the frontend shows a different number. Why?

How to approach it

JavaScript numbers are IEEE 754 double-precision floats. MDN's Number.MAX_SAFE_INTEGER page explains that only integers between -(2^53 - 1) and 2^53 - 1 are safely represented, and shows that Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 is true. JSON.parse converts numbers before your code sees them, so a larger id is already rounded by the time you read it. MDN's JSON.parse page says one way to transfer large numbers without loss is to serialize them as strings and revive them as BigInt.

This is not a JavaScript-only bug in the format. RFC 8259 allows implementations to set limits on range and precision and says integers within [-(2^53)+1, (2^53)-1] are interoperable. The Azure guidelines follow that, advising integers stay within that range. The practical rule: send identifiers as strings, and never do arithmetic on them.

Dates have a related trap. JSON has no date type; per RFC 8259 a value is an object, array, number, string, or true, false or null, so dates travel as strings. Prefer an RFC 3339 timestamp in UTC with a Z suffix. Then know the parsing rule from MDN's Date reference: when no time zone offset is present, a date-only string like 2019-01-01 is interpreted as UTC, while a date-time string like 2019-01-01T00:00:00 is interpreted as local time. MDN says the date-only behavior comes from a historical specification error that could not be changed for web compatibility.

Date.parse('2019-01-01'); // 1546300800000, treated as UTC
Date.parse('2019-01-01T00:00:00'); // local time of the machine running this
Date.parse('2019-01-01T00:00:00Z'); // UTC, explicit

A pure date such as a birthday should generally remain a date string rather than being converted into a JavaScript Date. If 2019-01-01 is parsed as a Date, it represents midnight UTC; displaying that instant in a negative-offset time zone can therefore show the previous calendar day. Timestamps are different: include an explicit offset or Z so the intended instant is unambiguous.

Common mistakes and red flags

Presenting HATEOAS as something every REST API should implement, without weighing the actual payload and complexity cost, is a common miss. Citing either proposal as a finalized RFC is a precision mistake: the RateLimit headers are still being developed through an active IETF Internet-Draft, while the proposed Idempotency-Key Internet-Draft has expired without becoming a final RFC. Treating JWT as strictly better than an opaque token without naming the revocation trade-off, and recommending the OAuth resource owner password grant for a normal third-party integration, are both red flags at this level.

The behavior-level mistakes are just as common:

  • Saying no-cache means "do not store". That is no-store.
  • Attributing stale-while-revalidate to RFC 9111. It is in RFC 5861.
  • Using 409 for every conflict. A false If-Match is 412, and a missing required precondition is 428.
  • Describing PATCH as simply idempotent or simply not. The specification says it is not guaranteed.
  • Setting Content-Type: multipart/form-data by hand and losing the boundary.
  • Returning 207 from a JSON API without knowing it is WebDAV.
  • Sending 64-bit ids as JSON numbers to a JavaScript client.

Frequently asked questions

Is RFC 7807 still the correct RFC to cite for API error format? RFC 7807 has been superseded by RFC 9457, which carries the same core field structure. Citing the newer number is the more current, precise answer, though the content is materially unchanged.

Do I need to implement idempotency keys for every POST endpoint? No. It matters most for operations with a real cost of duplication, payments, order creation, anything triggering a side effect that should not happen twice. A read-only or freely repeatable POST does not need one.

Is GraphQL replacing REST for public APIs? No, and current guidance treats this as a settled non-question. REST remains the dominant choice for public, partner-facing APIs specifically because of its caching and tooling ecosystem; GraphQL is more commonly an internal aggregation layer than a public API replacement.

How current does my knowledge of these IETF efforts need to be for an interview? Knowing their current status matters more than memorizing draft revision numbers: the standardized RateLimit headers are still being developed through an active IETF Internet-Draft, while the proposed Idempotency-Key draft has expired without becoming a final RFC. The idempotency-key pattern itself remains widely used in production APIs.

Is this guide enough for a 3 years experience round? Questions 9 to 19 cover the HTTP behavior that level is expected to apply. Questions 1 to 8 are what a senior round adds, and a mid-level round may still touch them, so read them for the reasoning even if you are not interviewing for a senior role.

Do I need to memorize RFC numbers? No, but knowing which document defines a behavior helps you answer precisely. If you cannot recall a number, describe the behavior and say where you would check.

Is this relevant for a frontend role? Yes. Caching, cancellation, optimistic updates and uploads are all decisions made in frontend code, and they come up in frontend and full-stack interviews.

How to prepare

Work through the mechanics above with a real scenario attached: given a public API serving both a mobile app and third-party integrators, how would you version it, and what would change if it only served an internal team. Practicing the design-trade-off questions, versioning, pagination, rate limiting, by actually writing out the response shapes is worth more than reading about the concepts, since a senior interviewer is more likely to hand you a scenario and ask you to design the response than to ask you to define a term.

For the behavior layer, open your browser's network panel on an app you built and inspect five responses. For each, write down its Cache-Control, whether it has an ETag, and what a reload does. Then try to cause each failure on purpose: send two overlapping search requests, edit a record in two tabs, upload a file with a hand-set Content-Type, and parse a 17-digit id. Being able to say what you saw in a real request is stronger than reciting a definition.

If you are also preparing GraphQL-specific rounds, GreatFrontEnd's GraphQL interview questions guide covers the equivalent data-layer fundamentals, and the versioning and pagination trade-off reasoning here carries over directly. For JavaScript fundamentals that appear alongside these questions, see GreatFrontEnd's JavaScript interview questions for 5 years experience.

Conclusion

Senior REST API developer interview questions test whether you can make real design trade-offs, not whether you can recite HTTP verbs. Idempotency and retry safety, versioning strategy, structured error responses, rate limiting, and the judgment to pick REST, GraphQL, or gRPC for the right layer are the differentiators at senior level. Underneath them sits the HTTP behavior every experienced developer is expected to know: caches that revalidate, preconditions that prevent lost updates, patch formats with different rules, 202 for slow work, signals that cancel stale requests, batches that report partial failure, optimistic updates that roll back, and JSON values that JavaScript can silently change. If you can state the behavior, the document it comes from, and the trade-off for each of the 19 questions here, you are answering at the level these interviews are looking for.

相关文章

Senior CSS Developer Interview Questions: Advanced Topics and AnswersSenior CSS developer interview questions and answers: cascade layers, design tokens, layout thrashing, and the modern CSS a mid-level round never covers
TypeScript Interview Questions for Freshers (2026 Guide)TypeScript interview questions for freshers: the basics interviewers actually ask, what is out of scope at this level, and a practical way to prepare for them.