- REST interviews test judgement: resource modelling, correct methods and status codes, idempotency, pagination, versioning and error design.
- Know the table: GET, PUT, DELETE are idempotent; POST is not; PATCH is not guaranteed. Make POSTs safe to retry with an Idempotency-Key.
- Use status codes precisely: 201 + Location for creates, 202 for async work, 401 vs 403, 409 for conflicts, 412 for failed preconditions, 429 with Retry-After.
- Prefer cursor (keyset) pagination for large or changing collections; offset only for small admin lists.
- In a design round, walk through resources, endpoints, request and response bodies, errors, auth, pagination, idempotency and versioning, in that order.
Almost every backend interview touches API design. Sometimes it is a direct question ("PUT vs PATCH?"), sometimes it is hidden inside system design ("define the APIs for a ride-hailing service"), and sometimes it is a resume probe ("how did clients of your service handle retries?"). The answers that impress are not definitions; they are decisions with reasons, especially about failure. This guide covers the questions in that order, with real HTTP exchanges, and finishes with a complete API design round for an e-commerce order and payment flow.
REST Fundamentals Interview Questions
Q1. What is REST?
REST (Representational State Transfer) is an architectural style described by Roy Fielding in his 2000 dissertation. Its constraints: client-server separation, statelessness (each request carries everything needed to process it), cacheability (responses declare whether they can be cached), a uniform interface (resources identified by URIs, manipulated through representations, self-descriptive messages, hypermedia), a layered system (proxies, gateways and CDNs can sit in between), and optional code on demand. In practice, "REST API" usually means resource-oriented HTTP + JSON that follows HTTP semantics.
Q2. What does stateless mean, and does it forbid sessions?
The server does not keep client conversation state between requests; each request is self-contained. That lets any server instance handle any request, which is what makes horizontal scaling and load balancing easy. Application data (orders, carts stored in a database) is fine; what REST avoids is server memory of "where this client is in a flow". Auth is carried in every request (a bearer token or a session cookie that maps to a shared store).
Q3. What is a resource vs a representation?
A resource is the concept (order 981). A representation is a particular serialised form of its state (JSON, XML, a PDF invoice), negotiated with Accept and Content-Type headers. GET /orders/981 with Accept: application/pdf could return the invoice for the same resource.
Q4. What is HATEOAS, and do real APIs use it?
Hypermedia as the Engine of Application State: responses include links to related actions, so clients discover what they can do next instead of hard-coding URLs.
{
"id": "ord_981",
"status": "PENDING_PAYMENT",
"links": {
"self": { "href": "/v1/orders/ord_981" },
"pay": { "href": "/v1/orders/ord_981/payments", "method": "POST" },
"cancel": { "href": "/v1/orders/ord_981/cancel", "method": "POST" }
}
}
Honest answer: most public APIs implement it partially at best (pagination links are the common case). It is worth mentioning, and the state-dependent links above (no pay link once paid) are a neat way to show you understand the idea.
Q5. What is the Richardson Maturity Model?
Level 0: one endpoint, everything POSTed (RPC over HTTP). Level 1: multiple resources. Level 2: proper HTTP methods and status codes, where most good APIs sit. Level 3: hypermedia controls (HATEOAS).
HTTP Methods and Idempotency Interview Questions
Q6. Which HTTP methods are safe and which are idempotent?
| Method | Purpose | Safe | Idempotent | Request body |
|---|---|---|---|---|
| GET | Read a resource or collection | Yes | Yes | No (semantics undefined) |
| HEAD | GET headers only | Yes | Yes | No |
| OPTIONS | Capabilities, CORS preflight | Yes | Yes | Rare |
| POST | Create in a collection, or trigger a process | No | No | Yes |
| PUT | Replace (or create at a client-chosen URI) | No | Yes | Yes |
| PATCH | Partial update | No | Not guaranteed | Yes |
| DELETE | Remove | No | Yes | Rare |
Safe means no intended state change. Idempotent means the effect on the server of N identical requests equals the effect of one. Idempotency is about server state, not the response: a second DELETE /orders/981 may return 404 instead of 204, and it is still idempotent because the order is equally gone. The definitions are in RFC 9110 (HTTP Semantics).
Q7. Why does idempotency matter so much?
Networks fail ambiguously. A client sends a request, the connection drops, and it does not know whether the server processed it. Idempotent requests can be retried blindly by clients, SDKs, proxies and load balancers. Non-idempotent ones (a POST that charges a card) can double-charge. That is why idempotency is the heart of every payment API discussion.
Q8. PUT vs PATCH vs POST?
PUT /v1/users/u_42 # full replacement: omitted fields are cleared or defaulted
{ "name": "Asha Rao", "email": "asha@example.com", "phone": "+919800000000" }
PATCH /v1/users/u_42 # partial: only these fields change
Content-Type: application/merge-patch+json
{ "phone": "+919811111111" }
POST /v1/users # create; server assigns the ID
{ "name": "Ravi", "email": "ravi@example.com" }
-> 201 Created, Location: /v1/users/u_43
PATCH format matters: JSON Merge Patch (RFC 7386) sends a partial object, where null means delete the field. JSON Patch (RFC 6902) sends operations ([{"op":"replace","path":"/phone","value":"..."}]) and can express array edits precisely. A PATCH like {"op":"increment"} is not idempotent.
Q9. How do you make POST idempotent? Design the Idempotency-Key mechanism.
POST /v1/payments
Idempotency-Key: 8f14e45f-ea3b-4c1d-9a6e-2c9d1c5e7b10
{ "order_id": "ord_981", "amount": 49900, "currency": "INR", "method": "upi" }
- Client generates a UUID per logical operation (per checkout attempt, not per HTTP attempt) and reuses it on every retry.
- Server inserts
(merchant_id, key)into an idempotency table with a unique constraint, plus a hash of the request body and statusIN_PROGRESS, inside the same transaction as the work, or before it with careful state handling. - Duplicate with the same body while in progress: return 409 (or make the client wait). After completion: return the stored response (same status and body), without re-executing.
- Same key, different body: reject with 422 or 409; it is a client bug.
- Expire keys after a window (24 hours is common).
Follow-up: idempotency at the edge does not remove the need for idempotent consumers downstream (a Kafka consumer may still see an event twice). This pattern is central to the payment system design.
Q10. How do you model actions that aren't CRUD, like "cancel order"?
Options: a state change via PATCH ({"status": "CANCELLED"}), which works when the transition is simple; or a sub-resource for the action, POST /v1/orders/ord_981/cancellation (or /cancel), when the action has its own inputs (reason, refund mode), side effects and rules. The second is clearer for business operations and easier to authorise separately. Avoid verbs at the top level (/cancelOrder?id=981).
HTTP Status Codes Interview Questions
Q11. Which status codes should every API use correctly?
| Code | Meaning | Use when |
|---|---|---|
| 200 OK | Success with body | GET, or PUT/PATCH returning the updated resource |
| 201 Created | Resource created | POST that creates; include Location header |
| 202 Accepted | Accepted for async processing | Report generation, bulk import; return a status URL |
| 204 No Content | Success, no body | DELETE, or updates that return nothing |
| 301 / 308 | Permanent redirect (308 keeps method and body) | Moved endpoints |
| 302 / 307 | Temporary redirect (307 keeps method and body) | Temporary moves |
| 304 Not Modified | Cached copy still valid | Conditional GET with If-None-Match |
| 400 Bad Request | Malformed request | Invalid JSON, wrong types |
| 401 Unauthorized | Not authenticated | Missing or invalid token |
| 403 Forbidden | Authenticated but not allowed | User accessing another merchant's data |
| 404 Not Found | No such resource | Also used instead of 403 to avoid revealing existence |
| 405 Method Not Allowed | Method unsupported on resource | DELETE on a collection; include Allow |
| 409 Conflict | Conflicts with current state | Duplicate unique field, invalid state transition |
| 412 Precondition Failed | Conditional header failed | If-Match ETag mismatch (lost-update protection) |
| 415 Unsupported Media Type | Wrong Content-Type | XML sent to a JSON-only endpoint |
| 422 Unprocessable Content | Valid syntax, invalid semantics | Validation failures |
| 429 Too Many Requests | Rate limited | Include Retry-After |
| 500 Internal Server Error | Unexpected server failure | Bugs; never for client errors |
| 502 / 503 / 504 | Bad gateway / unavailable / gateway timeout | Upstream failure, overload or maintenance, upstream timeout |
Q12. 401 vs 403?
401: "I don't know who you are" (no token, expired token, bad signature); the client should (re)authenticate, and the response should carry a WWW-Authenticate header. 403: "I know who you are, and you can't do this"; retrying with the same credentials won't help. Despite its name, 401 is about authentication, not authorisation.
Q13. 400 vs 422 vs 409?
400 for syntactically broken requests (unparseable JSON). 422 for well-formed requests that fail validation rules (quantity of -2). 409 for requests that are valid in isolation but conflict with the current state of the resource (cancelling an already-shipped order, registering an email that exists). Consistency across your API matters more than which camp you pick for 400 vs 422.
Q14. Should errors ever return 200 with an error body?
No. Clients, proxies, monitoring and retries all key off status codes. A 200 with {"success": false} breaks caching, hides errors from dashboards and confuses SDKs. Return the right status plus a structured body.
Q15. Design a good error response.
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://api.example.com/errors/validation",
"title": "Request validation failed",
"status": 422,
"detail": "2 fields are invalid",
"instance": "/v1/orders",
"request_id": "req_7Hq2kd91",
"errors": [
{ "field": "items[0].quantity", "code": "min", "message": "must be at least 1" },
{ "field": "shipping.pincode", "code": "pattern", "message": "must be a 6-digit PIN code" }
]
}
This follows the Problem Details format (RFC 9457): a stable machine-readable type/code for programs, a human message for developers, per-field errors, and a request ID for support. Never leak stack traces or SQL.
Resource and URL Design Questions
Q16. What are good URL naming conventions?
- Nouns, plural:
/orders,/orders/{id}; the method is the verb. - Lowercase, hyphenated:
/delivery-slots. - Nesting for ownership, at most one or two levels:
/orders/{id}/items. Deeper relations get their own top-level resources with filters (/shipments?order_id=981). - Query parameters for filtering, sorting, pagination, never for identity.
- No file extensions or verbs: not
/getOrders.json.
Q17. Should IDs be sequential integers?
Sequential IDs leak business volume (order 10452 tells a competitor how many orders you have) and invite enumeration attacks. Public APIs commonly use opaque, prefixed IDs (ord_8fK2...) or UUIDs; prefixes make logs and support tickets readable. Time-ordered IDs (UUIDv7, Snowflake-style) keep database indexes efficient; see unique ID generator design.
Q18. Should responses be wrapped in an envelope?
For single resources, return the object directly. For collections, an object wrapper is useful because it leaves room for pagination metadata: {"data": [...], "next_cursor": "..."}. Returning a bare top-level array makes it impossible to add metadata later without a breaking change.
Q19. How do you handle long-running operations?
POST /v1/reports { "type": "gst-sales", "month": "2026-09" }
-> 202 Accepted
Location: /v1/report-jobs/job_77
{ "id": "job_77", "status": "QUEUED" }
GET /v1/report-jobs/job_77
-> 200 { "id": "job_77", "status": "SUCCEEDED", "result_url": "https://files.example.com/..." }
Clients poll with backoff, or you notify via a webhook when done. Webhooks need signatures (HMAC of the body with a shared secret plus a timestamp to prevent replay), retries with backoff, and idempotent receivers.
Q20. How do you design bulk operations?
POST /v1/products/batch with an array, returning per-item results (status and error for each) with 200, or 207 Multi-Status in WebDAV-style APIs. Decide and document whether a batch is atomic (all or nothing) or partial; cap the batch size; for very large imports, upload a file and process it asynchronously (Q19).
Pagination, Filtering and Sorting Interview Questions
Q21. Offset vs cursor pagination?
Offset (?page=50&limit=20) | Cursor / keyset (?after=eyJpZCI6...&limit=20) | |
|---|---|---|
| SQL | ORDER BY created_at DESC LIMIT 20 OFFSET 980 | WHERE (created_at, id) < (:ts, :id) ORDER BY created_at DESC, id DESC LIMIT 20 |
| Deep page cost | Grows with offset: DB scans and discards 980 rows | Constant: index seek |
| Concurrent inserts | Items shift: duplicates or skips across pages | Stable |
| Jump to page N | Yes | No (next/previous only) |
| Best for | Small admin tables with page numbers | Feeds, transaction histories, large collections, infinite scroll |
Note the tiebreaker: sorting by created_at alone breaks when two rows share a timestamp, so the cursor encodes (created_at, id) and the index covers both. Encode the cursor opaquely (base64 of JSON, ideally signed) so clients cannot construct or depend on its format.
GET /v1/transactions?limit=20&after=eyJ0cyI6IjIwMjYtMTAtMDlUMTA6MDA6MDBaIiwiaWQiOiJ0eG5fOTkifQ
{
"data": [ ... 20 transactions ... ],
"has_more": true,
"next_cursor": "eyJ0cyI6IjIwMjYtMTAtMDhUMjI6MTQ6MDNaIiwiaWQiOiJ0eG5fNzEifQ"
}
Q22. Should you return a total count?
Only if clients truly need it. COUNT(*) over a large filtered table can cost more than fetching the page itself. Many APIs return has_more instead, or an approximate count, or make the total an opt-in parameter.
Q23. How do you design filtering and sorting?
GET /v1/orders?status=PAID&created_after=2026-10-01&sort=-created_at&limit=50. Whitelist filterable and sortable fields (each needs an index), cap limit (say 100), use ISO 8601 timestamps with time zones, and validate every parameter. For complex search, provide a dedicated search endpoint backed by a search engine rather than turning query strings into arbitrary SQL. Sparse fieldsets (?fields=id,status,amount) reduce payload size for mobile clients.
API Versioning Strategies
Q24. What are the ways to version a REST API?
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URI path | /v1/orders | Explicit, easy routing, cacheable, testable in a browser | "Version" is not a resource; big-bang moves to v2 |
| Query parameter | /orders?version=2 | Easy to add | Easy to forget, messy caching |
| Custom header | Api-Version: 2026-10-01 | Clean URIs, fine-grained date-based versions | Invisible in links and logs unless you log it |
| Media type | Accept: application/vnd.shop.v2+json | Most "RESTful" | Harder for clients and tooling |
A good answer picks one and explains. URI versioning for the major version is the most common; Stripe popularised date-based versions pinned per account, which lets the API evolve continuously while each client sees a stable contract.
Q25. What counts as a breaking change?
Breaking: removing or renaming a field or endpoint, changing a field's type or meaning, making an optional request field required, adding a new required parameter, changing error codes clients depend on, adding a new enum value that strict clients cannot parse. Non-breaking: adding optional request fields, adding response fields, adding endpoints. Tell clients to ignore unknown fields (the tolerant reader principle) so additions stay safe.
Q26. How do you deprecate an API version?
Announce a timeline, mark responses with Deprecation and Sunset headers (the Sunset header is RFC 8594), publish a migration guide, track usage per client via API keys, contact the heaviest remaining users directly, and only then retire. Optionally run "brownouts" (short scheduled failures) to surface forgotten clients before the final shutdown.
REST API Authentication and Security Questions
Q27. API keys vs OAuth 2.0 vs JWT vs session cookies?
| Mechanism | What it is | Fits |
|---|---|---|
| API key | A static secret identifying a client application | Server-to-server integrations; pair with IP allow-lists and rotation |
| OAuth 2.0 | A framework for delegated authorisation that issues scoped access tokens | Third-party access to user data ("Sign in with Google", partner apps) |
| JWT | A token format: signed claims, verifiable without a DB lookup | Access tokens between services; keep them short-lived |
| Session cookie | Opaque ID mapping to server-side session state | First-party web apps; easy revocation |
OAuth and JWT are not alternatives: OAuth defines flows, JWT is one way to encode the tokens. Details in OAuth2 and JWT explained.
Q28. What is the most common API security vulnerability?
Broken object level authorisation (BOLA), first in the OWASP API Security Top 10: the API checks that you are logged in but not that the object belongs to you, so changing /v1/orders/981 to /v1/orders/982 shows someone else's order. Fix: every data access is scoped by the caller's identity (WHERE id = :id AND customer_id = :caller), enforced centrally, and tested. Other top issues: broken authentication, excessive data exposure (returning whole DB rows), missing rate limits, and mass assignment (binding request JSON directly onto models so a client can set "role": "admin").
Q29. How should you rate limit an API?
Per API key or user (and per IP for unauthenticated endpoints like login and OTP), with algorithms such as token bucket or sliding window counters in a shared store like Redis. Return 429 with Retry-After, and expose remaining quota in headers (many APIs use X-RateLimit-Limit/-Remaining/-Reset; an IETF draft standardises RateLimit headers). Full design in rate limiter system design.
Q30. What is CORS and why does my API call fail in the browser but work in Postman?
Browsers block JavaScript from reading cross-origin responses unless the server opts in with Access-Control-Allow-Origin. "Non-simple" requests (JSON bodies, custom headers, PUT/DELETE) trigger a preflight OPTIONS request first. Postman is not a browser, so it ignores CORS. Fix it on the server with an explicit origin allow-list; never reflect arbitrary origins together with Access-Control-Allow-Credentials: true.
Q31. Other security essentials?
- HTTPS everywhere; HSTS.
- Validate input against a schema; reject unknown fields to prevent mass assignment.
- Never put secrets or personal data in URLs (they end up in logs and browser history).
- Limit request body sizes and pagination limits.
- Sign webhooks and verify signatures in constant time.
- Log with request IDs but mask PAN, Aadhaar numbers, tokens and passwords.
Caching and Concurrency Control in REST APIs
Q32. How does HTTP caching work for APIs?
Cache-Control declares policy: public, max-age=300 for shared caching of a product catalogue; private, max-age=60 for user-specific data browsers may cache; no-store for sensitive responses. Validators let clients revalidate cheaply:
GET /v1/products/p_12
-> 200 OK
ETag: "v42"
Cache-Control: max-age=60
(after 60 s)
GET /v1/products/p_12
If-None-Match: "v42"
-> 304 Not Modified (no body; client reuses its copy)
Use Vary: Accept-Encoding, Authorization (as appropriate) so caches do not serve one user's response to another.
Q33. How do you prevent lost updates when two clients edit the same resource?
Optimistic concurrency with ETags and If-Match:
GET /v1/products/p_12 -> ETag: "v42", { "price": 49900 }
PATCH /v1/products/p_12
If-Match: "v42"
{ "price": 44900 }
-> 200 OK, ETag: "v43" (if still at v42)
-> 412 Precondition Failed (if someone else changed it first: re-fetch and retry)
Server side, the ETag is often a version column: UPDATE products SET price = ?, version = version + 1 WHERE id = ? AND version = 42; zero rows updated means 412. An API can return 428 Precondition Required to force clients to send If-Match.
Q34. How do you make an API fast?
Avoid N+1 queries behind endpoints (and N+1 API calls from clients; offer ?expand=customer,items for related data), paginate everything, compress responses (gzip or brotli), cache at the CDN and application layers, keep payloads lean, use connection keep-alive and HTTP/2, and move slow work to async jobs (Q19). Measure p95/p99 per endpoint, not averages.
API Design Interview Scenario: Orders and Payments for an E-commerce App
"Design the APIs for checkout in a Flipkart-style app: create an order, pay for it, check its status, cancel it, and list a user's orders." Here is how to run that round.
Step 1: Clarify requirements
- Clients: mobile apps and web. Authenticated users only.
- Payments via a gateway (UPI, cards, netbanking) with asynchronous confirmation.
- Retries must never double-charge or double-create orders.
- Order history must paginate well for users with years of orders.
Step 2: Resources
orders (with embedded items), payments (an order may have several attempts), and webhooks from the payment gateway.
Step 3: Endpoints
| Method and path | Purpose | Success | Notable errors |
|---|---|---|---|
POST /v1/orders + Idempotency-Key | Create order from cart contents, reserve stock | 201 + Location | 409 out of stock, 422 invalid address |
GET /v1/orders/{id} | Order details and status | 200 (ETag) | 404 (also for other users' orders) |
GET /v1/orders?status=&limit=&after= | User's orders, newest first, cursor paginated | 200 | 400 bad cursor |
POST /v1/orders/{id}/payments + Idempotency-Key | Start a payment attempt, returns gateway action (UPI intent, redirect URL) | 201 | 409 order not payable |
GET /v1/payments/{id} | Poll payment status | 200 | 404 |
POST /v1/orders/{id}/cancellation | Cancel with reason, triggers refund if paid | 202 (refund is async) | 409 already shipped |
POST /v1/webhooks/payment-gateway | Gateway notifies payment outcome | 200 quickly | 401 bad signature |
Step 4: Request and response bodies
POST /v1/orders
Authorization: Bearer eyJ...
Idempotency-Key: 0b6c1e1e-6a1f-4a3e-bf0e-1f2a9b7c3d55
{
"items": [ { "sku": "MOB-IPH-128-BLK", "quantity": 1 } ],
"shipping_address_id": "addr_19",
"coupon": "DIWALI10"
}
-> 201 Created
Location: /v1/orders/ord_9Xk2
{
"id": "ord_9Xk2",
"status": "PENDING_PAYMENT",
"items": [ { "sku": "MOB-IPH-128-BLK", "quantity": 1, "unit_price": 6990000 } ],
"amount": { "subtotal": 6990000, "discount": 699000, "total": 6291000, "currency": "INR" },
"expires_at": "2026-10-10T12:15:00+05:30",
"created_at": "2026-10-10T12:00:00+05:30"
}
Design choices worth saying out loud: money as integers in the smallest unit (paise) with an explicit currency, never floats; the server computes prices (never trust client-sent prices); timestamps in ISO 8601 with offsets; a reservation expiry so abandoned orders release stock.
Step 5: Status as a state machine
Invalid transitions return 409. The webhook handler is idempotent (keyed by the gateway's event ID), verifies the HMAC signature, and only moves the order forward. Because webhooks can be delayed or lost, a reconciliation job also queries the gateway for payments stuck in a pending state.
Step 6: Cross-cutting decisions
- Auth: OAuth access tokens; every query scoped by the caller (BOLA protection).
- Idempotency: required on both POSTs that create things or move money.
- Pagination: cursor on
(created_at, id). - Versioning:
/v1; additive changes only within it. - Rate limits: per user on order creation, stricter on payment attempts.
- Observability: request IDs returned in responses and propagated to logs.
REST vs GraphQL vs gRPC: When to Use Which
| REST | GraphQL | gRPC | |
|---|---|---|---|
| Transport and format | HTTP, usually JSON | HTTP, single endpoint, JSON | HTTP/2, Protocol Buffers (binary) |
| Data fetching | Fixed responses per endpoint; over- or under-fetching possible | Client specifies exactly the fields it needs | Strongly typed RPC methods |
| Caching | Native HTTP caching and CDNs | Harder (POST to one URL); needs client or persisted-query caching | Application-level |
| Streaming | SSE, chunked responses | Subscriptions | Built-in client, server and bidirectional streaming |
| Browser support | Native | Native | Needs gRPC-Web or a proxy |
| Best for | Public APIs, CRUD services, broad compatibility | Frontends aggregating many sources with varied screens | Internal service-to-service calls needing low latency and strict contracts |
A common real-world combination: gRPC between internal microservices, REST for public and partner APIs, and GraphQL (or a backend-for-frontend) for complex mobile and web screens. Where gateways fit in that picture is covered in API gateway system design.
Q35. How do you document and test an API?
Describe it with OpenAPI (generate from code or design-first), publish interactive docs, and generate client SDKs from the spec. Test with contract tests (the provider verifies it still satisfies consumers' expectations, for example with Pact), integration tests for each status code path, and a backward-compatibility check in CI that diffs the OpenAPI spec and flags breaking changes before they merge.
REST API design ties together much of backend interviewing. Practise it in your language of choice with the Spring Boot or Node.js interview guides, then take the same thinking into system design, where every high-level design starts with "here are the APIs".
Frequently Asked Questions
Get Ready for API and System Design Rounds
Prepflix's interview program teaches API design, system design and DSA together, with mock interviews modelled on backend loops at Indian product companies.
Explore the Prepflix Course →