REST — Interview Q&A Matrix
Parent stack: Messaging & Integration Technology: REST Levels: Junior · Senior · Architect Questions: 3
Each entry contains a Question, the Expected Depth, an Ideal Answer, and Red Flags.
Junior
Q1 — REST fundamentals and idempotent methods
Question: What makes an API RESTful, and which HTTP methods are idempotent? Why does it matter?
Expected Depth:
- Models business concepts as resources with stable URLs.
- Explains stateless requests, representations, uniform HTTP methods, and cache controls.
- Distinguishes safe methods from idempotent methods.
- Knows that idempotency concerns the intended server state, not an identical response.
- Uses suitable status codes, headers, and content types.
- Understands that
POSTandPATCHare not idempotent by default but can be designed safely for retries.
Ideal Answer:
- REST exposes resources through URLs and exchanges representations such as JSON. Each request contains the information needed to process it; the server does not depend on hidden client session state.
- Use HTTP methods for their defined meaning:
GETreads a resource.POSTcreates a subordinate resource or starts an action.PUTcreates or fully replaces a resource at a known URI.PATCHapplies a partial change.DELETEremoves a resource.- A safe method is intended not to change server state.
GET,HEAD, andOPTIONSare safe. - An idempotent method has the same intended server effect when repeated.
GET,HEAD,OPTIONS,PUT, andDELETEare idempotent. The response may still change. For example, the firstDELETEmay return204, while a repeated request may return404. POSTandPATCHare not idempotent by default. A server can add idempotency to aPOSTwith an idempotency key.- Use clear status codes and headers:
POST /orders HTTP/1.1
Content-Type: application/json
{"customerId":"c-42","items":[{"sku":"A1","quantity":2}]}
HTTP/1.1 201 Created
Location: /orders/o-1007
Content-Type: application/json
{"id":"o-1007","status":"PENDING"}- Common responses include
200 OKfor a successful response with a body,201 Createdfor a new resource,204 No Contentfor success without a body,400 Bad Requestfor malformed input,404 Not Found, and409 Conflictfor a state conflict. - Use cache headers such as
Cache-Controland validators such asETagwhere caching or conditional updates are useful:
PUT /accounts/42 HTTP/1.1
If-Match: "version-7"
Content-Type: application/json
{"name":"Ada"}- If the version changed, the server can return
412 Precondition Failedinstead of overwriting a newer update.
Red Flags:
- Says that every HTTP method is idempotent or that
POSTis always idempotent. - Defines idempotency as returning the same status code and body every time.
- Uses
GETto change data. - Returns
200 OKfor every result, including validation errors and missing resources. - Stores required request state only in a server session and calls the API stateless.
- Ignores
Content-Type, caching headers, and concurrent-update protection.
Senior
Q2 — Idempotency keys for REST APIs
Question: A payment POST may be retried by clients/proxies, risking double charges. Design an idempotency mechanism for the REST API.
Expected Depth:
- Requires a unique idempotency key for one logical operation.
- Scopes the key by tenant, user, or endpoint and stores a request fingerprint.
- Uses a unique database constraint to handle concurrent duplicate requests.
- Stores operation state and the original successful response.
- Handles in-progress requests, key reuse with a different payload, expiration, and recovery after a crash.
- Understands that an external payment call cannot join the local database transaction.
Ideal Answer:
- Require an **
Idempotency-Key** for each logical payment attempt. Scope it with the tenant and operation so two tenants can safely use the same key. - Store the key, a stable request fingerprint, status (
PENDING,COMPLETED, orFAILED), response, and expiration time. Protect the scoped key with a unique constraint:
CREATE TABLE idempotency_record (
tenant_id text NOT NULL,
operation text NOT NULL,
idempotency_key text NOT NULL,
request_hash text NOT NULL,
status text NOT NULL,
response_code integer,
response_body jsonb,
expires_at timestamptz NOT NULL,
PRIMARY KEY (tenant_id, operation, idempotency_key)
);- Process requests with these rules:
- Try to insert a
PENDINGrecord. The unique constraint decides which concurrent request owns the operation. - If a completed record exists with the same fingerprint, return the stored status and body.
- If the same key has a different fingerprint, return
409 Conflictbecause the client reused the key incorrectly. - If the operation is still in progress, return
202 Acceptedwith a status URL andRetry-After. The client can poll the status without starting another payment. - The controller can keep this policy in one reusable service:
@PostMapping("/payments")
ResponseEntity<PaymentResponse> pay(
@RequestHeader("Idempotency-Key") String key,
@RequestHeader("X-Tenant-Id") String tenantId,
@Valid @RequestBody PaymentRequest request) {
return idempotency.execute(
tenantId,
"create-payment",
key,
fingerprint(request),
() -> payments.charge(request, key));
}- If the side effect is only a local database change, write the business data and completed idempotency response in one database transaction.
- A payment provider is external and cannot join that transaction. Send the same idempotency key to the provider. If the service crashes after the charge but before saving the response, recover by querying the provider with that key instead of charging again.
- Keep records longer than the maximum client and proxy retry window. Cleanup must not remove a key while an operation is still recoverable.
- Do not cache every failure forever. Store deterministic client failures if the contract requires the same replay result, but allow safe retry of transient failures according to a documented policy.
Red Flags:
- Relies only on client-side duplicate detection.
- Uses an in-memory map, which fails across instances and restarts.
- Checks for a key and inserts it in separate non-atomic steps without a unique constraint.
- Accepts the same key with a different request body.
- Stores the key but calls the payment provider again during replay.
- Claims that the local database transaction also covers the external payment provider.
- Expires records before the retry window ends or deletes
PENDINGrecords without a recovery plan.
Architect
Q3 — Resilient integration: retries, timeouts, and circuit breakers
Question: A synchronous call chain across services causes cascading failures under partial outages. Design resilience patterns for inter-service communication at scale.
Expected Depth:
- Uses an end-to-end deadline and divides it across connection, response, and processing timeouts.
- Retries only transient, idempotent operations with limits, exponential backoff, jitter, and a retry budget.
- Uses circuit breakers, bulkheads, rate limits, and bounded queues for different failure modes.
- Provides fallbacks only when stale or reduced data is safe for the business.
- Understands how retry multiplication across service layers causes retry storms.
- Uses asynchronous messaging only when eventual consistency is acceptable.
- Makes every resilience decision observable through metrics, logs, tracing, and alerts.
Ideal Answer:
- Start with an end-to-end request deadline. Give each downstream call a smaller timeout so the caller still has time to handle failure. Configure connection, response, and pool-acquisition timeouts; do not rely on client defaults.
- Configure the HTTP client separately from
TimeLimiter. For example, keep each network timeout below the method-level deadline:
ConnectionProvider pool = ConnectionProvider.builder("pricing")
.maxConnections(50)
.pendingAcquireTimeout(Duration.ofMillis(100))
.build();
HttpClient httpClient = HttpClient.create(pool)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 200)
.responseTimeout(Duration.ofMillis(400));
WebClient pricingClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();- Retry only transient failures, such as selected
502,503, or504responses and some connection failures. RespectRetry-Afterfor429or503. Do not retry validation errors or non-idempotent operations without an idempotency key. - Use a small retry count and a delay. At high traffic, use exponential backoff with jitter through a custom retry interval function. Retry at one chosen layer where possible. If three services each retry three times, one user request can create many downstream calls.
- A circuit breaker opens after a measured failure or slow-call threshold. While open, calls fail fast. After a wait period, a limited number of calls test whether the dependency has recovered.
- A bulkhead limits concurrent calls so one slow dependency cannot use every thread, virtual thread, connection, or permit.
- Resilience4j can apply these policies to an asynchronous call:
@CircuitBreaker(name = "pricing", fallbackMethod = "cachedQuote")
@Retry(name = "pricing")
@TimeLimiter(name = "pricing")
@Bulkhead(name = "pricing")
public CompletableFuture<Quote> quote(String sku) {
return CompletableFuture.supplyAsync(
() -> pricingClient.quote(sku),
pricingExecutor);
}
private CompletableFuture<Quote> cachedQuote(String sku, Throwable error) {
return CompletableFuture.completedFuture(lastKnownGood(sku));
}- Configure policies from measured latency and failure data:
resilience4j:
circuitbreaker:
instances:
pricing:
sliding-window-size: 50
failure-rate-threshold: 50
slow-call-duration-threshold: 400ms
slow-call-rate-threshold: 60
wait-duration-in-open-state: 10s
retry:
instances:
pricing:
max-attempts: 3
wait-duration: 100ms
bulkhead:
instances:
pricing:
max-concurrent-calls: 10
max-wait-duration: 0
timelimiter:
instances:
pricing:
timeout-duration: 500ms- A fallback must be correct for the business. A cached price may be acceptable for a product page but unsafe for a final trade or payment decision. Return a clear error when no safe fallback exists.
- Use asynchronous events to remove a synchronous dependency only when the workflow can accept eventual consistency. The broker, queue, or outbox must be durable and bounded, with retry, dead-letter handling, idempotent consumers, and back-pressure.
- Monitor timeout rate, retry count, circuit state, bulkhead saturation, queue depth, fallback use, and end-to-end latency. Include correlation and trace IDs in logs.
Red Flags:
- Uses no explicit timeout or sets every timeout to the same large value.
- Retries every error, including validation failures and non-idempotent payments.
- Retries at every service layer and creates a retry storm.
- Adds a circuit breaker but no bulkhead, so slow calls still consume all resources.
- Returns stale data as a fallback where correctness is more important than availability.
- Uses an unbounded queue and calls it resilience.
- Moves work to messaging without accepting eventual consistency or adding idempotency and dead-letter handling.
- Chooses thresholds without metrics, load tests, or production observations.
Scoring Rubric
| Level | Pass bar |
|---|---|
| Junior | Uses HTTP methods, status codes, and resource URLs correctly. |
| Senior | Designs safe retries and idempotent operations under concurrency and failure. |
| Architect | Defines measured resilience standards and prevents cascading failures at scale. |