API Design System Design Interview: Narrate the Contract
A repeatable way to narrate API design in system design interviews: client jobs, resources, methods, pagination, idempotency, versioning and the trade-off behind each choice.
API design shows up in system design interviews because it is the cheapest way for an interviewer to check whether you can turn a vague product idea into something concrete. Anyone can say "we'll store the data in a database". Far fewer candidates can say what a client actually calls, what comes back, and what happens when the call fails halfway. This article gives you a repeatable way to narrate those decisions out loud, so your API design system design interview answer sounds like an engineer making choices rather than someone reciting a schema.
Why API design matters in a system design interview
The API is the contract between the client, the service and every future team that touches it. Interviewers use it to test whether you can move from requirements to interface without hand-waving.
It also fits the format. In forty-five minutes you cannot design a whole system, but you can design two or three endpoints properly. That is usually enough to see how you think.
So frame the API as the first place where functional requirements become something a client can actually call. Take a vague requirement like "users can share links". That sentence hides a surprising number of decisions: does sharing create a new object, or change an existing one? Does the client send a recipient, or a permission level, or both? Does the call return the shared link, or just a confirmation? Answering those questions is what turns a requirement into an interface, and it is exactly the step interviewers want to watch you take.
That is also why it is a low-cost signal. The interviewer does not need you to finish the system; they need one requirement, one call, and one sentence of reasoning to see whether you can make a decision concrete. Then set expectations early: "I'll pick the two or three endpoints that carry the most risk and go deep on those." That sentence buys you permission to skip the boring parts.
If you have read our blog before, you will recognise the habit underneath this: name the decision, then name the cost. It works for requirements, and it works for interfaces.
Start from the client's job, not from the database schema
Ask what the client is trying to accomplish in one sentence. Then name the resources that sentence implies.
Resources are nouns the client cares about. A user, an order, a short link. They are not your internal tables. If you find yourself exposing a join table, you have probably leaked an implementation detail into the public surface.
Once you have the nouns, say out loud which operations are reads, which are writes, and which are rare but expensive. That last category is where interviewers learn the most, because it forces you to talk about cost.
A rare-but-expensive operation, such as a bulk export or a monthly report, is usually a poor fit for a synchronous request. Say that you would return 202 Accepted with a status resource the client can poll, and name the cost: you have added a job, a status endpoint and some state to track, in exchange for protecting the service from a long-running call. One sentence on each side is enough.
A worked example: the URL shortener
Take a URL shortener. The client has two jobs:
- Create a short link.
- Resolve a short link.
Those two jobs imply two endpoints with very different traffic shapes. Creation is a write, relatively rare, and worth protecting with authentication and rate limiting. Resolution is a read, extremely frequent, and worth caching aggressively. Same product, two completely different design conversations.
If you name that difference in the first two minutes, the rest of the interview tends to follow it.
Naming resources, methods and status codes without droning
Use plural nouns for collections and let HTTP methods carry the verb. Explain why you avoid verbs in paths in one clause: "POST /links rather than /createLink, because the method already says create, and the path stays stable if the operation changes."
Before you go endpoint by endpoint, name authentication and authorisation as a cross-cutting concern: who can call this, and at what scope. One sentence up front ("every endpoint here requires an authenticated user, and writes are scoped to the owner") stops the interviewer wondering whether you forgot, and you can move on.
Then map the common operations and justify each in a single clause:
GET /links/{id}reads one link. Safe and cacheable.POST /linkscreates one. Not idempotent, so retries need care.PUT /links/{id}replaces a link at a known identifier. Idempotent.PATCH /links/{id}changes part of it. Useful when the client does not hold the whole object, but not defined as idempotent, so retry safety depends on what the patch document does.DELETE /links/{id}removes it. DELETE is idempotent in effect: repeating the call leaves the same state. I would return 204 whether or not the resource existed, because the client cannot act differently on a 404; if the client needs to know whether it was already gone, that is a separate lookup. Returning 404 instead is a defensible choice, but then I would name the cost: the client has to treat a missing resource as an error even though the end state is the one it wanted.
Status codes are part of the contract, not decoration. Say what the client does differently because of each one:
200success,201created (read the new resource),202accepted (poll the status resource).400malformed request (fix and resend),401unauthenticated (re-authenticate),403authenticated but not allowed (do not retry).404not found,409conflict (reconcile state before retrying),429rate limited (back off and honourRetry-After).5xxgenerally means a server-side failure, but not all of it is transient:501and505will not succeed on retry. For the rest, retry only idempotent requests or ones with an idempotency key. On503(and429) honourRetry-After; otherwise back off with jitter.
One sentence per decision, then move on. The interviewer can ask follow-ups, and they usually will.
Pagination, filtering and sorting: the questions that separate candidates
This is where a lot of otherwise solid answers go quiet. Two questions do most of the work.
Pagination
Offset pagination is simple: ?page=3&size=20. It also drifts. Insert or delete an item while a client is paging and they will see duplicates or miss rows.
Cursor pagination is stable: ?after=abc123&limit=20. It costs you random access, because you cannot jump to page seven.
Say which one you would pick for the endpoint in front of you and why, referencing the read pattern. A feed that users scroll endlessly is a good fit for cursors. An admin table where someone wants to jump to page twelve is a reasonable fit for offsets.
Filtering and sorting
Filtering and sorting belong in query parameters. Document which fields are sortable, and say why unbounded sort fields are a performance trap: every new sortable column is a new index you have to maintain, and it is an easy way for a client to ask for something expensive.
Mention that page size needs a maximum, and that the default should be small enough to protect the service. For example, a default of twenty with a cap of a hundred is a defensible starting point; the point is that both numbers exist and you can justify them.
Idempotency, retries and the write path
A client retrying a POST can create duplicates. The request may have succeeded and the response may have been lost, so the client genuinely does not know whether the write landed. This is a distributed systems problem, not an HTTP trivia question: networks drop responses, not just requests.
An idempotency key turns an unsafe retry into a safe one. The client generates a unique key, sends it with the request, and the server treats a repeat of that key as the same operation. Say it at the level of the contract, not the storage engine. You do not need to describe the table that stores the keys.
Then distinguish the methods:
GET,PUTandDELETEare idempotent. Repeating them leaves the same state.POSTis not. Repeating it may create a second resource.
That distinction is what drives retry logic on the client, so it is worth thirty seconds of your answer.
Versioning, error shapes and evolution
Versioning is a promise about compatibility. Say what you would consider a breaking change: removing a field, renaming one, changing a type, or tightening validation so that previously valid requests now fail. Adding an optional field is usually not breaking.
Then compare the options and pick one with a reason tied to your client population:
- URL versioning (
/v2/links) is visible and easy to route, but it duplicates surface area. - Header versioning keeps URLs clean but is harder to test with a browser.
- Additive-only evolution avoids versions entirely, and works when you control or can coordinate with your clients.
A consistent error body is part of the API, not an afterthought. Something like a code, a human-readable message and optional details lets clients branch on the code instead of parsing prose. Say that once and move on.
Finally, say how you would deprecate a field without breaking existing clients: keep returning it, mark it as deprecated in the documentation, and give a timeline. That single sentence signals that you have thought about the people who call your API after you stop working on it.
Naming the trade-offs out loud
Every API decision trades something. Simplicity against flexibility. Latency against consistency. Coupling against convenience.
REST versus GraphQL is the same kind of trade-off, not a loyalty test. REST gives you simple, cacheable resources and predictable per-endpoint behaviour, at the cost of clients making several calls or receiving fields they do not need. GraphQL lets clients ask for exactly the shape they want in one request, but you pay for it: HTTP caching gets harder because most requests are a single POST, resolvers invite N+1 query problems, and you need query complexity limits so one client cannot ask for an expensive traversal. In an API design system design interview, either answer is fine if you name those costs and tie the choice to the client population.
Use a short sentence pattern: "I would choose X because Y, and I accept Z as the cost." It is memorable, it is honest, and it fits almost any decision you make in the interview.
Some examples you can adapt:
- Cursor pagination costs you random access but buys stability under writes.
- Chatty endpoints cost round trips but keep each response small and cacheable.
- Denormalising the response costs storage and consistency work but saves the client a second call.
- A strict error contract costs you flexibility in what you return but makes clients simpler to write.
This is the same habit that makes functional and non-functional requirements answers land well, and it is usually what interviewers remember about a candidate. It is also the part of your answer that is hardest to fake, which is why it is worth rehearsing.
A practice loop for API design in a system design interview
You do not need a whiteboard to practise this. You need repetition. One pass takes about ten minutes; the loop is meant to be run again and again with different products.
- Pick a familiar product: a ride-hailing app, a note-taking tool, a ticket booking site.
- Write down two client jobs in one sentence each.
- Sketch the two endpoints that serve them, with method, path and the status codes you would return.
- Say the design out loud in under three minutes. No notes.
- Write down the trade-off you named for each decision. If you cannot name one, you have not finished the decision.
- Record yourself and check whether you explained the why or only the what.
- Repeat with a different product until the pattern feels automatic.
The goal is fluency with the reasoning, not memorised schemas. If you want to see the same trade-off structure applied to a different topic, the Python interview questions and trade-offs article uses it for language questions.
Common traps and how to avoid them
Most weak answers fail in predictable ways. Watch for these:
- Designing the whole API surface instead of the risky parts. Pick depth over breadth, and say so.
- Forgetting authentication and authorisation until the end. Mention them early as cross-cutting concerns that apply to every endpoint.
- Ignoring rate limiting and abuse on public endpoints. One line shows operational awareness.
- Over-claiming. If you have not worked with a pattern, say what you know and what you would look up. That reads as senior, not weak.
- Listing status codes without explaining what the client does differently because of them.
Before you walk in
Treat the API as the part of the design where requirements become callable. Start from the client's job, name the resources it implies, choose methods and status codes with one clause of justification each, and say the trade-off out loud every time. That is the whole skill behind a strong API design system design interview answer, and it is trainable.
Then practise the three-minute version until it is automatic. Record it, play it back, and check whether the reasoning came through or whether you only described the mechanics.
Frequently asked questions
How much API detail should I include in a system design interview?
Enough to show you can turn a requirement into a callable interface. Pick two or three endpoints that carry the most risk and go deep, then say you are deliberately skipping the rest.
Should I use REST or GraphQL in a system design interview?
Treat it as a defensible default, not a rule: REST is a reasonable starting point for several clients with straightforward caching needs, while GraphQL is worth raising when clients need very different slices of the same data, at the cost of harder HTTP caching, N+1 resolver problems and the need for query complexity limits. Either way, name the cost.
What is the difference between PUT and PATCH, and does it matter in an interview?
PUT replaces a resource at a known identifier and is idempotent. PATCH changes part of a resource and is useful when the client does not hold the whole object. It matters because it shows how you think about retries and partial updates: a patch document that sets fields to absolute values is idempotent in effect, while one that increments a value is not, because the patch document is relative rather than absolute. Lost updates from concurrent writers are a separate concern, usually handled with versioning or conditional requests.
How do I explain idempotency without overcomplicating it?
Start from the lost response: the client does not know whether the write landed. An idempotency key lets it retry safely, because the server treats a repeat of the same key as the same operation.
Do I need to design every endpoint for the system I am asked about?
No. Say up front that you will focus on the endpoints with the most risk or traffic, design those properly, and list the rest in one line.
How can I practise explaining API trade-offs out loud before the interview?
Sketch two endpoints for a familiar product and say the design out loud in under three minutes. Then check whether you named the trade-off behind each decision.
See the same trade-off structure applied to Python questions