← Home
.NETWeb APIReported by candidates

What is REST? What is the difference between GET, POST, PUT, PATCH and DELETE, which of them are idempotent, and which status codes should an API return?

19 reports in the question index (9 linked below), from 2025 to this month: Infosys (4), Deloitte, TCS, UST, Capgemini and others. Asked as a basic in the first technical round, then pushed into idempotency and retries.

Say this first · about 30 seconds
Make it yours · Navistar Financial credit applications (invented example)
The code, simplified

Invented to show the shape, in C#. Tap Next to walk a repeat submit of application 7310, then a new application, 7311.

Navistar.sln · where this file sits
  • Controllers/
Diagram · step 1A dealer system POSTs to submit application 7310 again. It sent it yesterday.
ApplicationsController.cs · line 4 · 1 of 9The service that finds and updates applications. DI passes it in the constructor (not shown).

Fix later in your repo Example written for this card: invented, not from a real codebase.

Say it out loud, then check

Show the 30-second answer
  • REST is a style for designing web APIs around resources. Each thing, like a contract or a payment, has its own address, and you act on it with the standard HTTP methods instead of inventing verbs in the URL. The server keeps no session between calls, so every request carries everything it needs, which is what lets us run several copies of the API behind a load balancer.
  • GET reads and never changes anything. POST creates, or starts something, under a collection. PUT replaces the whole resource with what you send, and PATCH changes only the fields you send. DELETE removes it.
  • Idempotent means sending the same request twice leaves the server in the same state as sending it once. GET, PUT and DELETE are idempotent, POST is not, and PATCH isn't guaranteed to be. That matters because clients and retry policies resend requests after a timeout.
  • For status codes I keep to a small set, grouped by who is at fault. 2xx means it worked: 200 for a normal read, 201 Created with a Location header after a POST, and 204 when there's no body. 4xx means the request was wrong: 400 for bad input, 401 when you're not signed in, 403 when you're signed in but not allowed, 404 when it doesn't exist, and 409 for a conflict. 5xx means we failed: 500 for our own bugs. Errors come back as ProblemDetails, so every client parses them the same way.
  • The production lesson for us at BMW FS was retries. A client retried a payment POST after a timeout, and some customers were charged twice. Now any POST that moves money requires an idempotency key, and the API stores the key with its result, so a repeat gets the first answer back instead of a second payment.
Check your answer: tick what you said

You said 0 of 9

Then add your example: Navistar Financial credit applications (invented example)
  • Situation: Navistar Financial truck dealers send from their own dealer systems to our Credit Application API. When a dealer system submitted an application twice, the API threw an exception and answered 500, so the dealer's system retried, again and again, and the got paged for an error that wasn't a bug.
  • What I did: the submit action (a controller method that handles one kind of request, see ) now answers with the status code that says what really happened: 404 if the application doesn't exist, 409 Conflict if it was already submitted, and 202 Accepted when it starts the credit decision, which finishes later.
  • Result: dealer systems stop retrying on 409. HTTP itself doesn't stop them: their retry policy treats a 4xx code as "the request is wrong, don't repeat it". Pages for this error went to zero, and real 500s stand out again.
  • What I'd say: a 500 means our bug and invites a retry. A request that conflicts with the resource's state is a 409, so the client knows not to send it again.

See it

1/8GET reads contract 42. Nothing changes. The answer: 200 OK and the JSON.

In plain words

The senior answer · about 2 minutes
REST, in my words
  • REST means the API is built around resources, not actions. A contract lives at /api/contracts/42; its payments at /api/contracts/42/payments. You read with GET, create with POST, replace with PUT, change part with PATCH and remove with DELETE, and the status code tells the client what happened.
  • The constraint I care most about in production is statelessness: the API keeps no session between calls, each request carries its token. That is why we can run three copies of the API on and any of them can answer any request ().
Verbs and idempotency
  • PUT sends the whole resource and replaces it, so sending it twice gives the same result: idempotent. PATCH sends only the changed fields; ours are idempotent because they set values, but the standard doesn't promise it, since a PATCH could say "add 1". POST is the one that is never idempotent: two POSTs make two payments.
  • Idempotency matters because requests get repeated without anyone deciding to: a timeout, a retry policy, a user double-tapping. In .NET the standard resilience handler on retries every method by default, POST included. We call on clients that send money, and the API requires an idempotency key anyway.
Status codes I actually use
  • 201 with a header after a create, 204 after a delete, 202 when work continues in the background. For errors: 400 for invalid input, 401 versus 403 for who you are versus what you may do, 404, and 409 when the request clashes with the resource's state, like submitting an application twice.
  • The rule underneath: 4xx means the client must change something and shouldn't simply retry; 5xx means we failed and a retry may help. Every error body is ProblemDetails, so clients parse one shape, and the status code is what and our alerts count. A 200 with an error inside hides failures from all of them; that is the first bug below.
In production
  • Money-moving POSTs require an Idempotency-Key header. The API stores the key with the response in a table with a unique index (the database refuses a second row with the same key), and a repeat returns the stored response. That came out of the incident in Deep, where retried payment POSTs charged customers twice.
  • No personal data in URLs: a GET's query string is encrypted on the wire, but it lands in logs and browser history. Customer numbers go in the path only as our own ids, never names or tax numbers.

Every method on one table

The Contracts table after each call. Watch which calls change it, and what a repeat does.

1/7GET /contracts/42 reads row 42. The table doesn't change: safe.

The story

  1. Before

    HTTP is how programs ask each other for things: each request has a method (GET reads, POST sends) and each answer has a 3-digit status code (200 means it worked). JSON is the text format the data comes in. is Azure's monitoring tool; it counts failed requests by their status code. A retry is the client sending a request again after a timeout: harmless for a read, but it can repeat a change twice. A cache may serve an old saved answer. BMW FS's first contracts API, written years ago, put verbs in the address: POST /api/getContract, POST /api/updateContract, POST /api/deleteContract. Every answer was 200 OK, and an error was a field inside the JSON: "success": false. The dealer portal and two partner systems called it.

    1/4Before: every call is a POST to a verb, and every answer is 200, even errors.

  2. Why it had to change

    Everything around an API reads the method and the status code: browsers, caches (saved copies of answers), retry policies (rules for sending a request again), load balancers (a front door that spreads requests over copies of the API) and monitoring (tools that count failures). If every call is a POST and every answer is 200, none of them can do their job, and every client must write its own error parsing.

  3. How it works now

    REST uses what HTTP already has. The controller is the class whose methods, called actions, receive the requests (). Here is one request and its answer, in the parts every tool understands:

    1/4The request line says what and where: method GET, address /api/contracts/42.

  4. What we gained

    Retries, caches and monitoring work without custom code, and every client handles errors the same way. The rest of the card covers each method, idempotency, and the status codes to return.

In the lending API

Each marked line returns one status code; the diagram above shows the calls.

Only the key lines are shown; Deep walks the whole file.

Lending.sln · where this file sits
  • src/Api/Controllers/
⋯ 9 lines in Deep⋯ 26 lines in Deep
ContractsController.cs · line 20 · 1 of 4200 OK with the contract as JSON.

The code, file by file

The contracts controller in the BMW FS lending API. Tap Next to walk each method and the status code it returns.

Lending.sln · where these 2 files sit
  • src/Api/
    • Controllers/
    • Contracts/
Diagram · step 1GET reads contract 42. Nothing changes. The answer: 200 OK and the JSON.
ContractsController.cs · line 1 · 1 of 20[ApiController]: invalid input gets an automatic 400, and routes must be attributes like the next line.

What they ask next

The follow-ups interviews really ask. Answer out loud, then tap to check.

Follow-up tree

Answer, then open the next level. That is the order an interviewer digs in.

Reporteda candidate was asked Intervieweran interviewer asks it

Production bugs, and how to tell them

Each one: what you see, why, the fix in code, and the line to say.

Production incident: A retried payment POST charged 318 customers twice

P1 · outage or data leakBMW FSanother system failedPayments API, called by the Customer API (both .NET on App Service)

Composed, not a real event at BMW FS. The mechanism is real: the .NET standard resilience handler retries every HTTP method by default, POST included, after its 10-second attempt timeout (Microsoft Learn, "Build resilient HTTP apps"). Times, counts, names, ticket and PR numbers are composed.

The story in 5 lines
  1. On Wed 14 Oct, BMW FS's afternoon reconciliation flagged 318 customers who had paid the same amount two or three times within seconds through the customer portal's Pay now button.
  2. Between 11:40 and 12:25 ET, was slow during a large index rebuild, and some payment POSTs to the Payments API took 12 to 15 seconds.
  3. The Customer API's used the standard resilience handler. Its attempt timeout is 10 seconds and it retries every method by default, POST included. Each slow first try still finished, and each retry made another payment.
  4. Payments operations pulled the duplicates from the 18:00 file (the bank debit file), so no customer was charged twice. turned off retries for POST in config at 14:30.
  5. The fix: every payment POST carries an Idempotency-Key that the Payments API stores with a unique index, and clients never retry unsafe methods.
Who's who (5 people)
You
.NET developer on the vendor team (L3). Owns the fix.
Tech lead
Vendor senior. Runs the bridge call, reviews every PR.
L2 pod
Vendor support. Runs queries, changes config with approval.
BMW FS payments operations lead
Owns the daily ACH file to the bank. Decides what is pulled.
BMW FS IT change manager
Approves the emergency config change and the fix's release.

Tap a step. The map lights the place where it happens.

1/8 Detect

Reconciliation flags 318 customers with two or three identical payments today.

Wed 14 Oct, 14:05 ET (23:35 IST)BMW FS payments operations, then the L2 pod

  • What: the 14:00 reconciliation report, which compares payments with the day's bank file, flags 318 customers with two or three payments of the same amount, seconds apart.
  • Why it hid: every payment request got a 201. The API logged no errors, because from its side every request was a valid new payment.
  • How we saw it: grouped today's payments by contract and amount in . The duplicates were all between 11:40 and 12:25 ET.
App Insights: payment POSTs per contract
requests
| where timestamp > startofday(now())
| where cloud_RoleName == "payments-api"
| where name == "POST Payments/Pay"
| extend contract = tostring(
    customDimensions.ContractId)
| summarize tries = count(),
    first = min(timestamp)
    by contract
| where tries > 1
Try it yourself · 20 minutes, on your Mac
  1. dotnet new webapi -n RetryLab --use-controllers. Add a POST action that waits 12 seconds, adds a row to a static list and returns 201.
  2. In a console app, add and register an with AddStandardResilienceHandler().
  3. POST once. Then GET the list from the API: two or more rows, from one call.
  4. Add o.Retry.DisableForUnsafeHttpMethods() and repeat: one row, and a timeout on the client.
You should see

A client timeout doesn't stop the server. Retrying a POST without an idempotency key makes a second one.

The senior move

"Retries are only safe for idempotent requests. Money-moving POSTs carry an idempotency key the server stores with a unique index, and clients never retry unsafe methods on their own."

Sources Sourced: the standard resilience handler's defaults (10 s attempt timeout, 3 retries, all methods) and (Microsoft Learn, "Build resilient HTTP apps"); idempotent and safe methods (RFC 9110); the Idempotency-Key header (IETF draft). Status codes checked with a real ASP.NET Core run on .NET 10. Composed: BMW FS's systems, dates, counts, names, ticket and numbers, the and the SQL.

Traps

Traps

What should you say instead? Tap to check.

Live coding

Easy first, then harder. Set a timer and type it before you look.

Task 1

Question index: Musteasy · 10 minutes

Design the endpoints for a contract's payments: list them, read one, make one, cancel one. For each, give the method, the address and the status codes it can return.

Infosys: POST, PUT and PATCH, usage and effects
Stuck? Hints
  1. Is a payment its own resource, or part of a contract?
  2. What does the client need back after creating one?
  3. Is cancelling a delete, or a change of status?
Show a solution
Lending.sln · where this file sits
Payments API design · line 1 · 1 of 4200 with the list; 404 if contract 42 doesn't exist.
What the interviewer checks
  • Nouns in the address, methods for the action.
  • 201 with a header for the create.
  • 404 for an unknown contract or payment.
  • Cancel as a status change (PATCH or POST to a cancellation), not a hard DELETE of a money record.
  • Names which ones are idempotent.

Task 2

Question index: Mustmedium · 15 minutes

Write the PUT and PATCH actions for a contract's contact details, and show with two requests what happens to the email when the body leaves it out.

Infosys: how do POST, PUT and PATCH differ?
Stuck? Hints
  1. What does PUT promise about fields that are missing?
  2. How can the PATCH action tell "not sent" from "sent"?
  3. Which status code fits when nothing comes back?
Show a solution
Lending.sln · where these 2 files sit
  • src/
    • Api/Controllers/
    • Application/Contacts/
Diagram · step 2With PUT, the body is the whole contact. No email in it, so the email is cleared.
ContactsController.cs · line 3 · 1 of 2PUT: body is the whole contact. A missing email arrives as null. The service below does the copying.
What the interviewer checks
  • PUT replaces every field: a missing email becomes empty.
  • PATCH with nullable properties sets only what was sent.
  • 204 No Content for both; 404 for an unknown contract.
  • Can say which is idempotent and why.

Task 3

Question index: Musthard · 30 minutes

Make POST /api/contracts/{id}/payments idempotent with an Idempotency-Key header: store the key with the response, return the stored response for a repeat, and handle two copies of the same request arriving at the same moment.

Question index: idempotent POST (API-lc-idempotent-post), from interviews that push REST into retries
Stuck? Hints
  1. Where does the key live so every copy of the API sees it?
  2. What stops two requests with the same key from both inserting?
  3. What should a repeat get back: a new answer or the first one?
Show a solution
Lending.sln · where these 2 files sit
  • src/Payments/
    • Controllers/
    • Idempotency/
Diagram · step 1The client makes key 7f3a… once, and sends it with the payment POST.
PaymentsController.cs · line 4 · 1 of 5Reads the key from the request header.
What the interviewer checks
  • 400 when the header is missing on a money-moving POST.
  • Key stored in the database with a unique index, not in memory.
  • Insert the key first; a duplicate-key error means another request owns it.
  • A repeat returns the first response's status and body.
  • Says the key is made by the client once per payment, not per try.
  • Senior extra: the key row has an "in progress" status with a time limit, so if the first try crashes, a repeat isn't left waiting forever.
With AI tools

Interviewers now ask how you check AI-written code. Assistants generate controllers quickly, and two habits slip in: returning with an error object inside, and wrapping every in a retry policy without asking which calls are POSTs. I check every generated action's status codes against the list on this card, and every retry policy for or an idempotency key.

Glossary: every name, one line each →

My notes

Paste the NOTES block from your doubt chat here. The grader never reads notes; the coach reads "Still unclear".

Read moreWhere it was asked