Background
Sections
IntroductionRequirements & Problem AnalysisConstraints & AssumptionsEstimation TechniquesFunctional vs Non-Functional RequirementsMoSCoW PrioritizationSystem Design FundamentalsArchitecture DiagramClass DiagramComponent DiagramData Flow Diagram (DFD)ER Diagram (Entity-Relationship Diagram)High Level Design (HLD)Low Level Design (LLD)Sequence DiagramState DiagramUse Case DiagramData StorageDocument StoresFile StorageGraph DatabasesIn-Memory DatabasesKey-Value StoresNewSQLNoSQL DatabasesObject StorageSQL Databases (RDBMS)Time-Series DatabasesWide-Column StoresDatabase ConceptsACID PropertiesCAP TheoremConsistency ModelsIndexingNormalization & DenormalizationReplicationSharding & PartitioningTransactions & Isolation LevelsScalabilityAuto-Scaling & ElasticityConsensus & Leader ElectionLoad BalancingReplication & Read ReplicasSharding & PartitioningVertical vs Horizontal ScalingAvailability & ReliabilityBackup & Data DurabilityCircuit BreakerData ConsistencyDisaster RecoveryFault Tolerance & FailoverGraceful DegradationHigh AvailabilityNetworkingCDNDNSFirewalls & VPNHTTP & HTTPSLoad Balancer & Reverse ProxyTCP/IP & UDPWebSocketsCachingCache InvalidationCache Read/Write PatternsCaching LayersEviction PoliciesRedis vs MemcachedMessaging & CommunicationDead-Letter QueueIdempotencyKafka vs RabbitMQ vs SQSMessage QueuesPub/SubCompute & ServicesAPI GatewayContainers & OrchestrationMonolith vs MicroservicesServerlessService DiscoveryService MeshWeb Server & App ServerAPI DesignAPI Versioning & IdempotencyAuthentication & AuthorizationGraphQLgRPCPaginationRate Limiting & ThrottlingRESTSecurityAuthentication & AuthorizationData PrivacyEncryptionInput Validation & InjectionOAuth2 & JWTSecrets ManagementXSS & CSRFStorage & File SystemsBackup & RetentionBlock vs File vs Object StorageData Lakes & WarehousesDistributed File SystemsEphemeral StorageObservability & MonitoringDistributed TracingHealth ChecksLoggingMetricsSLI, SLO, SLADesign PatternsBulkhead PatternCircuit Breaker PatternCreational PatternsRate Limiter PatternRetry PatternStructural & Behavioral Patterns

API Design

6 min read

The Contracts Between Systems

An API is a contract. It's the interface through which clients — browsers, mobile apps, other services, third-party developers — interact with your system, and once published, it becomes a promise you must keep. Good API design is therefore about far more than picking URLs: it's about choosing the right communication style for the job, evolving the contract without breaking the people who depend on it, letting clients interact safely over unreliable networks, controlling who can do what, protecting the system from abuse, and returning large datasets efficiently. These decisions shape the developer experience of everyone who touches your system and the operational reality of running it.

What makes API design a coherent topic is that these concerns recur regardless of the underlying architecture. Whether you expose REST, GraphQL, or gRPC, you still have to version the contract, secure it, rate-limit it, and paginate large responses. And the choices interact: a public REST API optimizes for different things (caching, readability, broad compatibility) than an internal gRPC API (performance, strong typing, streaming). Mastering API design means understanding both the menu of styles and the cross-cutting concerns that apply to all of them — so you can design interfaces that are pleasant to consume, safe to depend on, and cheap to operate.

When This Comes Up

  • System design interviews: Almost every design has an API surface, and interviewers probe it: "REST or GraphQL here, and why?" "How do you version this?" "How do you stop one client from overwhelming the service?" "How do you paginate a feed of millions of items?" Strong candidates justify the communication style for the traffic type and proactively address versioning, auth, rate limiting, and pagination.
  • Real architecture: These are daily decisions with lasting consequences. The API style you pick shapes performance and client experience; the versioning strategy determines how painlessly you can evolve; rate limiting and auth determine reliability and security. Getting them wrong is expensive to undo once clients depend on the contract.
  • Production concerns: Breaking changes that shatter integrations, retries that double-charge customers, a single client overwhelming the system, and pagination that grinds the database to a halt on deep pages are all real incidents rooted in API-design choices.

How the Sub-Topics Connect

The sub-topics divide into two groups: the communication styles (REST, GraphQL, gRPC) that define how clients and servers talk, and the cross-cutting concerns (versioning & idempotency, auth, rate limiting, pagination) that apply regardless of style:


1. REST

The default architectural style for web APIs: model everything as resources (nouns) addressed by URLs, manipulated with standard HTTP methods and status codes. REST's genius is leaning entirely on HTTP's existing machinery — so caching, proxies, and tooling work for free — and its statelessness is the foundation of horizontal scaling. Most production "REST" APIs sit at Richardson Level 2 (resource URLs + proper verbs and status codes), which is the pragmatic target. Its limitations — over-fetching, under-fetching / N+1, and server-dictated response shapes — are precisely what GraphQL and gRPC exist to address.


2. GraphQL

A query language that flips control of the response shape from server to client. Against a single endpoint and a typed schema, clients ask for exactly the fields and nesting they need — solving REST's over-fetching and under-fetching in one round trip. It excels when many clients have different data needs, when data is deeply nested/related, and when frontends evolve rapidly. The costs are real: caching is harder (single POST endpoint), the N+1 problem moves server-side (solved with DataLoader batching), and query abuse must be controlled. For simple CRUD or cache-heavy public APIs, REST is often still simpler.


3. gRPC

A high-performance, contract-first framework using Protocol Buffers (compact binary) over HTTP/2, where you define typed service methods in a .proto file and generate strongly-typed clients and servers. It delivers fast, strongly-typed remote procedure calls with native streaming in all directions — ideal for internal microservice (east-west) communication where performance and strong contracts compound across millions of calls. Its main limitation is that browsers can't speak it directly (needing gRPC-Web), which is why the common pattern is REST/GraphQL at the public edge and gRPC between internal services.


4. API Versioning & Idempotency

Two concerns that make an API safe to depend on. Versioning lets you evolve the contract (serving /v1 and /v2 side by side) without breaking existing clients — the key skill is distinguishing breaking from non-breaking changes and publishing a clear deprecation policy. Idempotency (via idempotency keys) lets clients safely retry requests after a network failure without duplicate side effects — the mechanism that keeps a retried POST /charges from double-billing. Together they protect clients across time (as the API evolves) and across failures (as requests are retried).


5. Authentication & Authorization

Every request must answer who are you? (authentication — verify identity via API key, token, or OAuth) and what may you do? (authorization — check permissions via RBAC, ABAC, scopes). They're separate steps with separate failure codes (401 vs 403). The common pattern authenticates once (often centrally at the API gateway) and authorizes per resource in the owning service. The most dangerous failure in multi-tenant APIs is broken object/tenant authorization — a valid token accessing another tenant's data — prevented by checking resource ownership against identity on every access. (Security internals are in Topic 12.)


6. Rate Limiting & Throttling

Capping how many requests a client can make protects the API from abuse and DoS, enforces fair usage and pricing tiers, and shields backends from overload. The token bucket algorithm is the popular default — allowing controlled bursts while enforcing an average rate — and fixed-window approaches suffer boundary-burst problems. Correct enforcement across many servers requires shared atomic state (Redis), typically at the gateway so abusive traffic is rejected before reaching backends. Transparency matters: return 429 with Retry-After and quota headers so well-behaved clients self-regulate.


7. Pagination

Breaking large result sets into fetchable pages. Offset-based (page 2, skip 20) is intuitive and allows jumping to any page, but degrades at deep offsets (scanning skipped rows) and produces duplicates/skips on changing data. Cursor-based (keyset — "items after this marker" via an indexed key) stays fast at any depth and stable under inserts/deletes, at the cost of arbitrary-page jumps and easy totals. Large-scale, fast-changing, infinite-scroll APIs (feeds, timelines) overwhelmingly use cursor pagination — with a unique tiebreaker in the sort key to keep cursors unambiguous.


Sub-Topics

# Sub-Topic What You'll Learn
1 REST Resource-oriented API design on top of HTTP
2 GraphQL Client-driven queries that solve over/under-fetching
3 gRPC High-performance, contract-first RPC for internal services
4 API Versioning & Idempotency Evolving APIs safely and enabling safe retries
5 Authentication & Authorization Verifying identity and enforcing permissions on APIs
6 Rate Limiting & Throttling Protecting APIs from abuse and enforcing fair usage
7 Pagination Returning large datasets efficiently and correctly