Why API Design Matters in Interviews
API design questions appear in senior system design interviews: “Design the Twitter API,” “Design a payment API,” or “How would you design the API for our checkout system?” Good API design affects developer experience, performance, security, and long-term maintainability. The three dominant paradigms: REST (most common, stateless HTTP), GraphQL (query language, single endpoint), and gRPC (binary RPC, efficient inter-service communication).
RESTful API Design
REST (Representational State Transfer) uses HTTP methods (GET, POST, PUT, PATCH, DELETE) on resource-oriented URLs. Key principles:
- Resource naming: use nouns, not verbs. /users/123/orders not /getUserOrders/123. Plural nouns for collections: /users, /orders, /products.
- HTTP methods: GET (read, idempotent, cacheable), POST (create, non-idempotent), PUT (full update, idempotent), PATCH (partial update), DELETE (remove, idempotent).
- Status codes: 200 OK, 201 Created (POST success), 204 No Content (DELETE success), 400 Bad Request (invalid input), 401 Unauthorized (not authenticated), 403 Forbidden (not authorized), 404 Not Found, 409 Conflict (duplicate resource), 422 Unprocessable Entity (validation error), 429 Too Many Requests, 500 Internal Server Error.
- Versioning: URL versioning (/v1/users) is most visible. Header versioning (Accept: application/vnd.myapi.v2+json) is cleaner but less discoverable. Query parameter versioning (?version=2) is simple. Use URL versioning for public APIs — maximum clarity for clients.
- Pagination: cursor-based pagination for large datasets (stable even if new items are inserted). next_cursor is an encoded pointer to the next page. Offset-based (?page=2&limit=20) is simpler but inconsistent with inserts/deletes during pagination.
GraphQL
GraphQL gives clients control over what data they receive. A single endpoint (POST /graphql) accepts a query specifying exactly which fields are needed:
query {
user(id: "123") {
name
email
orders(last: 5) {
id
total
items { name, quantity }
}
}
}
Advantages: eliminates over-fetching (REST returns all fields; GraphQL returns only requested fields) and under-fetching (no N+1 endpoints needed; one query can span multiple resources). Excellent for mobile apps (limited bandwidth). Strongly typed schema enables automatic documentation and tooling.
Disadvantages: N+1 query problem at the resolver level (each item.orders resolver fires a separate DB query — mitigate with DataLoader for batching). Harder to cache (POST requests aren’t cached by CDN/browser). Complex queries can overload the server (query depth limit and cost limits are required). Not ideal for file uploads (multipart form workarounds).
Use GraphQL when: you have multiple client types with different data needs (mobile vs web), the data graph is complex and deeply nested, or you want a single unified API for a microservices backend.
gRPC
gRPC uses Protocol Buffers (protobuf) for binary serialization over HTTP/2. Define the service in a .proto file:
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (stream User);
}
message User {
string id = 1;
string email = 2;
int64 created_at = 3;
}
Advantages: 3-10× more efficient than JSON over HTTP/1.1 (binary vs text, HTTP/2 multiplexing). Auto-generated client libraries in 10+ languages. Streaming support (server streaming, client streaming, bidirectional). Strongly typed with generated code. Ideal for inter-service communication in microservices (internal APIs).
Disadvantages: not human-readable (binary format — need a tool to inspect). Browser clients require gRPC-Web proxy (browsers don’t support raw HTTP/2 framing). Schema changes require careful backward compatibility (field numbers must not change). Overkill for public APIs.
Use gRPC when: high-throughput internal service-to-service communication, real-time bidirectional streaming (chat, live updates), or polyglot microservices needing strongly typed contracts.
API Design Checklist
- Idempotency: POST endpoints should accept an Idempotency-Key header. Retrying with the same key returns the same result.
- Rate limiting: return 429 with Retry-After and X-RateLimit-* headers.
- Authentication: OAuth 2.0 for user authorization, API keys for service-to-service. JWT for stateless auth tokens (include user_id and role in payload, sign with secret).
- Pagination: always paginate list endpoints. Never return unbounded lists.
- Error format: consistent error response: {error: {code: “INVALID_AMOUNT”, message: “Amount must be positive”, details: {…}}}.
- Backward compatibility: new optional fields are backward-compatible. Removing or renaming fields is a breaking change — bump the API version.
- Webhooks: for async events, provide webhook delivery with exponential retry, HMAC signature verification, and event replay.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “When should you choose REST vs GraphQL vs gRPC?”,
“acceptedAnswer”: { “@type”: “Answer”, “text”: “Use REST for simple public APIs and CRUD operations. Use GraphQL when clients need flexible queries and you want to avoid over-fetching (mobile apps, complex frontends). Use gRPC for internal microservice communication where performance matters—binary Protocol Buffers are 3-10x faster than JSON and gRPC supports streaming.” }
},
{
“@type”: “Question”,
“name”: “What makes an HTTP method idempotent and why does it matter for API design?”,
“acceptedAnswer”: { “@type”: “Answer”, “text”: “An idempotent operation produces the same result regardless of how many times it is called. GET, PUT, DELETE, and HEAD are idempotent; POST is not. Idempotency matters for retries: clients can safely retry idempotent requests after network failures without risk of duplicate side effects.” }
},
{
“@type”: “Question”,
“name”: “How do you version a REST API without breaking existing clients?”,
“acceptedAnswer”: { “@type”: “Answer”, “text”: “Common strategies: URL versioning (/v1/, /v2/) is explicit and easy to route; header versioning (Accept: application/vnd.api+json;version=2) keeps URLs clean; query parameter versioning (?version=2) is simple but pollutes logs. URL versioning is most practical. Always support old versions for at least one deprecation cycle and communicate timelines clearly.” }
}
]
}