Step 1 Understand it
The problem it solves. and are often built by different teams, or even different companies. They need a clear, stable contract for asking and answering; otherwise every change breaks somebody.
New words in this topic (tap one to see what it means)
Watch it happen
Read the animation as text
- The app creates an order: POST /v1/orders, with an Idempotency-Key header — a random id for this one attempt.
- The server creates order 981 and stores the key with the result. It replies 201 Created…
- …but the reply is lost on a bad network. The app cannot tell whether the order was made, so it retries with the SAME key.
- The server finds k7 already stored and returns the same result: order 981. No second order. POST isn't idempotent by itself — the key makes the retry safe.
Picture it
sequenceDiagram participant C as Client participant S as Server participant D as Database C->>S: POST /v1/orders (Idempotency-Key k1) S->>D: save the order and key k1 S-->>C: 201 Created Note over C,S: the reply gets lost, so the client retries C->>S: POST /v1/orders (Idempotency-Key k1) S->>D: has key k1 been used? D-->>S: yes, for order 77 S-->>C: 201 Created (the same order 77, no duplicate)
The idea
A good is predictable. Resources have clear URLs like /users/42/orders, mean what they say, and tell the client what happened. Lists are , the API is , and requests that may be retried are made so a retry can't do the work twice.
How it works, step by step
- Name resources as nouns: /users, /users/42, /users/42/orders. Avoid verbs in URLs, like /getUserOrders.
- Choose the method by intent: GET to read, POST to create, PUT to replace, PATCH to change part, DELETE to remove.
- Return a status code that tells the truth: 201 after creating, 400 for bad input, 404 when something is missing, 409 for a conflict, when , 500 when the server broke.
- Paginate lists: return 20 items plus a ; the client sends the cursor back to get the next 20.
- Make retries safe: the client sends an Idempotency-Key header with POST; the server stores the key with the result and returns the same result if the key comes again.
- Version from day one: /v1/. When you must break something, add /v2/ and keep /v1/ running for old clients.
Analogy
A restaurant menu with clear sections. You ask for a dish by name (the resource), say what you want done (the method), and the waiter replies 'served', 'out of stock' or 'not on the menu' (the status code).
Where the analogy breaks
A waiter can improvise. An API is a contract: once apps depend on it, you can't change it freely, which is why versioning matters.
Example
GET /v1/orders?limit=20&cursor=abc returns 200 OK with 20 orders and a cursor for the next page. POST /v1/orders with an Idempotency-Key header returns 201 Created; retrying with the same key returns the same order instead of creating a second one.
Must-know points
- Methods: GET reads (safe), POST creates, PUT replaces, PATCH changes part, DELETE removes. GET, PUT and DELETE should be idempotent.
- Status codes: 2xx success (200, 201), 3xx redirect (301, 302), 4xx client error (400, 401, 403, 404, 409, 429), 5xx server error (500, 503).
- Pagination: offset (simple, but slow and unstable on big, changing lists) or cursor (fast and stable; preferred at scale).
- Idempotency key: the client sends a unique key so a retried request isn't processed twice. Essential for payments.
- Versioning: /v1/ in the path or a header, so old clients keep working.
- Alternatives: (the client chooses the fields it wants) and gRPC (fast, typed calls between services).
Common mistakes
- Returning 200 OK with an error message hidden in the body.
- Using GET for actions that change data (crawlers and browser prefetching may call it).
- No pagination, so one request can return a million rows.
- Breaking changes without a new version.
Trade-offs
- : simple and cacheable, but may need several calls per screen.
- GraphQL: flexible queries, but harder and rate limiting.
- gRPC: fast and strongly typed, but less browser-friendly.
Step 2 Go further (free, optional)
Optional. The notes above are written to be enough on their own. These free resources give a second explanation and extra practice.
- Read
-
Read
MDN Web Docs, free
-
Read
hellointerview.com · free guides
Step 3 Practise
Try each exercise yourself first: on paper, out loud or in code. Then open the answer and compare it with yours.
-
ExplainSay it in plain words in under a minute, with no notes.
Explain idempotency, and why POST isn't idempotent by default.
Show answer
An operation is idempotent if doing it twice has the same effect as doing it once. "Set my email to a@b.com" (PUT) is idempotent: the second time changes nothing. "Delete order 7" (DELETE) is too: after the first time it is already gone. GET reads, so it changes nothing at all.
POST usually means "create a new one". Send "create an order for £50" twice and you get two orders. That matters because networks fail: if the reply is lost, the client can't tell whether the order was made, and the safe-looking retry creates a duplicate.
The fix is an idempotency key: the client makes one unique key per real action and sends it with every retry. The server saves the key with the first result, and returns that same result when the key comes again, instead of creating a second order.
-
DrawSketch the diagram (or class diagram) from memory.
Write the API for a small library app (list books, borrow a book, return a book) with methods, URLs and status codes.
Show answer
Resources are nouns (books, loans); borrowing creates a loan, and returning updates it, so history is kept. Status codes say exactly what went wrong, so the app can show the right message.
textGET /v1/books?limit=20&cursor=… 200 OK { books: [...], next_cursor } GET /v1/books/{book_id} 200 OK | 404 Not Found POST /v1/loans body: { book_id, member_id } header: Idempotency-Key 201 Created { loan_id, due_on } 404 Not Found (no such book) 403 Forbidden (member at loan limit) 409 Conflict (no copy available) PATCH /v1/loans/{loan_id} body: { status: "returned" } 200 OK (also when already returned) 404 Not Found -
ApplyUse it in a real design question and say exactly where it fits.
Design the API for sending money between two bank accounts, including how a retried request is handled.
Show answer
The :
POST /v1/transfers
Headers: : Bearer <>, Idempotency-Key: <a made once per transfer by the client>
Body: from_account, to_account, amount_minor (integer pence, never a float), currency, reference.: 201 Created with transfer_id and status; 400 for bad input; 401 or 403 if the caller can't use from_account; 404 for an unknown account; 422 for not enough money; 409 if the same key comes with a different body; 429 when rate-limited. GET /v1/transfers/{id} lets the client check what happened.
On the server, in one : insert the idempotency key (a UNIQUE column), insert the transfer, then write the two entries (debit and credit). Store the response with the key. Either all of it happens, or none of it.
A retried request (say the reply ) carries the same key:
• If the key is found and finished, return the stored response: the money moves once.
• If the first attempt is still running, return 409 "in progress" (or wait for it).
• Keys expire after, say, 24 hours.The client's rule: on a timeout or a 5xx, retry with the same key and back off; never make a new key for the same transfer.
-
Trade-offsSay what it costs, when not to use it, and the alternative.
When would you choose gRPC or GraphQL instead of REST?
Show answer
Choose gRPC for calls between your own services: it is fast (binary messages over ), strongly typed (the .proto file is the contract, and client code is generated from it), and supports in both directions. The cost: browsers can't call it directly without a , and it is harder to inspect by hand than .
Choose GraphQL when many different clients (web, iOS, Android) need different slices of the same data, and REST would mean several round trips per screen or a lot of unused fields. The client asks for exactly the fields it needs, in one request. The costs: HTTP caching is harder (one endpoint, POST bodies), rate limiting must look at how expensive a query is, and careless cause N+1 database queries.
Stay with REST for public APIs, simple CRUD, and anything that benefits from plain HTTP caching and tooling. A common mix: REST or GraphQL at for apps, gRPC inside between services.
Step 4 Interview-ready, then prove it
Your interview answer
About 20 to 40 seconds, said out loud. Practise it until it sounds like you, not like notes.
I'd design the API around resources with standard methods and status codes, paginate lists with cursors, version it, and require an idempotency key on any create or payment endpoint so client retries are safe.
Follow-up questions
401 or 403?
401 means not (we don't know who you are). 403 means authenticated but not allowed to do this.
Why is cursor pagination better than offset?
Offset has to skip rows, which is slow for page 10,000, and pages shift when new rows arrive. A cursor continues from the last item seen, so it's fast and stable.
Tick each check when you can do it without notes
A topic is finished when you pass all four checks, not when you finish a video. Be strict: if you needed your notes, it is not ticked yet.
- Explain Say it in plain words in under a minute, with no notes.
- Draw Sketch the diagram (or class diagram) from memory.
- Apply Use it in a real design question and say exactly where it fits.
- Trade-offs Say what it costs, when not to use it, and the alternative.
0 of 4 checks ticked.
Saved in this browser. Back it up or move it to another device.