API Development interview questions
API Development questions a Backend developer can be asked, from first job to senior. Open one to see the strongest answer and why it works.
The interviewer asks: “When do you use GET versus POST?” Strongest answer?
The strongest answer: GET for reads that must not change server state — it can be cached, logged and retried. POST when the call creates something or has a side effect.
Why it works: Exactly. The verb is a contract: GET is safe and idempotent; POST is allowed to create or change. Browsers, caches and CDNs rely on that.
- GET = no side effects; POST = create or change.
- Treat GET as cacheable and retry-safe.
- Use POST (or PUT/PATCH/DELETE) when the server will mutate.
- Never put secrets in a query string.
The interviewer asks: “What's the difference between HTTP 401 and 403?” Strongest answer?
The strongest answer: 401: we don't know who you are — authenticate. 403: we know who you are, and you still may not do this.
Why it works: Exactly. 401 is missing or invalid credentials. 403 is an authenticated caller without permission. Clients branch: sign in vs you can't.
- 401 = who are you; 403 = you may not.
- 401: authenticate or refresh the token.
- 403: identity is known; authorization failed.
- Don't wrap either in HTTP 200.
The interviewer asks: “When is 404 the right status, and when is 400?” Strongest answer?
The strongest answer: 404: that resource isn't here (or I must not reveal that it is). 400: the request itself is malformed — bad JSON, missing field, invalid id format.
Why it works: Exactly. 400 is “you sent something I cannot interpret.” 404 is “that thing isn't here.” Don't punish a missing row with a 5xx.
- 400 = malformed request; 404 = missing resource.
- Validate shape first — that is 400.
- A well-formed id that doesn't exist is 404.
- Don't turn “not found” into 500.
The interviewer sketches /getOrder and /doDeleteUser. Strongest design?
The strongest answer: Model URLs as nouns — /orders/12 — and use the HTTP verb for the action. Don't put get or delete in the path.
Why it works: Exactly. REST's uniform interface is the verb plus a resource. /orders/12 + DELETE is cacheable, loggable and obvious.
- Resources are nouns; verbs belong to HTTP.
- GET /orders/12, DELETE /orders/12 — not /getOrder.
- Query strings filter; they don't mean “do.”
- Never make GET mutate.
The interviewer asks: “Which HTTP methods are safe to retry, and why?” Strongest answer?
The strongest answer: GET, HEAD, PUT and DELETE should be idempotent: repeating the same request does not create extra side effects. POST usually is not — retrying it can create a second order.
Why it works: Exactly. Idempotent means the effect of N identical calls is the same as one. That's why timeouts on GET/PUT are retryable and a raw POST needs an idempotency key.
- Idempotent = same effect if I send it again.
- GET/PUT/DELETE are built for that.
- POST creates — retry only with a key.
- Identical JSON is not the test.
The interviewer asks: “How do you send and read JSON on an HTTP API?” Strongest answer?
The strongest answer: Set Content-Type: application/json, declare Accept, and (de)serialize with the language's JSON library — don't guess from the string.
Why it works: Exactly. The header is the contract. A serializer handles unicode, escaping and types; guessing invites mismatches and injection.
- JSON is a media type, not a vibe.
- Set Content-Type and Accept.
- Use a real serializer both ways.
- Never concatenate user text into JSON.
The interviewer asks: “What status do you return when POST creates a resource?” Strongest answer?
The strongest answer: 201 Created, with a Location header (and often the body). 200 means “here is a representation,” not “I created it.”
Why it works: Exactly. 201 + Location is the uniform way for a client to follow the new resource. 200 is a successful fetch or an in-place update.
- POST create → 201 + Location.
- Put the new URL in Location.
- Don't invent { success: true } on 200.
- Never 201 a failed write.
The interviewer asks: “When do you nest URLs like /orders/12/items?” Strongest answer?
The strongest answer: When items are a real sub-resource of that order. If the child is first-class, a flat /items?orderId=12 is clearer than a deep tree.
Why it works: Exactly. Nesting expresses ownership. Deep trees that exist only to pass filters are harder to cache, authorise and document.
- Nest real children, not every filter.
- Flat + query is fine for first-class resources.
- Don't build five-level trees by habit.
- Paths are nouns; GET must not destroy.
In a GraphQL API, what is the schema and what is a resolver?
The strongest answer: The schema is the typed contract — the types and fields a client may ask for — and a resolver is the function that produces the value for one of those fields when a query asks for it
Why it works: Right — one contract, one function per field. That per-field model is also why field-level fetching needs care.
- The schema is the contract.
- Types and fields clients may request.
- A resolver returns one field.
- Resolvers are code, not the database.
The interviewer asks: “Do you return the database entity as JSON?” Strongest answer?
The strongest answer: No. I return a DTO I control. Entities hide lazy loads, circular references, and columns the client must not see.
Why it works: Exactly. The persistence model is not the HTTP contract. Map it. That's how you avoid over-posting and surprise columns.
- API returns DTOs, not entities.
- Map explicitly at the edge.
- Don't share one type with the database.
- Never serialise secrets or hashes.
The interviewer asks: “A handler throws. What does the client see?” Strongest answer?
The strongest answer: A stable error code and a safe message. Stack traces, SQL and host names stay in logs. 500 means we failed — not that we dump internals.
Why it works: Exactly. Clients get a contract (RFC 9457 Problem Details is the 2026 default). Operators get the stack in the log with a request id.
- Safe body + honest status.
- Stacks and SQL belong in logs.
- Don't launder errors into 200.
- Don't leak internals to clients.
The interviewer asks: “How do you paginate a list endpoint?” Strongest answer?
The strongest answer: A stable ORDER BY plus a limit. Keyset/cursor pagination (WHERE (created, id) < last) beats OFFSET for large pages. Always return a next cursor, not “page 7 of unknown.”
Why it works: Exactly. Unordered OFFSET is a dice roll. Cursors stay correct as rows insert, and they use the index instead of skipping N rows.
- Stable order + limit.
- Prefer keyset/cursors over deep OFFSET.
- Don't load the table to paginate.
- Whitelist sort fields; never splice them.
The interviewer asks: “Where do you validate input?” Strongest answer?
The strongest answer: At the HTTP edge (schema, types, limits) and again as domain rules (this user may not approve their own expense). I never trust the client or a second internal caller by default.
Why it works: Exactly. Edge validation gives cheap 400s. Domain rules belong next to the decision. Defense in depth — DB constraints are the last net, not the first.
- Validate at the edge and in the domain.
- Never trust the client.
- DB constraints are a backstop.
- Internal callers are still callers.
The interviewer asks: “PUT versus PATCH?” Strongest answer?
The strongest answer: PUT replaces the resource with the body I send (idempotent full document). PATCH applies a partial update. I don't overload PUT with “only the fields I included.”
Why it works: Exactly. Clients and caches treat PUT as “this is the new representation.” Partial PUT is how you wipe fields you didn't mean to send.
- PUT = full replace; PATCH = partial.
- Don't send a subset as PUT.
- PATCH is current, not deprecated.
- Omitted ≠ null unless the client said so.
The interviewer asks: “How should API errors look to clients?” Strongest answer?
The strongest answer: A stable shape — Problem Details (RFC 9457) or equivalent: type, title, status, a machine code, correlation id. Don't change field names per endpoint. Don't leak stacks.
Why it works: Exactly. Frontends and other services bind to a contract. A correlation id lets support grep logs without a stack in the browser.
- One error envelope, stable fields.
- Machine code + correlation id.
- No stacks on the wire.
- Don't parse English; don't hide errors in 200.
The interviewer asks: “How do you version a public JSON API?” Strongest answer?
The strongest answer: Compatibility first: additive changes in place. When I must break, a version in the URL or a negotiated header, both documented, with a sunset date. I don't silently reshape fields.
Why it works: Exactly. Most “versions” should be additive. A real break needs a dual-run window, not a surprise. URL vs header is a taste; the contract and the sunset are the job.
- Prefer additive, compatible changes.
- Breaking change → explicit version + sunset.
- Document it; dual-run it.
- Don't reuse names for new meanings.
When would you pick gRPC over a JSON REST API, and what do you give up?
The strongest answer: Between your own services, where a generated versioned contract and lower overhead matter — protobuf over HTTP/2 gives typed clients and streaming; you give up browser support, readable payloads and plain curl debugging
Why it works: Exactly — and naming the costs is the part interviewers listen for: tooling, debuggability and the browser.
- Typed contract, HTTP/2, streaming.
- Best between services you own.
- Costs: browsers, tooling, readability.
- Keep REST at the public edge.
Your OpenAPI document lists every path and method and nothing else. A partner team says integrating still took them a week.
What is missing?
The strongest answer: What they actually integrate against: request and response schemas, the error bodies and status codes, the auth scheme, pagination and examples — plus a CI check that the document still matches the running code
Why it works: Right — and the CI check is the half most teams skip; a spec nobody verifies becomes fiction within a quarter.
- The spec is the contract.
- Schemas, errors, auth, pagination.
- Ship examples, not only paths.
- Check the spec against the code in CI.
The interviewer asks: “You're building an internal platform API other teams will live on. What changes?” Strongest answer?
The strongest answer: Compatibility is the product: SLOs, versioning, a deprecation policy, example clients, and a change process that doesn't break them on Tuesday. I optimise for their deploy cadence, not ours.
Why it works: Exactly. Platform APIs have customers. The bar is closer to public API than to a private controller. Reliability and boring evolution beat cleverness.
- Internal platforms still have customers.
- Version, deprecate, overlap.
- SLOs for the API, not just the app.
- Don't publish the database.