API Design
Every lesson in this module has covered one piece of an API in isolation — what it is, what sits in front of it, how it shapes data, how it stays safe under retries and load. Good API design is what happens when those pieces are chosen deliberately and made to work together, instead of accumulating ad hoc as a service grows. It's less about any single rule and more about a handful of habits that keep an API predictable to use and safe to evolve.
Model resources, not actions
A REST API (see REST vs. GraphQL) reads clearly when URLs name things and HTTP methods name the verb — POST /orders, not POST /createOrder; DELETE /orders/42, not POST /deleteOrder. This isn't a stylistic preference: once resources are nouns, the method/status-code vocabulary from APIs does real work, or plugs into HTTP-native caching the way REST vs. GraphQL described. An API that instead exposes a pile of verb-named endpoints (/getUser, /updateUserEmail, /deactivateUser) throws that consistency away and forces every client to learn each endpoint's behavior individually instead of relying on a shared convention.
Design for the request lifecycle, not just the happy path
Pulling together the rest of this module into one request's journey is a useful way to see how the pieces actually compose:
Auth and rate limiting typically happen at an API Gateway before a request ever reaches business logic; idempotency handling wraps the mutation itself. A well-designed API is one where every request that could plausibly go wrong — unauthenticated, over quota, a retried duplicate — has an explicit, well-understood path, not just the successful one.
Version deliberately, before you need to
Every API contract will eventually need to change in a way that breaks existing clients — a field renamed, a response shape restructured. Because APIs are a contract, that change can't just happen to everyone at once; it needs a versioning strategy decided before the first breaking change is needed, not improvised in the middle of one:
| Strategy | Example | Tradeoff |
|---|---|---|
| URL path | /v2/orders | Explicit and cacheable, but "clutters" the URL and encourages permanent duplication of endpoints |
| Header | Accept: application/vnd.api+json;version=2 | Keeps URLs clean, but harder to test in a browser and easy for clients to omit by accident |
| Query parameter | /orders?version=2 | Simple to add, but easy to forget and awkward to treat as part of the resource's identity |
None of these is a universally "correct" choice — the practical point is picking one deliberately and applying it consistently, so that a breaking change has a defined, non-improvised path to ship without cutting off every existing client at once.
Paginate anything that can grow without bound
An endpoint like GET /orders that returns all of a user's orders works fine in a demo and becomes a real problem the moment a heavy user has fifty thousand of them — an unbounded response is both a performance and a memory problem for both sides. Cursor-based pagination (?after=<opaque_cursor>&limit=50) is generally preferred over offset-based (?offset=1000&limit=50) for anything backed by a frequently-changing dataset, since offset-based pagination can skip or repeat items if rows are inserted or deleted between page requests — a subtle correctness bug that's easy to miss until it shows up as a real user complaint.
Keep error responses structured and consistent
A client needs more than a bare status code to handle an error well — a consistent JSON error body (a machine-readable error code, a human-readable message, and ideally which field caused a validation failure) lets client code branch on error.code reliably instead of parsing prose. Consistency matters more than the exact shape chosen: an API where every endpoint's error response has a different structure forces every integration to special-case each one.
Why this matters in an interview
API design questions reward showing that you treat the API surface as a deliberate artifact, not an incidental byproduct of the implementation: naming resources as nouns, planning a versioning strategy up front, pagination for anything unbounded, and a consistent error contract. Tying it back to the request-lifecycle diagram above — where auth, rate limiting, and idempotency each have an explicit place — is a strong way to show the whole module clicking together into one coherent answer, rather than as eight disconnected facts.
Designing an explicit versioning strategy from day one: pros and cons
Pros
- Breaking changes ship without cutting off every existing client at once
- Clients can opt into a new version on their own schedule
- Makes the API's evolution predictable instead of ad hoc
Cons
- Maintaining multiple live versions is real ongoing engineering cost
- Requires discipline to actually deprecate and retire old versions
- Adds a small amount of upfront design overhead before the first breaking change is even needed
Further Reading
- Microsoft REST API Guidelines — a detailed, widely referenced set of conventions for resource naming, versioning, pagination, and errors.
- Google API Design Guide — Google's own guidance on designing consistent, evolvable APIs at scale.
Saved locally in your browser — visible in the sidebar as you go.