Lesson 1 of 5
The API Design Interview
"The interviewer doesn't want your endpoints. They want to watch you think." — a hiring manager, after a hundred design loops
Seven parts ago you couldn't say what a backend was. Now you can name resources, choose status codes, hash a password, paginate a feed, and make a retry safe. This final part turns all of it toward one goal: getting the offer. And the round where a backend engineer most often wins or loses is the "design the API for X" interview — a whiteboard (or CoderPad) prompt like "design the API for a ride-hailing app" or "sketch the endpoints for a comment system." It is the backend cousin of the system-design interview you met in Volume 5, but zoomed in on the API surface: the resources, the routes, the request and response shapes, the edge cases — not the sharding strategy.
It is also the round people fail while knowing the material, because they treat it as a trivia dump — blurting GET /users and going quiet — when it is really a conversation that reveals how you scope ambiguity, make trade-offs, and communicate under mild pressure. This chapter gives you a repeatable framework so you never stare at a blank board, then walks one prompt end-to-end so you feel the rhythm.
When someone says "design the API for X" and then goes silent, what do you actually do — in what order — so that forty minutes later you've produced a clean, defensible design and made the interviewer confident they'd want you on their on-call rotation?
What they're really testing
The endpoints are the artifact. The signal is everything around them. A strong candidate demonstrates, roughly in this order of what interviewers weight:
- Do you scope before you build? Weak candidates start drawing routes on assumptions. Strong ones ask "who calls this — a mobile app, a partner, an internal service?" first.
- Do you know the conventions cold? Plural nouns, the right status codes,
201+Locationon create,204on delete, honest4xx. This is table stakes (all of Part II). - Do you think about the hard parts unprompted? Auth, validation, pagination, idempotency, rate limits. Raising these before the interviewer asks is the single biggest differentiator.
- Can you reason about trade-offs? "Cursor pagination scales better than offset, but offset lets you jump to page 50 — for an infinite feed I'd pick cursor." Naming the alternative and choosing is the whole game.
- Do you communicate? Thinking aloud, stating assumptions, checking in. This is the same muscle as the behavioral loop in Volume 6 — an interview is a collaboration, not an exam.
Analogy A "design the API" interview is like being handed a plot of land and asked to draw a house. A nervous candidate immediately draws windows. A strong one first asks: how many people live here? what's the budget? which way does the sun rise? The windows are easy once the constraints are known — and the interviewer learns far more from the questions than from the windows. Your questions are not a delay before the real work; they are the work.
The framework: nine phases
Memorize this sequence. It fits on a mental index card and it means you always have a next move. The right-hand column tells you where in this Library each phase was taught in depth, so an interview follow-up can go as deep as they want.
| # | Phase | The question you answer | Deep dive |
|---|---|---|---|
| 1 | Clarify & scope | Who calls it? What must it do? What's out of scope? | Behavioral (V6) |
| 2 | Resources & operations | What are the nouns, and what can you do to each? | REST (Part II) |
| 3 | Design the endpoints | Methods, paths, request/response shapes, status codes | REST, Errors (Part II) |
| 4 | Auth & permissions | Who are you, and are you allowed to do this? | Part IV |
| 5 | Validation & errors | How is bad input rejected, and in what shape? | Validation, Errors (Part II) |
| 6 | Pagination & filtering | How do collections stay bounded and queryable? | Part VI |
| 7 | Reliability: rate limits & idempotency | Abuse control and safe retries | Part VI |
| 8 | Versioning & docs | How does it evolve without breaking clients? | Parts II & VII |
| 9 | Non-functionals & trade-offs | Scale, caching, observability — and what you'd change | Parts V, VI; System Design (V5) |
You will rarely get through all nine at equal depth in one session, and that's fine. Move top-to-bottom, spend the most time on phases 2–3 (the core artifact), and let the interviewer pull you deeper into whichever later phase interests them. Announcing the framework up front — "I'll clarify scope, model the resources, design the endpoints, then talk auth, validation, pagination, and how it scales" — is itself a strong signal: it tells them you've done this before.
Technique — narrate the map. Before you draw anything, say your plan out loud: "Let me start by clarifying scope, then I'll list the resources, design the endpoints, and we can go deep on auth and scale after." This does three things: it buys you thinking time, it lets the interviewer redirect you early ("skip auth, focus on the data model"), and it frames everything that follows as deliberate rather than improvised.
The prompt: "Design the API for a social feed"
Let's work one end-to-end. The prompt: design the API for a simple social app — users can post, comment on posts, and like posts. This is a favorite because it's small enough to finish yet rich enough to surface nesting, pagination, idempotency, and permissions. Watch how each phase produces a concrete decision.
Phase 1 — Clarify & scope
Do not touch a route yet. Ask, and state the assumptions you'll proceed on so a silent interviewer can correct you:
- Who are the clients? "I'll assume a mobile + web first-party app, so a JSON REST API over HTTPS. If partners consumed it I'd think harder about versioning and rate tiers."
- What's the feed? "I'll assume a reverse-chronological feed of posts from people you follow. A ranked/ML feed changes the read path a lot — I'll flag it but assume chrono."
- What's in a post? "Text plus optional image. I'll treat image upload as a separate concern — presigned URLs, per Part V — and keep the post body to a media reference."
- Scale? "I'll design for, say, low millions of users. That rules out offset pagination on the feed and pushes me toward cursors, but the endpoint shapes stay the same."
- Out of scope, to name explicitly: follows/friendship graph, notifications, DMs, moderation. "I'll assume
followexists as a resource but won't design it unless you want to."
In production The assumptions you state here quietly become the interview's contract. If you say "reverse-chronological feed" and later the interviewer asks "how would you rank it?", you have a graceful pivot ready instead of a panic. Interviewers respect a candidate who scoped narrowly on purpose and can say "that's a deliberate v1 cut; here's what v2 adds" far more than one who tried to design everything and finished nothing.
Phase 2 — Resources & operations
Name the nouns and the actions on each. This is the skeleton the endpoints hang on.
- Post — create, read, list (the feed), edit own, delete own.
- Comment — create on a post, list a post's comments, delete own.
- Like — like a post, unlike a post, (optionally) list who liked. A like is set membership: a (user, post) pair either exists or it doesn't.
- User — assume it exists from the auth system; we expose read-only public profiles.
The relationships, sketched as an entity diagram — drawing this on the board shows you're modeling data, not just URLs:
That }o--o{ between user and post is the many-to-many "likes" relationship — the hint that a like is best modeled as membership in a set, which will drive an idempotent design in phase 7. Calling that out now earns credit.
Phase 3 — Design the endpoints
Here is the core deliverable: the full endpoint table. Lead with it, then narrate the interesting rows. Note plural nouns, one level of nesting, correct success codes, and — critically — that a like uses PUT/DELETE (not POST) because it's idempotent.
| Operation | Method & path | Success | Auth | Notes |
|---|---|---|---|---|
| Create post | POST /posts |
201 + Location |
required | Body: { text, mediaId? } |
| Feed (list) | GET /posts |
200 |
required | Cursor-paginated; posts from people you follow |
| Read one post | GET /posts/:id |
200 |
optional | 404 if missing/deleted |
| Edit own post | PATCH /posts/:id |
200 |
author only | Partial: { text? } |
| Delete own post | DELETE /posts/:id |
204 |
author only | Soft-delete internally |
| List comments | GET /posts/:id/comments |
200 |
optional | Paginated |
| Add comment | POST /posts/:id/comments |
201 + Location |
required | Body: { text } |
| Delete comment | DELETE /comments/:id |
204 |
author only | Top-level route — comment has its own identity |
| Like a post | PUT /posts/:id/like |
204 |
required | Idempotent — like twice = liked once |
| Unlike a post | DELETE /posts/:id/like |
204 |
required | Idempotent — unlike when not liked = still fine |
| List likers | GET /posts/:id/likes |
200 |
optional | Paginated |
Three rows deserve a spoken justification:
DELETE /comments/:id, notDELETE /posts/:id/comments/:cid. Once a comment has a stable id, give it a top-level route rather than nesting two levels deep — the deeper URL couples you to a hierarchy for no gain (the "nest one level" rule from Part II).- Like is
PUT, notPOST.POST /posts/:id/likeswould imply "create a new like each call" — click twice, two likes. Modeling like as a singleton sub-resource (/posts/:id/like) withPUT= "ensure it's liked" andDELETE= "ensure it's not" makes the operation idempotent: a double-tap or a retried request lands in the same state. That single choice pre-answers the idempotency question in phase 7. - Create returns
201with aLocationheader pointing at the new resource, and the body is the created object — so the client doesn't need a second round-trip to read it.
Now let's make it concrete. Modeling the resource types, the write DTOs (no client-supplied id or authorId — the server owns those), and the route table in real TypeScript forces the shapes to be honest. This runs through the TS compiler:
Notice the design is encoded in the types: CreatePostDTO cannot carry an id or likeCount, so the compiler itself enforces "the server owns identity and counts." That's the point to make aloud — good API design and good typing are the same discipline.
Phase 4 — Auth & permissions
Two distinct questions, always separate them (Part IV):
- Authentication — who are you? "A bearer token — a JWT or an opaque session token — in the
Authorizationheader. Missing/invalid →401." Reads of public posts may be anonymous; writes require a user. - Authorization — are you allowed? "Editing or deleting a post requires you to be its author; otherwise
403. That's theauth: 'author'rows. A moderator role could override — that's RBAC, Part IV."
Say the golden rule out loud: the filter is not the fence. GET /posts?authorId=me narrows the feed, but the server still verifies the token's user owns a post before letting PATCH /posts/:id through. Never confuse "the client asked for its own data" with "the client can only reach its own data."
Think about it Should GET /posts/:id for a post you're not allowed to see return 403 or 404? A 403 confirms the post exists — leaking information to someone probing ids. Many mature APIs deliberately return 404 for "exists but you can't see it," so the caller can't distinguish "no such post" from "not yours." Being able to raise this trade-off unprompted signals real security instinct.
Phase 5 — Validation & errors
"Every write is validated server-side before it touches the database — type, presence, length, format — returning 400 (or 422) with a consistent error envelope" (Part II). Commit to one error shape for the whole API so clients write one handler:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{ "error": { "code": "validation_failed", "message": "text is required",
"details": [ { "field": "text", "issue": "required" } ] } }
And say the sin to avoid: never 200 OK with an error in the body — it breaks caching, monitoring, and client retries (Part II). Failures are 4xx/5xx, honestly.
Phase 6 — Pagination & filtering
"No collection is ever unbounded." The feed, comments, and likers are all paginated. For an infinite, frequently-changing feed, choose cursor (keyset) pagination over offset (Part VI):
GET /posts?limit=20&after=eyJpZCI6IjEwMDIiLCJ0cyI6MTIzfQ HTTP/1.1
HTTP/1.1 200 OK
{ "data": [ /* up to 20 posts */ ],
"page": { "nextCursor": "eyJpZCI6Ijk4MiJ9", "hasMore": true } }
Justify it: "Offset pagination on a live feed skips or duplicates rows when new posts arrive between pages, and OFFSET 100000 forces the database to scan and discard rows (ties back to indexing, Volume 7). A cursor encoding (createdAt, id) is stable and index-friendly. The trade-off is you can't jump to 'page 50' — which a feed never needs anyway." Naming the alternative and why you rejected it is exactly the trade-off reasoning they're grading.
Phase 7 — Reliability: rate limits & idempotency
Two things, both from Part VI:
- Rate limiting. "Per-user (and per-IP for anonymous) limits — e.g. a token bucket — on writes especially, to blunt spam and abuse. Over the limit →
429 Too Many Requestswith aRetry-Afterheader." - Idempotency. "Two of my endpoints are idempotent by design —
PUT/DELETE .../like— so a retried like is harmless. The one that isn't naturally idempotent isPOST /posts(a retry could double-post). I'd support anIdempotency-Keyheader on it: the client sends a unique key, the server stores the first result against that key and replays it on retry." This is the mature answer, and it pays off thePUT-for-like decision you made back in phase 3 — a callback that shows the design was coherent, not accidental.
Technique — plant seeds early, harvest late. Notice how the phase-3 choice to model likes with PUT made phase 7's idempotency answer nearly free. The best design interviews feel like this: an early, principled decision quietly pre-solves a later question. When it happens, say so — "this is why I chose PUT earlier" — because narrating the connection is what turns a list of good choices into the story of a good engineer.
Phase 8 — Versioning & docs
Briefly, unless pushed: "I'd version via a URL prefix (/v1/posts) or an Accept header — URL prefix is simplest for a first-party app (Part II). Additive changes (new optional fields) don't need a bump; removing or renaming a field does. And I'd publish an OpenAPI spec so clients get generated types and docs stay honest (Part VII)." One or two sentences here is usually enough — but having them ready shows range.
Phase 9 — Non-functionals & trade-offs
Close by connecting to the bigger picture (Volume 5 system design, Parts V–VI):
- Caching: "Public
GET /posts/:idis cacheable withCache-Control/ETag; the personalized feed is per-user so I'd cache at the application layer (a materialized feed in Redis) rather than at the CDN" (Part V). - Scale & counts: "
likeCount/commentCountare denormalized onto the post so the feed doesn't runCOUNT(*)per row; they're updated on like/comment, and reconciled by a background job" (Part V, jobs). - Observability: "Structured logs with a request id, latency and error-rate metrics per endpoint, and traces across the DB calls" (Part VI) — so on-call can answer "why is the feed slow?" fast.
Then invite the trade-off conversation explicitly: "If we expected a ranked feed or heavy partner use, I'd revisit the read path and versioning. Where would you like to go deeper?" Handing the interviewer the wheel at the end is a confident, collaborative close.
Communicating while you design
The design is only half the score; how you deliver it is the other half (Volume 6). Concretely:
- Think aloud. Silence reads as being stuck. Narrate even the obvious: "posts is plural, create is
POST, returns201…" - State assumptions, don't wait for permission. "I'll assume cursor pagination unless you'd prefer offset." This keeps momentum while staying correctable.
- Offer alternatives, then choose. "We could nest comments two levels deep or give them a top-level route — I'll go top-level because…" Naming the road not taken is the trade-off signal.
- Manage time. Get a complete, shallow design first (all nine phases lightly), then deepen where they steer. A finished shallow design beats a gorgeous half.
- Check in. "Does this direction work, or should I focus somewhere specific?" — collaboration, not monologue.
Common Mistakes
- Skipping clarification. Diving into routes on unstated assumptions. Always scope first — who calls it, what's in and out — and say the assumptions aloud.
- Endpoint dump, then silence. Listing
GET /posts,POST /postsand stopping. The routes are the start; auth, validation, pagination, idempotency are where the signal is. POSTfor a toggle. Modeling like/unlike asPOST(non-idempotent) so a double-tap creates two likes. Use aPUT/DELETEsingleton sub-resource for set membership.- Forgetting pagination. Designing
GET /poststhat returns everything. Every collection is bounded and paginated from the first draw. - Confusing filtering with authorization.
?authorId=meis a convenience, not a fence — the server still authorizes the caller on every write. 200 OKfor errors. Hiding failures in a success body. Failures are honest4xx/5xxwith one consistent error envelope.- Designing everything, finishing nothing. Trying to cover follows, notifications, DMs, and ranking. Scope narrowly, finish, then offer the extensions.
- Not talking. Treating it as a written exam. It's a conversation; narrate, offer alternatives, check in.
Thought experiment The interviewer says, "Great — now the product team wants a post to be shareable to a group and to a person, and to expire after 24 hours like a story." Don't panic-redesign. Which existing phase does each requirement touch? (Sharing → a new resource or a visibility field, phases 2–3; expiry → a expiresAt field plus a background sweep, phases 3 and 9; groups → a permissions model, phase 4.) The framework is exactly what lets you absorb a curveball calmly: every new requirement slots into a phase you already have.
POST /links with { url, customAlias? } → 201 + Location: /links/abc123 and body { code: "abc123", url, shortUrl }. GET /links/:code → 200 returns metadata; GET /links (auth'd) lists the caller's links, paginated; DELETE /links/:code → 204. The redirect itself is a separate concern: GET /:code on the public host returns 301/302 to the target. Mention: validate the URL, rate-limit creation, and that code generation (base62 of an id, or a random collision-checked string) is the interesting design sub-problem.401 if missing); the public redirect is anonymous. Only the owner can delete (403 otherwise). Pagination: GET /links?limit=20&after= with a cursor envelope — never return every link. Versioning: /v1/ URL prefix; adding an optional field (say expiresAt) is additive and needs no bump, while changing the response shape does. Bonus: an Idempotency-Key on POST /links so a retried create doesn't mint two codes.302 if you need per-click analytics (so clients keep asking you) vs 301 if you want browsers to cache and offload you — a real trade-off. Rate-limit creation per user; keep the redirect path lock-free and idempotent. This is where the API interview shades into the system-design interview (Volume 5) — signal that you know the boundary.Hands-on Practice the artifact for a fresh prompt: design the API for a restaurant bookings service (a user reserves a table for a time). Fill in the route table and the create-DTO validator below, applying the framework: what are the resources? which operations are idempotent? what must be validated? Aim for POST /bookings (create, 201), GET /bookings (the caller's bookings, paginated, 200), GET /bookings/:id (200), PATCH /bookings/:id (change party size / time, 200), DELETE /bookings/:id (cancel, 204, author only). Then decide: should "confirm a booking" be POST /bookings/:id/confirm or PATCH with a status — and can you make cancellation idempotent?
Summary
- The "design the API for X" interview is the backend cousin of system design (Volume 5), focused on the API surface — resources, routes, request/response shapes, edge cases. It grades your thinking and communication as much as your endpoints.
- Run a repeatable nine-phase framework: clarify & scope → resources & operations → design the endpoints → auth & permissions → validation & errors → pagination & filtering → rate limits & idempotency → versioning & docs → non-functionals & trade-offs. Announce it up front and move top-to-bottom.
- The core artifact is a clean endpoint table: plural nouns, correct methods and status codes (
201+Location,204, honest4xx), one level of nesting. Model set-membership toggles (like/unlike) with idempotentPUT/DELETE, notPOST. - Raise the hard parts unprompted — auth vs. authorization, server-side validation, bounded/cursor pagination, rate limits, idempotency keys — and connect early decisions to later payoffs.
- Communication is half the score: think aloud, state assumptions, offer alternatives then choose, manage time (finish shallow before going deep), and check in. It's a collaboration, not an exam (Volume 6).
- Every phase maps to a Part of this volume you can go arbitrarily deep on — so a follow-up is an opportunity, not a threat.
Next, we scale the lens back out: designing a whole service end-to-end — not just its API surface but its data model, dependencies, and deployment — the round where the API-design and system-design interviews meet.
Editorial reflection The candidates who ace this round are rarely the ones who memorized the most status codes. They're the ones who treat the interviewer as a colleague at a whiteboard — curious about the problem, honest about trade-offs, calm when the requirements shift. That is not a performance you put on for forty minutes; it's the same posture that makes someone good to work with for years. The interview is testing for a teammate, and the framework in this chapter is really just a structured way to be one: scope before you build, say what you're assuming, and never pretend a hard part isn't there.
Q&A · 0
Enrol to ask questions and join the discussion.
No questions yet — be the first to ask.