API Design
Overview
API design is a dedicated step in the system design interview for good reason: a well-designed API is the contract between frontend and backend, between services, and between your system and the external world. An interviewer who watches you define endpoints fluently, with correct HTTP methods, consistent resource naming, proper error shapes, and thoughtful pagination, sees evidence that you have actually built production APIs, not just read about them.
Step 4 in the Nalar Linuwih interview framework is "Define API." It sits between resource estimation and high-level design because the API surface drives the implementation choices. The endpoints you define tell you which operations need to be transactional, which need pagination, which need idempotency guarantees, and which will be the most heavily loaded. All of this feeds back into your architecture.
API design also signals engineering maturity. A candidate who designs a URL shortener API with POST /createShortUrl (RPC-style) demonstrates less experience than one who designs POST /urls (resource-oriented, RESTful). A candidate who forgets pagination on a list endpoint is signaling that they have not dealt with the consequences, infinite scroll breaking at 10,000 records, responses timing out because the query fetched everything.
This lesson covers the principles, patterns, and common conventions you need to design clean APIs confidently in any system design interview.
Core Concepts
RESTful API Conventions: Resources, HTTP Methods, Status Codes
REST (Representational State Transfer) is an architectural style, not a protocol, that defines how clients and servers communicate over HTTP. The key principle is resource orientation: the URL identifies a resource (a thing), and the HTTP method describes the action (what to do to the thing).
Resource naming:
- Use nouns, not verbs:
/urlsnot/createUrl - Use plural nouns for collections:
/users,/posts,/urls - Use lowercase with hyphens for multi-word resources:
/short-urls,/rate-limits - Nest resources to express relationships:
/users/{user_id}/postsfor posts belonging to a user
HTTP methods and their semantics:
| Method | Semantics | Body | Idempotent | Safe |
|---|---|---|---|---|
| GET | Retrieve a resource | No | Yes | Yes |
| POST | Create a resource | Yes | No | No |
| PUT | Replace a resource entirely | Yes | Yes | No |
| PATCH | Partially update a resource | Yes | No | No |
| DELETE | Remove a resource | Optional | Yes | No |
HTTP status codes you must know:
| Code | Meaning | When to use |
|---|---|---|
| 200 OK | Success | GET, PUT, PATCH responses |
| 201 Created | Resource created | POST responses |
| 204 No Content | Success, no body | DELETE responses |
| 301 Moved Permanently | Permanent redirect | URL shortener redirects |
| 302 Found | Temporary redirect | Temporary URL redirects |
| 400 Bad Request | Invalid client input | Validation errors |
| 401 Unauthorized | Missing authentication | Auth token absent |
| 403 Forbidden | Insufficient permissions | Auth token present, wrong scope |
| 404 Not Found | Resource does not exist | Unknown ID |
| 409 Conflict | State conflict | Duplicate creation |
| 422 Unprocessable Entity | Semantic validation error | Field value invalid |
| 429 Too Many Requests | Rate limit exceeded | Rate limiting |
| 500 Internal Server Error | Server-side failure | Unexpected errors |
Endpoint Design Patterns: CRUD, Nested Resources, Bulk Operations
CRUD pattern for a URL shortener:
POST /urls → create a new short URL
GET /urls/{short_code} → get URL details (or redirect)
PATCH /urls/{short_code} → update destination URL
DELETE /urls/{short_code} → delete short URL
GET /urls → list all URLs for authenticated user
Nested resources express hierarchical relationships:
GET /users/{user_id}/posts → list posts by user
POST /users/{user_id}/posts → create post for user
GET /users/{user_id}/posts/{post_id} → get specific post
Limit nesting to one or two levels, deeper nesting becomes unwieldy and couples the API to your data model.
Bulk operations for efficiency:
POST /urls/batch → create multiple short URLs in one request
DELETE /urls/batch → delete multiple short URLs
Bulk operations reduce network round-trips when creating or deleting many resources at once. Define idempotency behavior clearly: should a batch fail atomically (all or nothing) or should it report partial success?
Action endpoints (where REST gets awkward):
Some operations do not fit CRUD neatly. Examples:
POST /users/{user_id}/follow → follow a user (creates a relationship)
POST /posts/{post_id}/publish → publish a draft post
POST /payments/{id}/cancel → cancel a payment
These are acceptable, REST is a guideline, not a religion. Use resource-oriented patterns where they fit naturally.
Pagination: Cursor-Based vs. Offset-Based
Any endpoint that returns a list of items must have pagination. Without it, a GET /posts for a user with 100,000 posts will timeout or return a multi-megabyte response.
Offset-based pagination:
GET /posts?limit=20&offset=40
Returns 20 records starting from position 40 in the result set.
Pros:
- Simple for clients to implement
- Allows random access ("jump to page 5")
- Easy to implement with SQL
LIMITandOFFSET
Cons:
- Inconsistent if records are inserted or deleted between pages (the "shifting problem": if you delete a record between page 1 and page 2 requests, you may skip or repeat a record)
- Poor performance at high offsets, the database must scan all records up to the offset
- Not suitable for real-time feeds where the dataset changes frequently
Cursor-based pagination:
GET /posts?limit=20&cursor=eyJpZCI6MTIzfQ== (cursor is a base64-encoded position)
The cursor encodes the position of the last returned record (often its ID or timestamp). The next page request starts after that position.
Pros:
- Consistent: inserts and deletes between requests do not cause duplicate or skipped records
- Performant: database can use an indexed range scan (
WHERE id > ?) instead of scanning all preceding records - Scales to arbitrary list sizes
Cons:
- No random access, you cannot "jump to page 5"
- Slightly more complex to implement client-side
- Requires a stable, ordered key (ID, timestamp)
Rule of thumb: Use cursor-based pagination for feeds, timelines, and any list that changes frequently. Use offset-based for static or slowly-changing administrative lists where random access is needed.
Idempotency and Safe Methods
Idempotency means that making the same request multiple times has the same effect as making it once. Idempotent operations are critical for reliability because they allow safe retries without fear of side effects.
HTTP method idempotency:
- GET, HEAD, OPTIONS are idempotent and safe (read-only, no side effects)
- PUT, DELETE are idempotent by definition, replacing or deleting a resource twice has the same result as doing it once
- POST is NOT inherently idempotent, calling
POST /urlstwice creates two short URLs
For write operations that must be idempotent, use an idempotency key:
POST /payments
Idempotency-Key: user_123_order_456_timestamp_1234567890
The server stores processed idempotency keys and returns the same response for duplicate requests with the same key. Stripe's payment API is the canonical example, payments are never double-charged because the client includes an idempotency key.
In interviews, mention idempotency whenever you design endpoints for:
- Payment creation and processing
- Order creation
- Notification sending (prevents duplicate alerts)
- Any mutation that should not be applied twice on retry
API Versioning Strategies
APIs evolve. When you add new fields or change behavior, clients built against the old API must not break. API versioning is how you manage this.
Option 1: URL path versioning (most common in practice)
GET /v1/users/{id}
GET /v2/users/{id}
Pros: immediately obvious in logs, easy to route to different backends, bookmarkable. Cons: URL changes when version changes, which technically violates REST (the URL should identify a resource, not a version).
Option 2: Header versioning
GET /users/{id}
Accept: application/vnd.myapi.v2+json
Pros: keeps URLs clean, closer to REST ideals. Cons: less visible, harder to test in browsers, requires custom Accept header logic.
Option 3: Query parameter versioning
GET /users/{id}?version=2
Pros: easy to pass in any HTTP client. Cons: query parameters imply the version is optional or a filter, which is semantically wrong.
In system design interviews, say you use URL path versioning and explain why: "URL path versioning is the most explicit and operationally straightforward, each version can be routed to a separate backend, and you can deprecate old versions by shutting down the old route."
Authentication and Authorization in API Context
Authentication verifies who you are. Authorization determines what you can do.
Common API authentication mechanisms:
- API Keys: a long random token passed in a header or query parameter. Simple, stateless, suitable for service-to-service calls. No user context.
Authorization: Bearer api_key_abc123 - OAuth 2.0: delegated authorization. A user grants a third-party app access to their resources without sharing credentials. The app receives an access token. Used by Google, GitHub, etc. for third-party integrations.
- JWT (JSON Web Token): a self-contained token that encodes claims (user ID, scopes, expiry) and is signed with a secret or private key. Stateless, the server can verify the token without a database lookup. Used widely for user session tokens.
In interviews, you do not need deep OAuth expertise. Say: "For user authentication, I'll use JWTs. The token carries user_id and role claims, verified with a shared secret. For service-to-service calls, I'll use API keys stored in the secrets manager."
Rate Limiting at the API Layer
Rate limiting protects the API from abuse and ensures fair usage across clients. It is enforced at the API gateway or the application server.
Common rate limiting strategies:
- Token bucket: each client has a bucket of tokens that refills at a fixed rate. Each request consumes a token. Allows short bursts while enforcing an average rate.
- Leaky bucket: requests are processed at a fixed rate, regardless of burst. Smooths out traffic spikes.
- Fixed window counter: count requests per client per time window (e.g., 100 requests per minute). Simple but allows burst at window boundaries.
- Sliding window: more accurate than fixed window; counts requests over a rolling time window.
Rate limit responses use HTTP 429:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please retry after 30 seconds.",
"retry_after": 30
}
In interviews, mention rate limiting at the API layer when designing any public-facing API, particularly ones dealing with user-generated content creation, authentication attempts, or expensive operations.
Error Handling: Consistent Error Response Shapes
A well-designed API returns consistent error responses. Clients should always know what format to expect. A common convention:
{
"error": {
"code": "resource_not_found",
"message": "The URL with short code 'abc123' was not found.",
"details": {
"short_code": "abc123"
}
}
}
Key principles:
- Use a machine-readable error
codefor programmatic handling - Use a human-readable
messagefor debugging - Include
detailswith context about what went wrong - Always use the appropriate HTTP status code, do not return 200 with an error body
- Never expose stack traces or internal system details in error responses
GraphQL vs. REST vs. gRPC: When to Mention Alternatives
In most system design interviews, REST is the right default. Mention alternatives only when they genuinely fit:
GraphQL: useful when clients need to query flexible, nested data shapes and the frontend team consumes many different entity combinations. Facebook and GitHub use it. Trade-off: complex server implementation, caching is harder (no URL-level cache invalidation).
gRPC: useful for service-to-service communication where performance is critical. Uses Protocol Buffers (binary format), strongly-typed contracts, and built-in streaming. Trade-off: not human-readable, limited browser support without a proxy layer.
In interviews, say: "I'll design RESTful HTTP APIs for the external-facing interface. For internal service-to-service communication, gRPC is a good fit, it has lower overhead and enforces contract schemas. But for simplicity, REST is fine unless we have specific performance requirements for internal calls."
Mermaid Diagram 1: API Request Lifecycle
sequenceDiagram
participant Client as Client (Browser/App)
participant GW as API Gateway
participant Auth as Auth Service
participant RL as Rate Limiter
participant App as App Server
participant DB as Database
Client->>GW: POST /urls\nAuthorization: Bearer <jwt>
GW->>Auth: Validate JWT token
Auth-->>GW: user_id: 12345, scopes: [write]
GW->>RL: Check rate limit for user_id 12345
RL-->>GW: 47 of 60 requests used (OK)
GW->>App: Forward request with user_id header
App->>DB: INSERT INTO urls (short_code, destination_url, user_id)
DB-->>App: url_id: 9876, created_at: 2026-06-24T12:00:00Z
App-->>GW: HTTP 201 Created\n{"id": "9876", "short_code": "abc123"}
GW-->>Client: HTTP 201 Created\n{"id": "9876", "short_code": "abc123"}
Mermaid Diagram 2: Pagination Strategy Decision
graph TD
Q["Does the list change frequently?\n(inserts/deletes between page requests)"]
Static["Use Offset-Based Pagination\nGET /items?limit=20&offset=40\n\nPros: random access, simple SQL\nCons: stale pages on live data"]
Dynamic["Use Cursor-Based Pagination\nGET /items?limit=20&cursor=<opaque_token>\n\nPros: consistent, scalable, no skipped records\nCons: no random access"]
AdminList["Admin lists, reports\nSlowly-changing data\nRandom page access needed"]
FeedTimeline["News feeds, timelines\nFrequently updated\nInfinite scroll patterns"]
Q -->|"No, relatively static"| Static
Q -->|"Yes, real-time or frequently updated"| Dynamic
Static --> AdminList
Dynamic --> FeedTimeline
Interview Application
Presenting API Design Concisely: The Table Format
Time is limited in interviews. Rather than describing each endpoint in prose, use a compact table format. State the table format explicitly: "I'll define the core API endpoints in a table, Endpoint, Method, Request body, Response body."
Example for a URL shortener:
| Endpoint | Method | Request | Response |
|---|---|---|---|
/urls |
POST | {destination_url, custom_alias?, expiry?} |
{id, short_code, short_url, created_at} |
/urls/{short_code} |
GET | , | 301 redirect to destination |
/urls/{short_code} |
GET (with ?details=true) |
, | {id, short_code, destination_url, click_count} |
/urls/{short_code} |
PATCH | {destination_url?, expiry?} |
{id, short_code, updated_at} |
/urls/{short_code} |
DELETE | , | 204 No Content |
/users/me/urls |
GET | ?limit=20&cursor=<token> |
{urls: [...], next_cursor} |
This table conveys more information in 30 seconds than prose descriptions of each endpoint would take 5 minutes to communicate.
Mentioning Idempotency and Rate Limiting Proactively
Proactively surfacing idempotency and rate limiting signals API design experience. Integrate them naturally as you define endpoints:
"For the URL creation endpoint, I'll require an idempotency key in the header, this ensures that if the client retries on a timeout, we don't create duplicate short URLs. The key can be a client-generated UUID stored in the database against the created URL. If we see the same key again, we return the original response."
"I'll add rate limiting at the API gateway layer, 60 URL creations per user per minute, with a 429 response that includes a Retry-After header. This protects against abuse without impacting legitimate users who create links occasionally."
Connecting API Design Back to NFRs
Every API design choice can be connected back to NFRs:
- Cursor-based pagination → "supports our consistency requirement, users should see a stable list even as new URLs are created"
- Idempotency keys → "supports our durability requirement, write operations are safe to retry without data corruption"
- Rate limiting → "supports our availability requirement, abusive clients cannot degrade service for others"
- Versioning strategy → "supports our maintainability requirement, we can evolve the API without breaking existing clients"
Common Mistakes
Designing RPC-style endpoints instead of resource-oriented endpoints. POST /createShortUrl, GET /getUserById, POST /sendNotification are not RESTful, they embed verbs in the URL. Use /urls, /users/{id}, and /notifications instead. This mistake signals unfamiliarity with REST conventions.
Forgetting pagination for list endpoints. Any endpoint that returns a collection must have pagination. "What if the user has 10,000 URLs?" is a question every interviewer will ask if you omit pagination. Define the pagination strategy (cursor vs. offset) and the response shape (next_cursor, has_more, or total_count).
Not defining error responses. Describing only the happy path is a red flag. Always say how the API responds to invalid inputs, unauthorized access, and missing resources. What is the response body for a 404? For a 429? Consistent error shapes are a sign of production experience.
Over-designing with GraphQL when REST suffices. GraphQL is an excellent tool for specific use cases, flexible client-driven queries over complex graphs. For most system design interview problems, REST is simpler, more cacheable, and equally powerful. Proposing GraphQL without clear justification signals over-engineering.
Ignoring idempotency for write operations. Any write that has real-world consequences (payments, notifications, order creation) must be idempotent. If you design a POST /payments endpoint without discussing idempotency, an interviewer at a financial company will notice immediately.
Key Vocabulary
REST (Representational State Transfer), An architectural style for web APIs where resources are identified by URLs and actions are expressed via HTTP methods. In interviews: "I'll design a RESTful API, resource-oriented URLs, standard HTTP methods, consistent status codes."
Resource, A named concept that an API exposes. Resources are nouns: users, posts, urls, payments. In interviews: "The core resource in a URL shortener is the URL mapping, identified by its short code."
Endpoint, A specific URL path that accepts requests. Each endpoint implements operations on a resource. In interviews: "The critical endpoints are POST /urls for creation and GET /urls/{code} for redirect resolution."
Idempotency, The property that making the same request multiple times has the same effect as making it once. Critical for safe retries. In interviews: "I'll add an Idempotency-Key header to URL creation, if a client retries on timeout, we return the original result instead of creating a duplicate."
Pagination, Breaking a large list result into smaller pages, returned one page at a time. Required for any list endpoint. In interviews: "I'll use cursor-based pagination for the URL list endpoint, offset-based would show inconsistent results as new URLs are created."
Cursor-Based Pagination, A pagination strategy where the client provides an opaque token (cursor) encoding the position of the last seen record. The server returns the next page starting after that position. In interviews: "Cursor-based pagination is more consistent for frequently-updated feeds, there are no skipped or duplicated records even when new items are inserted."
Rate Limiting, Controlling the number of requests a client can make within a time window to protect the system from abuse and ensure fair usage. In interviews: "I'll enforce rate limiting at the API gateway, 60 requests per minute per user, with 429 responses that include a Retry-After header."
API Gateway, A server that sits in front of backend services and handles cross-cutting concerns: authentication, rate limiting, SSL termination, request routing, logging. In interviews: "The API gateway handles auth and rate limiting centrally, the application servers only see pre-validated requests with user context in the headers."
HTTP Status Code, A 3-digit numeric code in an HTTP response that indicates the result of the request. Categorized by first digit: 2xx success, 3xx redirect, 4xx client error, 5xx server error. In interviews: "URL shortener redirects return 301 Moved Permanently, this lets browsers cache the redirect and skip hitting our servers on repeat visits."
API Versioning, The practice of maintaining multiple versions of an API simultaneously so that clients built against older versions continue to work when the API evolves. In interviews: "I'll use URL path versioning, /v1/ and /v2/, so we can run both versions simultaneously during migration periods."
Summary
A well-designed API is the most visible artifact of an interview, it is the first thing you put on the whiteboard that interviewers can evaluate concretely. A clean API design communicates production experience: you have dealt with the consequences of missing pagination, you know why idempotency matters for payment flows, you understand that REST conventions exist because they make APIs predictable across teams.
The table format, Endpoint, Method, Request, Response, is your best tool for communicating API design quickly under time pressure. Use it, reference it back to your NFRs, and be ready to justify each choice with a one-sentence trade-off statement.
The next lesson, Domain Knowledge, builds on your API design foundation by showing you how domain-specific context (calendar scheduling semantics, geospatial coordinates for ride-sharing, inventory reservation for ticketing) shapes the specific resources and operations you expose.
