API Design Patterns & Interview Cheat Sheet
Fast-access reference for common API design patterns, best practices, and interview talking points.
Authentication & Authorization Patterns
┌──────────────────┬───────────────────────────────────────────────────────────┐
│ Method │ How it works │
├──────────────────┼───────────────────────────────────────────────────────────┤
│ API Key │ Static secret in header: X-API-Key: abc123 │
│ │ Simple but can't expire, hard to rotate │
├──────────────────┼───────────────────────────────────────────────────────────┤
│ Basic Auth │ Base64(username:password) in Authorization header │
│ │ Insecure without HTTPS, sent every request │
├──────────────────┼───────────────────────────────────────────────────────────┤
│ Bearer Token │ Authorization: Bearer <token> │
│ │ Usually a JWT or opaque token from OAuth │
├──────────────────┼───────────────────────────────────────────────────────────┤
│ JWT │ Self-contained: header.payload.signature │
│ │ Stateless — server verifies signature, no DB lookup │
├──────────────────┼───────────────────────────────────────────────────────────┤
│ OAuth 2.0 │ Delegated access — user grants app access to their data │
│ │ Authorization Code, Client Credentials, Device flows │
├──────────────────┼───────────────────────────────────────────────────────────┤
│ mTLS │ Both client and server present certificates │
│ │ Strongest — used in microservice-to-microservice │
└──────────────────┴───────────────────────────────────────────────────────────┘
JWT Structure
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← Header (base64)
.eyJzdWIiOiI0MiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcwNDA2NzIwMH0 ← Payload
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← Signature
Decoded payload:
{
"sub": "42", // subject (user ID)
"role": "admin",
"exp": 1704067200, // expiry (Unix timestamp)
"iat": 1704063600 // issued at
}
JWT is not encrypted by default — payload is base64, anyone can read it. Use JWE for encryption. Never store secrets in JWT payload.
Rate Limiting Algorithms
Token Bucket (most common):
- Bucket fills with N tokens per second
- Each request consumes 1 token
- Allows bursting up to bucket capacity
Sliding Window:
- Count requests in the last N seconds
- More accurate than fixed window
- Prevents boundary bursts
Fixed Window:
- Count resets every N seconds
- Simple but allows 2x burst at window boundary
- Window resets: 10:00, 10:01, 10:02...
Leaky Bucket:
- Requests enter queue, process at fixed rate
- Smooths out traffic, no bursting allowed
API Versioning Comparison
Strategy Example Pros Cons
──────────────── ──────────────────────────────── ───────────── ─────────────────
URL Path /v1/users, /v2/users Simple URL pollution
Query Param /users?version=2 Optional Easy to miss
Accept Header Accept: application/vnd.v2+json Clean URLs Less discoverable
Subdomain v2.api.example.com Full isolate DNS overhead
Custom Header X-API-Version: 2 Flexible Non-standard
Common API Design Patterns
Batch Operations
POST /batch
{
"operations": [
{ "method": "GET", "path": "/users/1" },
{ "method": "DELETE", "path": "/users/2" },
{ "method": "POST", "path": "/users", "body": {"name": "New"} }
]
}
Response:
{
"results": [
{ "status": 200, "body": { "id": 1, "name": "Alice" } },
{ "status": 204 },
{ "status": 201, "body": { "id": 99, "name": "New" } }
]
}
Async / Long-Running Operations
POST /reports/generate
→ 202 Accepted
{ "jobId": "job-abc", "statusUrl": "/jobs/job-abc" }
GET /jobs/job-abc
→ 200 OK { "status": "processing", "progress": 45 }
GET /jobs/job-abc (later)
→ 200 OK { "status": "complete", "resultUrl": "/reports/42" }
GET /reports/42
→ 200 OK { "data": [...] }
Idempotency Keys (Safe Retries)
POST /payments
Idempotency-Key: client-generated-uuid-here
→ 201 Created { "id": "pay_abc" }
# Client retries (network timeout):
POST /payments
Idempotency-Key: client-generated-uuid-here ← same key
→ 200 OK { "id": "pay_abc" } ← returns original, no duplicate charge!
Error Response Design
// Consistent error format (don't just return a string!)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Must be a valid email address",
"value": "notanemail"
},
{
"field": "age",
"message": "Must be >= 0",
"value": -1
}
],
"requestId": "req-abc-123",
"timestamp": "2025-01-01T00:00:00Z",
"docs": "https://api.example.com/errors/VALIDATION_ERROR"
}
}
Real-Time Patterns: Decision Tree
Need real-time updates?
No → Standard REST/GraphQL
Yes ↓
Client sends AND receives in real-time?
Yes → WebSocket
No ↓
Server pushes only?
Yes ↓
Is client a browser?
Yes → SSE (simple) or WebSocket (if need binary)
No → WebSocket or gRPC streaming
Need reliability / message history?
Yes → Message Queue (Kafka, RabbitMQ, SQS) + polling
No → SSE or WebSocket
API Security Checklist
Authentication & Authorization:
☐ All endpoints require auth (except public ones)
☐ Use HTTPS everywhere (TLS 1.2+)
☐ JWT: verify signature, check exp, validate claims
☐ Principle of least privilege on tokens/scopes
☐ Rotate API keys on compromise
Input Validation:
☐ Validate all inputs at API boundary
☐ Parameterized queries (no SQL injection)
☐ Sanitize for XSS in any HTML contexts
☐ Limit request body size
☐ Validate Content-Type
Rate Limiting & Protection:
☐ Rate limit by IP and by user
☐ Return 429 with Retry-After header
☐ Protect auth endpoints more aggressively
Headers:
☐ CORS: restrict to allowed origins
☐ X-Content-Type-Options: nosniff
☐ X-Frame-Options: DENY
☐ Strict-Transport-Security (HSTS)
☐ Don't expose server/framework version
Sensitive Data:
☐ Never log auth tokens or passwords
☐ Mask/truncate sensitive fields in logs
☐ Don't return sensitive data in errors
☐ Use 404 (not 403) to hide existence of private resources
Interview Questions & Talking Points
"Design an API for X"
- Identify resources (nouns)
- Define CRUD operations + HTTP methods
- Design URL structure (hierarchical, plural nouns)
- Pick status codes for each outcome
- Define error response format
- Consider pagination, filtering, sorting
- Think about versioning strategy
- Add auth (what level of access for what?)
"REST vs GraphQL vs gRPC"
REST: Resource-oriented, widely understood, HTTP caching works
GraphQL: Flexible queries, solves over/under-fetching, typed schema
gRPC: Fast binary, streaming, strongly typed, ideal for microservices
Key question: Who are the clients? Public → REST. Mobile bandwidth-sensitive → GraphQL. Internal services → gRPC.
"How would you handle API versioning?"
For public APIs: URL path versioning (/v1/, /v2/) — most discoverable
For internal: Header-based versioning — cleaner URLs
Always: maintain old versions for deprecation window (6-12 months)
Communicate breaking changes via changelog, email, deprecation headers
"How do you handle long-running operations?"
Return 202 Accepted immediately
Include a job ID and status polling URL
Support webhooks for push notification on completion
Consider: SSE or WebSocket for progress streaming
Quick Compare: All Protocols
mindmap
root((Protocol Selection))
REST
Public APIs
Simple CRUD
HTTP caching
Wide client support
GraphQL
Complex nested data
Multiple client types
Mobile apps bandwidth
Rapid frontend iteration
gRPC
Microservices
Low latency required
Strongly typed contracts
Bidirectional streaming
WebSocket
Chat apps
Multiplayer games
Collaborative tools
Live trading
SSE
Notifications
Live dashboards
AI token streaming
Build logs
IPC
Same-host processes
High throughput local
OS-level performance
Database local connections