Skip to content
Backend & systems

36 HTTP and REST API Interview Questions and Answers

This focused guide turns RecallDeck’s curated HTTP and REST API material into 36 interview-ready questions. Answer each one before opening the explanation, then use the examples and edge cases to repair anything vague or incomplete.

34 min read36 detailed answersReviewed Aug 24, 2026
What to remember

Organize the answer around ownership, limits, failure, and recovery. Definitions become interview-ready when they survive a concrete production scenario.

Question set

36 detailed answers

01

1. What is HTTP and how does the request-response cycle work?

Short answer: HTTP (HyperText Transfer Protocol) is a text-based application-layer client-server protocol on top of TCP (in HTTP/3 — on top of QUIC/UDP). The client sends a request, the server returns a response; the connection holds no state between requests (stateless).

In depth:

A request consists of a start line (method + path + version), headers, and an optional body:

POST /api/v1/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
Content-Length: 38

{"name": "Anna", "email": "a@ex.com"}

A response consists of a status line (version + code + reason phrase), headers, and a body:

HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/v1/users/42

{"id": 42, "name": "Anna", "email": "a@ex.com"}

⚠️ Gotcha: "HTTP only works over TCP" is incorrect for HTTP/3, which uses QUIC over UDP. And the reason phrase ("OK", "Created") is purely informational — clients should rely on the numeric code, not the text.

02

2. HTTP methods: semantics, safe and idempotent

Short answer: A method defines the intent of an operation on a resource. Safe — does not change server state (read-only). Idempotent — a repeated identical request has the same effect as a single one.

In depth:

Method Purpose Safe Idempotent Request body Response body
GET Retrieve a resource no yes
HEAD Like GET, but headers only no no
OPTIONS Discover allowed methods / CORS no yes
POST Create / perform an action yes yes
PUT Fully replace / create at a URI yes yes
PATCH Partially modify a resource ❌* yes yes
DELETE Delete a resource opt. opt.

* PATCH is not idempotent by the spec, but can be made so with proper design (e.g. replacing fields with absolute values instead of increments).

  • Safe methods (GET, HEAD, OPTIONS) can be cached, prefetched, and repeated without consequences.
  • Idempotent methods (GET, HEAD, OPTIONS, PUT, DELETE) are safe to retry on network failures.
  • HEAD is useful for checking whether a resource exists or its size (Content-Length) without downloading the body.
  • OPTIONS is used by the browser for the CORS preflight.

⚠️ Gotcha: "GET never has a body" — the HTTP/1.1 spec allows a body on GET, but its behavior is undefined; many proxies and servers ignore it or drop the connection. Don't pass parameters in a GET body. Also: safe is not the same as idempotent — DELETE is idempotent but not safe.

03

3. What's the difference between POST, PUT, and PATCH?

Short answer: POST creates a resource (the server assigns the URI) and is not idempotent; PUT fully replaces a resource at a known URI and is idempotent; PATCH partially updates a resource.

In depth:

POST — creating a subordinate resource in a collection; the server assigns the URI:

POST /api/v1/articles HTTP/1.1
Content-Type: application/json

{"title": "REST", "body": "..."}
HTTP/1.1 201 Created
Location: /api/v1/articles/777

Two identical POSTs will create two articles — not idempotent.

PUT — the client knows the URI and sends a full representation; the resource is created or replaced in its entirety:

PUT /api/v1/articles/777 HTTP/1.1
Content-Type: application/json

{"title": "REST v2", "body": "new text", "tags": []}

A repeated identical PUT leaves the resource in the same state — idempotent. Important: fields not specified in the body are considered nulled/deleted (a full replacement).

PATCH — a partial modification; only the fields being changed are sent:

PATCH /api/v1/articles/777 HTTP/1.1
Content-Type: application/json

{"title": "REST v3"}

The body field stays the same. There is a formalized application/json-patch+json (RFC 6902) with add/remove/replace operations.

⚠️ Gotcha: a common mistake is using PUT for a partial update. If PUT accepts a partial body and doesn't null the remaining fields, it semantically turns into PATCH and breaks the contract. If a client sends PUT without a field — it should disappear, otherwise it's a violation of the semantics.

04

4. What is idempotency and why do you need it?

Short answer: An operation is idempotent if performing it N times has the same effect on server state as performing it once. This lets you safely repeat requests on network failures.

In depth:

Idempotency concerns the effect on the server, not the identity of the responses. For example, two DELETEs of the same resource: the first returns 204, the second 404, but the server state (the resource is absent) is the same — that's idempotent.

Idempotent methods: GET, HEAD, OPTIONS, PUT, DELETE. Not idempotent by default: POST (creates a new entity on each call), PATCH (depends on the semantics — the increment balance += 10 is not idempotent, the assignment balance = 100 is idempotent).

Why: on a timeout the client doesn't know whether the request arrived or not. An idempotent request can be safely repeated. A non-idempotent one (a payment POST) is dangerous to repeat — you could create a duplicate.

⚠️ Gotcha: idempotency is not about "the same response is returned." A view counter implemented on GET (GET /article?inc_views=1) breaks the contract: GET should be safe and idempotent, but here it changes state. Search engines and prefetch agents will "inflate" the views.

05

5. Idempotency key for payments

Short answer: An Idempotency-Key is a unique identifier that the client passes in a header so that the server can recognize a repeat of a non-idempotent request (e.g. a payment POST) and not perform the operation twice.

In depth:

POST creates a resource — it's not idempotent. But for payments, a retry after a timeout must not charge the money twice. The solution — the client generates a unique key (UUID) and sends it:

POST /api/v1/payments HTTP/1.1
Idempotency-Key: 7c4e1f2a-1d3b-4f5a-9c8e-6b2a1d0e3f4c
Content-Type: application/json

{"amount": 5000, "currency": "RUB", "order_id": "A-123"}

Server logic:

  1. On the first request with this key — perform the operation, save (key → result + status) in a store (e.g. Redis with a TTL).
  2. On a repeat with the same key — do not perform it again, but return the saved result (usually the same 201 and the same body).
  3. If the first request is still in progress (in-flight) — return 409 Conflict or wait.
  4. If the body on the repeat differs from the original — return 422 (the key was reused with different data).

That's how Stripe, PayPal, and YooKassa do it. The key is stored with a TTL (e.g. 24 hours).

⚠️ Gotcha: the key must be generated by the client (before sending), not the server, otherwise a new key would be generated on a retry and the protection wouldn't work. Atomicity also matters: the "does the key exist" check and the write must be transactional (via INSERT ... ON CONFLICT or Redis SETNX), otherwise two parallel attempts would both pass the check and charge twice.

06

6. HTTP status codes: the 1xx-5xx classes

Short answer: The first digit of the code defines the class: 1xx — informational, 2xx — success, 3xx — redirection, 4xx — client error, 5xx — server error.

In depth:

  • 1xx Informational — intermediate. 100 Continue (you may send the body), 101 Switching Protocols (upgrade to WebSocket).
  • 2xx Success — 200 OK, 201 Created, 202 Accepted (taken for asynchronous processing), 204 No Content, 206 Partial Content (range requests).
  • 3xx Redirection — 301 Moved Permanently, 302 Found, 303 See Other, 304 Not Modified, 307/308 (preserve the method).
  • 4xx Client Error — 400, 401, 403, 404, 405 Method Not Allowed, 409 Conflict, 410 Gone, 422 Unprocessable Entity, 429 Too Many Requests.
  • 5xx Server Error — 500 Internal Server Error, 501 Not Implemented, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout.

⚠️ Gotcha: returning 200 OK with a body of {"error": "..."} is an antipattern (that's what bad SOAP/legacy APIs do). Clients and monitoring rely on the status code; an error should carry a 4xx/5xx, otherwise failures "hide" under a success code.

07

7. Key status codes and when to use them

Short answer: You need to know at least 200/201/204, 301/302/304, 400/401/403/404/409/422/429, 500/502/503 and their semantics.

In depth:

Code When
200 OK Successful GET/PUT/PATCH with a body
201 Created A resource was created (usually after POST), a Location is desirable
204 No Content Success without a body (DELETE, PUT with no return)
301 Moved Permanently The resource is permanently at a new URL (cached, affects SEO)
302 Found A temporary redirect
304 Not Modified The client's cache is current (response to a conditional GET)
400 Bad Request Malformed syntax/invalid JSON, a broken request
401 Unauthorized Not authenticated (missing/invalid credentials)
403 Forbidden Authenticated, but lacking permissions
404 Not Found The resource was not found
409 Conflict A state conflict (a duplicate, a concurrent change)
422 Unprocessable Entity Syntax is fine, but semantically invalid (business-rule validation)
429 Too Many Requests The rate limit was exceeded
500 Internal Server Error An unhandled exception on the server
502 Bad Gateway The upstream returned an invalid response (a proxy/service problem)
503 Service Unavailable The service is temporarily unavailable (overload/maintenance), a Retry-After is desirable

⚠️ Gotcha: 502 vs 503 vs 504. 502 — the proxy received a broken response from the upstream; 503 — the service itself is unavailable/overloaded; 504 — the upstream didn't respond in time (a timeout). They're constantly confused when debugging load balancers and API gateways.

08

8. 401 vs 403, 400 vs 422, 201 vs 204

Short answer: 401 — "who are you?" (no authentication), 403 — "I know who you are, but you can't" (no authorization). 400 — invalid syntax, 422 — valid syntax but business rules are violated. 201 — a resource was created (with a body/Location), 204 — success without a body.

In depth:

401 vs 403:

  • 401 Unauthorized (really "not authenticated") — the token is missing, expired, or invalid. The server must return a WWW-Authenticate header.
  • 403 Forbidden — the identity is established, but there are no rights for the action (an ordinary user tries to delete someone else's resource). Retrying with the same token won't help.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"

400 vs 422:

  • 400 Bad Request — the server couldn't parse the request: broken JSON, a missing required header, a wrong type.
  • 422 Unprocessable Entity — the JSON is valid and parsed, but the data fails business validation: the email is already taken, the date is in the past, a negative amount.

201 vs 204:

  • 201 Created — a new resource was created; the body usually contains the representation itself, and the Location header its URI.
  • 204 No Content — the operation succeeded, but there's nothing to return (typical for DELETE, sometimes for PUT/PATCH).

⚠️ Gotcha: many frameworks send 400 on validation errors by default. A clear 400/422 split isn't mandatory (422 isn't from core HTTP, but from WebDAV), but it's useful: it lets the client distinguish "I formed the request incorrectly" from "the data didn't pass validation." The main thing is to be consistent across the whole API.

09

9. HTTP headers: standard and custom

Short answer: Headers are metadata for the request/response. The key ones: Content-Type, Accept, Authorization, Cache-Control, ETag, Cookie, User-Agent. Custom ones used to be written with an X- prefix; now it's recommended without it.

In depth:

Header Direction Purpose
Content-Type both the body format: application/json, text/html, multipart/form-data
Accept request which formats the client is ready to accept (content negotiation)
Authorization request credentials: Bearer <token>, Basic <base64>
Cache-Control both the caching policy: no-cache, max-age=3600, private
ETag response the resource's version "fingerprint" for conditional requests
Cookie / Set-Cookie request / response sending and setting cookies
User-Agent request information about the client (browser/SDK)
Location response the URI of a created resource or the target of a redirect
Content-Length both the body size in bytes
Accept-Encoding / Content-Encoding request / response compression (gzip, br)

Custom headers (e.g. X-Request-Id, Idempotency-Key): RFC 6648 recommends not using the X- prefix for new headers, but in practice it lives on (X-Forwarded-For, X-Real-IP).

⚠️ Gotcha: header names are case-insensitive (Content-Type == content-type), but values are not. In HTTP/2 and HTTP/3 names are transmitted in lowercase. Don't store secrets in logged headers without masking (Authorization often ends up in proxy logs).

10

10. HTTP/1.0 vs 1.1 vs 2 vs 3

Short answer: 1.0 — a connection per request; 1.1 — keep-alive and pipelining; 2 — a binary protocol with multiplexing over a single TCP; 3 — on top of QUIC/UDP with no head-of-line blocking at the transport level.

In depth:

HTTP/1.0 — a new TCP connection for each request (expensive: a handshake every time).

HTTP/1.1:

  • Connection: keep-alive by default — connection reuse.
  • Pipelining (several requests without waiting for responses) — but barely used because of head-of-line blocking: a slow first response blocks the rest.
  • Chunked transfer encoding, virtual hosts (a mandatory Host).
  • Browsers open ~6 parallel connections per domain (hence "domain sharding").

HTTP/2:

  • A binary format instead of text.
  • Multiplexing: many parallel streams within a single TCP connection, without HOL blocking at the HTTP level.
  • Header compression (HPACK).
  • Server Push (the server sends resources ahead of time) — in practice rarely used and being removed from browsers.
  • HOL blocking remains at the TCP level: the loss of a single packet stalls all streams.

HTTP/3:

  • On top of QUIC (a UDP-based transport).
  • Solves TCP HOL blocking: streams are independent at the transport level.
  • TLS 1.3 is built in, 0-RTT/1-RTT handshake — faster connection establishment.
  • Better on packet loss and network changes (mobile clients, connection migration).

⚠️ Gotcha: HTTP/2 doesn't eliminate HOL blocking entirely — it remains at the TCP level. That's exactly why HTTP/3 on QUIC/UDP appeared. And Server Push in HTTP/2 is not a "silver bullet"; Chrome ripped out its support.

11

11. HTTPS/TLS: why it's needed and a brief overview of the handshake

Short answer: HTTPS is HTTP over TLS. TLS provides encryption (confidentiality), integrity, and server authentication via a certificate. The handshake negotiates keys and verifies the certificate.

In detail:

Why: without TLS, traffic travels in plaintext — an ISP/Wi-Fi/proxy can read and tamper with it (MITM). TLS provides three guarantees: encryption, integrity (MAC/AEAD), and server authentication.

Simplified TLS 1.2 handshake:

  1. ClientHello — the client sends supported TLS versions, a set of cipher suites, and a random value.
  2. ServerHello — the server picks a cipher and sends its certificate (the chain up to a trusted CA).
  3. The client verifies the certificate (CA signature, validity period, domain in CN/SAN).
  4. Key exchange (ECDHE) → a shared symmetric session key.
  5. From there, data is encrypted symmetrically (fast).

TLS 1.3 reduced the handshake to 1-RTT (and 0-RTT for resumed connections) and dropped legacy ciphers.

⚠️ Gotcha: TLS authenticates the server (via its certificate) but by default not the client. For mutual authentication you need mTLS (client certificates). Also: the "green padlock" only means the connection is encrypted and the certificate is valid — not that the site is safe; phishing happens over HTTPS too. (Certificate/CA details are in the networking file.)

12

12. Why is HTTP stateless and how is state maintained?

Short answer: Each HTTP request is self-contained — the server isn't required to remember previous ones. This simplifies scaling (any request can go to any instance). State is "attached" via cookies+sessions or tokens.

In detail:

Stateless means: all the information needed to process a request is in the request itself. The server keeps no context between requests. Pros: horizontal scaling, fault tolerance, simple caching — any instance behind a load balancer can handle the request.

But applications need state (who's logged in, the cart). Approaches:

  • Session-based (server-side state): the server stores the session and gives the client Set-Cookie: session_id=.... On each request the server looks up the data by id from a store (Redis/DB). Downside — it requires a shared store or sticky sessions.
  • Token-based (stateless auth): a signed JWT carries claims (user id, roles). The server verifies the signature and stores nothing. Pro — scalable; con — hard to revoke before expiry.
  • Cookie — the general transport mechanism (it holds both the session id and sometimes the token itself).
HTTP/1.1 200 OK
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax

⚠️ Gotcha: storing sessions in an instance's local memory breaks the stateless advantage — when load balancing routes the user to another instance, they get "logged out." You need either a shared store (Redis), or sticky sessions (which hurt fault tolerance), or stateless tokens.

13

13. What is REST and its principles

Short answer: REST (Representational State Transfer) is an architectural style for distributed systems (Roy Fielding's dissertation). It's based on resources, a uniform interface, and stateless interaction over HTTP.

In detail:

The six REST constraints:

  1. Client-Server — separation of UI and data storage concerns.
  2. Stateless — the server keeps no client context between requests.
  3. Cacheable — responses must be marked as cacheable or not.
  4. Uniform Interface — uniformity: resources identified by URIs, manipulation through representations, self-descriptive messages, HATEOAS.
  5. Layered System — the client doesn't know whether it talks directly to the server or through a proxy/load balancer.
  6. Code on Demand (optional) — the server may send executable code (e.g. JS).

The key idea: resources (nouns) are identified by URIs, and HTTP methods (verbs) define the operations on them.

⚠️ Gotcha: most "REST APIs" in practice are Richardson level 2 (resources + methods + status codes), without HATEOAS. Strictly speaking, without HATEOAS an API isn't "truly RESTful" (per Fielding), but the industry calls such APIs REST. In an interview, call out this nuance.

14

14. Richardson Maturity Model

Short answer: The Richardson model describes 4 levels of "RESTfulness": 0 — RPC over HTTP, 1 — resources, 2 — HTTP verbs and status codes, 3 — HATEOAS.

In detail:

  • Level 0 (The Swamp of POX) — a single endpoint, everything via POST, HTTP as transport for RPC. Example: POST /api with a body describing the method.
  • Level 1 — Resources. Separate URIs per entity appear: /users/1, /orders/5. But methods aren't yet used semantically (everything via POST/GET).
  • Level 2 — HTTP verbs and status codes. Proper use of GET/POST/PUT/DELETE and status codes (200/201/404...). Most real-world APIs are here.
  • Level 3 — HATEOAS. Responses contain hyperlinks to available actions; the client "navigates" the API instead of hardcoding URIs.
{
  "id": 5,
  "status": "pending",
  "_links": {
    "self": { "href": "/orders/5" },
    "cancel": { "href": "/orders/5/cancel", "method": "POST" }
  }
}

⚠️ Gotcha: the levels are a heuristic, not a strict ladder. Level 3 (HATEOAS) is rare in practice: it complicates both client and server, and the payoff is debatable for machine-to-machine APIs with a fixed contract. Don't confuse marketing "RESTful" with actual level 3.

15

15. What "RESTful" means: designing resource URLs

Short answer: URLs should identify resources (plural nouns), not actions. Verbs are expressed via HTTP methods. Nesting reflects the resource hierarchy.

In detail:

Good:

GET    /users              # list of users
POST   /users              # create
GET    /users/42           # a single user
PUT    /users/42           # replace
PATCH  /users/42           # partial update
DELETE /users/42           # delete
GET    /users/42/orders    # orders of user 42 (nesting)
GET    /users/42/orders/7  # a specific order

Rules:

  • Plural nouns: /users, not /user or /getUser.
  • No verbs in the URI: not /createUser, not /users/42/delete.
  • Nesting for "belongs-to" relationships: /users/42/orders. But don't go deeper than 2 levels — /orders/7 is better than /users/42/orders/7/items/3/....
  • kebab-case or lowercase: /order-items, not /orderItems.
  • Actions that don't map onto CRUD are modeled as a "controller resource": POST /orders/7/cancel, POST /payments/9/refund — an acceptable compromise.

⚠️ Gotcha: don't stuff actions into query parameters (GET /users?action=delete&id=42) — that's level-0 RPC and violates the safe semantics of GET. And don't breed deep nesting: deep URIs are fragile and awkward. Filters go in the query (?status=active), not the path.

16

16. HATEOAS

Short answer: HATEOAS (Hypermedia As The Engine Of Application State) — responses contain hyperlinks to the possible next actions, so the client doesn't hardcode URIs and transitions.

In detail:

The idea: the client knows only the entry point and from there "follows links," like a human browsing a website. In every response the server states what can be done with the resource in its current state.

{
  "order_id": 7,
  "status": "paid",
  "total": 5000,
  "_links": {
    "self":    { "href": "/orders/7" },
    "invoice": { "href": "/orders/7/invoice" },
    "refund":  { "href": "/orders/7/refund", "method": "POST" }
  }
}

If the order isn't paid yet, there's no refund link, but a pay link appears instead. The client reacts to the presence of a link rather than hardcoding business logic.

Formats: HAL (_links), JSON:API (links), Siren.

⚠️ Gotcha: HATEOAS sounds elegant but is almost never adopted in reality: clients know the API structure anyway, and the overhead (generating links, parsing hypermedia) is high. In an interview, what matters is knowing what it is and why level 3 is rare — not treating it as mandatory.

17

17. API versioning: URL vs header

Short answer: The API version is set via the URL (/v1/users), a header (Accept: application/vnd.api+json; version=1), or a query parameter. URL versioning is the most popular and the most visible.

In detail:

1. In the URL path (most common):

GET /api/v1/users

Pros: immediately visible, easy to route and cache, convenient to test in a browser. Cons: it "violates" the idea that a URI identifies a single resource.

2. In a header (media type versioning):

GET /api/users
Accept: application/vnd.example.v2+json

Pros: the URI stays clean and stable. Cons: less visible, harder to test manually, easy to forget.

3. Custom header:

GET /api/users
X-API-Version: 2

4. Query parameter: GET /api/users?version=2 — simple, but clutters the query.

⚠️ Gotcha: version only on breaking changes. Adding a new optional field or a new endpoint is backward-compatible and doesn't require a new version. Don't breed v2, v3 for every change — maintaining old versions is expensive. Semantics: breaking changes = a new major version.

18

18. Pagination: offset vs cursor

Short answer: Offset pagination (?limit=20&offset=40) is simple but slow at large offsets and unstable against inserts. Cursor pagination (?limit=20&cursor=...) is stable and fast, but doesn't let you jump to an arbitrary page.

In detail:

Offset / page-based:

GET /users?limit=20&offset=40        # or ?page=3&per_page=20
  • Simple, supports "jump to page N."
  • Cons: OFFSET 100000 in SQL is slow (the DB scans and discards rows). With inserts/deletes between requests, pages "shift" — you can see a duplicate or skip a record.

Cursor / keyset:

GET /users?limit=20&cursor=eyJpZCI6MTIzfQ==
{
  "data": [ ... ],
  "next_cursor": "eyJpZCI6MTQzfQ==",
  "has_more": true
}
  • The cursor encodes a position (e.g. the id of the last element). SQL: WHERE id > :last_id ORDER BY id LIMIT 20 — uses the index, fast at any depth.
  • Stable against inserts. Cons: you can't jump to an arbitrary page, and you need a stable sort on a unique field.

⚠️ Gotcha: offset pagination without a stable sort (ORDER BY created_at with equal timestamps) produces nondeterministic ordering and duplicates/skips at page boundaries. Sort by a unique key (or a composite that includes id). For an infinite feed, a cursor is almost always the right choice.

20

20. A unified error-handling format

Short answer: All API errors should have a single machine-readable body format with a code, a message, and details, plus a correct HTTP status. The standard is RFC 7807/9457 (Problem Details).

In detail:

Problem Details format (Content-Type: application/problem+json):

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json

{
  "type": "https://api.example.com/errors/validation",
  "title": "Validation failed",
  "status": 422,
  "detail": "The email field is already in use",
  "instance": "/users",
  "errors": [
    { "field": "email", "code": "duplicate", "message": "email is already taken" }
  ]
}

Principles:

  • A stable, machine-readable code/type (the client branches on it, not on the text).
  • A human-readable message/detail.
  • Per-field details for validation.
  • Don't leak stack traces and internal details in production.
  • A trace_id/request_id for correlating with logs.

⚠️ Gotcha: returning errors in different formats on different endpoints (a string here, an object there, a 200 with success:false somewhere else). The client won't be able to handle errors uniformly. Fix the format at the middleware/exception-handler level.

21

21. Rate limiting and 429

Short answer: Rate limiting caps the number of requests per period; on exceeding it you return 429 Too Many Requests with a Retry-After and/or RateLimit-* header.

In detail:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30

Algorithms: Token Bucket (smooth bursts), Leaky Bucket, Fixed Window, Sliding Window Log/Counter. Token bucket and sliding window are the most common.

Limits are applied per key: API key, user id, IP. Counters are usually stored in Redis (atomic increments with a TTL).

Why: protection from DDoS/abuse, fair resource sharing, protecting the backend from overload, and billing tiers (free vs paid).

⚠️ Gotcha: rate limiting by IP breaks behind NAT/proxies (many users behind one IP) and is bypassed by changing IP. It's better to limit by the authenticated subject. Retry-After also matters so the client knows when to retry and implements exponential backoff instead of hammering the wall.

22

22. Idempotency, retries, and at-least-once

Short answer: In distributed systems delivery is often at-least-once (a message may arrive more than once). So that retries don't duplicate the effect, handlers are made idempotent (dedup by key/ID).

In detail:

Delivery guarantees:

  • at-most-once — no more than once, but loss is possible.
  • at-least-once — no fewer than once, duplicates possible. The practical default (retries on timeouts, queue redelivery).
  • exactly-once — the ideal, strictly unattainable; emulated via at-least-once + idempotency/deduplication.

Strategy: at-least-once on the transport + an idempotent receiver = an "exactly once" effect. Deduplicate by message_id/Idempotency-Key (a table of processed IDs, a unique index).

Retries on the client: you may retry idempotent methods (GET, PUT, DELETE); for POST — only with an idempotency key. Use exponential backoff + jitter, and respect Retry-After.

⚠️ Gotcha: retrying a POST without an idempotency key is dangerous — a timeout doesn't mean "it didn't happen." The request may have arrived and been processed while the response was lost; a retry creates a duplicate (a double charge). Always distinguish "it didn't arrive" from "it arrived but the response was lost."

23

23. REST vs RPC vs GraphQL vs gRPC

Short answer: REST — resources over HTTP verbs; RPC — calling remote procedures (actions); GraphQL — a single endpoint with a flexible query language; gRPC — binary RPC on protobuf over HTTP/2.

In detail:

Criterion REST RPC (JSON-RPC) GraphQL gRPC
Model resources/nouns procedures/actions data graph procedures/actions
Transport HTTP HTTP HTTP (usually POST) HTTP/2
Format JSON JSON JSON protobuf (binary)
Endpoints many URIs one (method in body) one (/graphql) services/methods
Over/under-fetch possible possible solved (client picks fields) fixed contract
Schema/types OpenAPI (opt.) weak strong (SDL) strong (.proto)
Streaming SSE/WS separately no subscriptions built-in
HTTP caching excellent weak weak (POST) none
Browser natively yes yes needs grpc-web

When to use what:

  • REST — public APIs, CRUD, when you need HTTP caching and simplicity.
  • RPC — internal services with operations that don't map onto CRUD.
  • GraphQL — clients with different data needs (mobile + web), aggregating many sources, fighting over/under-fetching.
  • gRPC — high-load inter-service communication (microservices), when you need streaming and low latency.

⚠️ Gotcha: GraphQL isn't "better than REST" by default. It shifts complexity to the server: HTTP caching barely works (everything is POST to one URL), it's easy to hit N+1 queries and heavy nested queries (you need DataLoader, depth/complexity limits). For simple CRUD, REST is simpler and more efficient.

24

24. Over-fetching and under-fetching: how GraphQL solves them

Short answer: Over-fetching — the server returns more data than the client needs. Under-fetching — there's not enough data, so multiple requests are needed. GraphQL lets the client request exactly the fields it needs in a single request.

In detail:

In REST: GET /users/42 returns the whole user object even if you only need the name — over-fetching. And to show a user with their orders and addresses, you need GET /users/42, GET /users/42/orders, GET /users/42/addressesunder-fetching (N+1 round-trips).

GraphQL — the client describes what it wants and gets it in a single request:

query {
  user(id: 42) {
    name
    orders(last: 3) { id total }
  }
}
{ "data": { "user": { "name": "Anna", "orders": [ ... ] } } }

Partial solutions in REST: sparse fieldsets (?fields=name), embed/expand (?include=orders), composite endpoints (BFF — Backend for Frontend).

⚠️ Gotcha: GraphQL cures over/under-fetching on the client, but on the server an N+1 problem arises (each field's resolver hits the DB). It's solved with DataLoader (batching + per-request cache). Without it, GraphQL can be slower than REST.

25

25. gRPC: protobuf, HTTP/2, streaming

Short answer: gRPC is Google's RPC framework: the contract is in .proto, serialization is binary protobuf, the transport is HTTP/2, and it supports four kinds of streaming. Fast and type-safe, ideal for inter-service communication.

In detail:

The contract is described in .proto, and from it a client and server are generated in many languages:

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
  rpc ListUsers (ListRequest) returns (stream User);   // server streaming
}
message GetUserRequest { int32 id = 1; }
message User { int32 id = 1; string name = 2; }

Kinds of calls:

  • Unary — request → response (like an ordinary RPC).
  • Server streaming — one request → a stream of responses.
  • Client streaming — a stream of requests → one response.
  • Bidirectional streaming — a two-way stream over a single HTTP/2 connection.

Advantages: a compact binary format (smaller than REST/JSON), HTTP/2 multiplexing, a strict schema and code generation, low latency. Cons: the binary isn't human-readable, it doesn't work directly in a browser (you need grpc-web + a proxy), and HTTP caching is weaker.

⚠️ Gotcha: gRPC requires HTTP/2 end-to-end; not every proxy/load balancer/legacy infrastructure supports it. And you can't debug binary protobuf by hand (the way you can with curl and JSON) — you need grpcurl. For public browser-facing APIs, gRPC is usually awkward.

26

26. WebSockets vs polling vs SSE

Short answer: Polling — the client periodically queries the server; long polling — holds a request open until an event; SSE — a one-way stream of events server→client; WebSocket — a full-duplex two-way channel.

In detail:

Technology Direction Transport When
Short polling client pulls periodically plain HTTP simplicity, infrequent updates
Long polling server responds on an event HTTP (holds the request) realtime without WS, fallback
SSE server → client (one-way) HTTP, text/event-stream feeds, notifications, progress
WebSocket duplex (both directions) HTTP upgrade → ws chat, games, collaboration

Short pollingsetInterval(fetch, 5000). Simple, but extra traffic and latency.

Long polling — the request hangs, the server responds when data appears, the client immediately reopens. Fewer empty responses, but it holds connections.

SSE — the server sends events within a single long HTTP response:

GET /events HTTP/1.1
Accept: text/event-stream
data: {"type":"new_message","id":5}

data: {"type":"typing"}

One-way, auto-reconnect, runs over plain HTTP, works with HTTP/2. Not suitable for sending from the client.

WebSocket — after the handshake (Upgrade: websocket, response 101 Switching Protocols) — a persistent two-way channel with low per-message overhead.

⚠️ Gotcha: WebSocket isn't always the right choice. If data flows only server→client (stock quotes, notifications), SSE is simpler: it runs over HTTP, has auto-reconnect out of the box, and is easier to proxy and scale. WebSocket is harder to load-balance (stateful connections) and isn't cacheable.

27

27. CORS and preflight

Short answer: CORS (Cross-Origin Resource Sharing) is a browser mechanism that allows or denies requests from one origin to another. For "non-simple" requests, the browser first sends a preflight OPTIONS.

In detail:

Because of the Same-Origin Policy, the browser by default blocks JS requests to a different origin (scheme+host+port). CORS lets the server explicitly allow cross-origin requests via headers.

Simple requests (GET/POST/HEAD with "safe" headers) go straight through, and the server responds:

Access-Control-Allow-Origin: https://app.example.com

Preflight — for "non-simple" requests (PUT/DELETE/PATCH methods, custom headers, Content-Type: application/json) the browser first sends an OPTIONS:

OPTIONS /api/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Max-Age: 86400

If the response allows it, the browser sends the real request.

⚠️ Gotcha: CORS protects the browser, not the server. curl/Postman/mobile clients ignore CORS. It's not a substitute for authentication/authorization. And with Access-Control-Allow-Credentials: true you can't use Allow-Origin: * — you need a specific origin (details in the auth file).

28

28. Content negotiation

Short answer: Content negotiation is a mechanism where the client signals its desired format/language/encoding via Accept* headers, and the server picks the most suitable representation of the resource.

In detail:

The client states preferences with weights (q-factors):

GET /report HTTP/1.1
Accept: application/json;q=0.9, application/xml;q=0.5
Accept-Language: ru-RU, en;q=0.7
Accept-Encoding: br, gzip

The server picks and responds:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Language: ru-RU
Content-Encoding: br
Vary: Accept, Accept-Language, Accept-Encoding

The Vary header tells caches/CDNs that the response depends on these headers (you can't serve an XML cache entry to someone who asks for JSON).

⚠️ Gotcha: forgetting Vary during content negotiation — a CDN/proxy caches one variant (e.g. the gzip version) and serves it to a client that doesn't support it, or serves JSON to someone who asked for XML. Vary is mandatory for correct caching of negotiated content.

29

29. Cookies: attributes and types

Short answer: A cookie is a key-value pair the server sets via Set-Cookie and the browser returns in Cookie. Attributes control its lifetime, scope, and security.

In detail:

Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax; Domain=example.com; Path=/; Max-Age=3600
Attribute Purpose
HttpOnly inaccessible from JS (document.cookie) — protection against XSS theft
Secure sent only over HTTPS
SameSite Strict/Lax/None — controls sending in cross-site requests (CSRF protection)
Domain which domains it applies to (including subdomains)
Path which paths it applies to
Max-Age / Expires lifetime

Types:

  • Session cookies — no Max-Age/Expires, deleted when the browser closes.
  • Persistent cookies — with an explicit expiry, surviving a browser restart.

⚠️ Gotcha: SameSite=None must be accompanied by Secure, otherwise the browser rejects the cookie. Modern browsers default to SameSite=Lax. For authentication cookies always use HttpOnly + Secure — otherwise XSS steals the session and a sniffer intercepts it over HTTP.

30

30. HTTP caching: Cache-Control, ETag, 304, CDN

Short answer: HTTP caching is controlled by the Cache-Control header (how long and where to cache) and the ETag/Last-Modified validators for conditional requests that return 304 Not Modified with no body.

In detail:

Freshness (Cache-Control):

Cache-Control: public, max-age=3600
Cache-Control: private, no-cache
Cache-Control: no-store
  • max-age=N — how many seconds the response is "fresh."
  • public — may be cached in shared caches (CDN); private — only in the browser.
  • no-cache — may be cached, but must be revalidated before use.
  • no-store — don't cache at all (sensitive data).

Validation (conditional requests): The server returns an ETag (a hash/version) or Last-Modified. On the next request the client sends:

GET /avatar.png HTTP/1.1
If-None-Match: "v3-abc"

If the resource hasn't changed:

HTTP/1.1 304 Not Modified
ETag: "v3-abc"

304 with no body — saved traffic; the browser takes the resource from cache.

CDN — geographically distributed caching nodes. They respect Cache-Control/ETag/Vary and serve static/cacheable responses closer to the user, offloading the origin.

⚠️ Gotcha: no-cache != no-store. no-cache allows storing the response but requires revalidation before use; no-store forbids storing it at all. For private data (banking, personal data) you need exactly no-store. A common mistake is thinking no-cache means "don't cache."

31

31. Compression: gzip and brotli

Short answer: Compressing the response body reduces traffic. The client advertises support in Accept-Encoding, the server compresses and sets Content-Encoding. gzip is universal; brotli (br) is more efficient for text.

In detail:

GET /api/data HTTP/1.1
Accept-Encoding: br, gzip, deflate
HTTP/1.1 200 OK
Content-Encoding: br
Vary: Accept-Encoding
  • gzip — ubiquitous support, a good speed/compression tradeoff.
  • brotli (br) — compresses text (HTML/CSS/JS/JSON) better, especially at the highest levels, but is slower to compress; ideal for static assets compressed ahead of time.
  • deflate — outdated, better avoided.

It's worth compressing text formats (JSON, HTML, CSS, JS). Already-compressed ones (JPEG, PNG, MP4, zip) are pointless to compress — no gain, just CPU.

⚠️ Gotcha: forgetting Vary: Accept-Encoding — a CDN may serve a gzip response to a client that doesn't support it. Also: compressing + encrypting sensitive data in the same response is vulnerable to BREACH/CRIME-style attacks (the compressed response size can leak secrets) — don't compress responses that mix secrets with user input.

32

32. multipart/form-data and file uploads

Short answer: multipart/form-data is a body format for sending binary files and form fields together. Each part is separated by a boundary and has its own headers.

In detail:

POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----X9Z

------X9Z
Content-Disposition: form-data; name="title"

My document
------X9Z
Content-Disposition: form-data; name="file"; filename="doc.pdf"
Content-Type: application/pdf

<binary PDF data>
------X9Z--
  • boundary separates the parts; each part has its own Content-Disposition and an optional Content-Type.
  • Suitable for mixed data (text fields + files).
  • Alternative: application/x-www-form-urlencoded (text only, no files).

For large files:

  • Chunked / resumable upload (tus, S3 multipart upload) — uploading in parts with the ability to resume.
  • Presigned URLs — the client uploads directly to object storage (S3), bypassing the backend.

⚠️ Gotcha: don't load large files into server memory in full — you need streaming to disk/storage and a size limit (413 Payload Too Large). And always validate type/size on the server: the client's Content-Type can be spoofed, so check the "magic bytes" and restrict extensions so nobody uploads an executable.

33

33. URL structure: scheme/host/path/query/fragment

Short answer: A URL consists of a scheme, a host (+port), a path, a query string, and a fragment. Path parameters identify the resource; query parameters filter/configure.

In detail:

https://api.example.com:443/v1/users/42?fields=name&active=true#section
└─┬─┘   └──────┬───────┘└┬┘└────┬─────┘└────────┬────────────┘└──┬──┘
scheme       host      port    path           query          fragment
  • scheme — the protocol (https, http, ws).
  • host — a domain or IP; port — optional (443 for https, 80 for http).
  • path — a hierarchical path to the resource.
  • querykey=value pairs joined by & after ?.
  • fragment (#...) — an anchor, not sent to the server (handled by the browser).

Path vs query params:

  • Path — identifies a specific resource (mandatory, hierarchical): /users/42.
  • Query — optional modifiers of a collection: filter, sort, pagination: ?status=active&limit=20.

⚠️ Gotcha: the fragment (#...) never reaches the server — you can't put API data there. Also, don't put secrets (tokens) in the query — they end up in server logs, proxies, browser history, and the Referer header. Tokens go in the Authorization header.

34

34. Webhooks: why and how to secure them

Short answer: A webhook is an HTTP callback: instead of a third-party service polling your API, the service itself sends a POST to your URL on an event. It's "push" instead of "pull." Security is via signing the body.

In detail:

Example: on a successful payment, the payment provider sends you a POST:

POST /webhooks/payments HTTP/1.1
Content-Type: application/json
X-Signature: t=1700000000,v1=5257a869e7...

{"event":"payment.succeeded","payment_id":"p_123","amount":5000}

Security and reliability:

  • Signature (HMAC): the provider signs the body with a secret, you recompute the HMAC and compare — this confirms authenticity and integrity.
  • Replay protection: check the timestamp in the signature and reject old requests.
  • HTTPS is mandatory.
  • Idempotency: webhooks are delivered at-least-once — process them by event_id with deduplication (the same event may arrive twice).
  • A fast 2xx: respond quickly (200) and move heavy processing to a queue; otherwise the provider considers delivery failed and will retry.

⚠️ Gotcha: trusting a webhook body without verifying the signature — anyone can forge payment.succeeded and get goods for free. Compare signatures with a constant-time comparison (protection against timing attacks), not a plain ==. And don't forget deduplication — redelivery must not hand out goods twice.

35

35. Why idempotency and where does it save you?

Short answer: Idempotency lets you safely retry requests over an unreliable network without creating duplicates or side effects. It saves you on timeouts, retries, and message redelivery.

In detail:

The network is unreliable: a request may arrive while the response is lost. The client sees a timeout and doesn't know whether the operation completed. Options:

  1. Don't retry — risk losing the operation.
  2. Retry an idempotent operation — safe, the effect is the same.
  3. Retry a non-idempotent one (a payment POST) without protection — a duplicate (a double charge).

Where it saves you:

  • Payments: an idempotency key prevents charging twice on a retry.
  • Message queues: at-least-once delivery → duplicates; an idempotent consumer (dedup by message_id) makes the effect "exactly-once."
  • Distributed transactions / Saga: steps are repeatable on failures.
  • Infrastructure (Terraform, k8s): desired state is applied idempotently — a repeated apply breaks nothing.

⚠️ Gotcha: idempotency must be designed up front — it can hardly be "bolted on" over a finished non-idempotent system. If the architecture inherently allows at-least-once (and in distributed systems that's the norm), handlers must be idempotent from day one.

36

36. How is REST better or worse than GraphQL?

Short answer: REST is simpler, caches beautifully over HTTP, and is predictable; GraphQL is more flexible for the client (no over/under-fetching) and is great with many heterogeneous clients, but it pushes complexity onto the server and breaks HTTP caching.

In detail:

REST is better when:

  • Simple CRUD, the resource model maps naturally.
  • You need HTTP caching (CDN, ETag, 304) — works out of the box.
  • A public API with a clear contract, where ease of integration matters.
  • Files, streaming, different content-types.

GraphQL is better when:

  • Many clients with different data needs (mobile saves bandwidth, web pulls more).
  • Aggregating data from many services into one request (BFF).
  • Frequently changing requirements for which fields to select, without touching the backend.
  • Strong typing and schema introspection matter.

The cost of GraphQL:

  • HTTP caching barely works (everything is a POST to /graphql).
  • The N+1 problem (you need DataLoader).
  • Hard to limit expensive/deep queries (you need depth/complexity limits, persisted queries).
  • Rate limiting is harder (one request != one "cost").

⚠️ Gotcha: the "REST vs GraphQL" choice isn't about "what's more modern," it's about context. In an interview, a bad answer is "GraphQL is better because it's newer." A good one is "it depends on: the number and diversity of clients, the importance of caching, the complexity of aggregation." Often they're even combined: GraphQL for the client-facing BFF, REST/gRPC between services.

Source notes

References and review policy

RecallDeck’s interview answers are editorial material, reviewed against maintained official documentation where a primary reference is available. Tool selections use direct provider links and contain no affiliate placements. Features can change after the review date.

From reading to recall

Practice the full interview loop.

RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.

Start studying

Keep going

RecallDeck Interview Library

Detailed answers from the same curated interview deck, organized for search, study, and durable recall.

RSS