APIs & Communication Patterns

Why This Matters

Every system design involves defining how services talk to each other. Choosing the right communication pattern is one of the first trade-offs you'll make.


Synchronous Communication

REST (Representational State Transfer)

The default for most web APIs.

Key Principles:

  • Stateless — each request contains all info needed
  • Resource-oriented — URLs represent resources, not actions
  • Standard HTTP methods — GET, POST, PUT, PATCH, DELETE
  • Uniform interface — predictable, discoverable

Design Best Practices:

GET    /api/v1/users              → List users
GET    /api/v1/users/{id}         → Get user
POST   /api/v1/users              → Create user
PUT    /api/v1/users/{id}         → Replace user
PATCH  /api/v1/users/{id}         → Update user
DELETE /api/v1/users/{id}         → Delete user
GET    /api/v1/users/{id}/orders  → User's orders (nested resource)

Pagination patterns:

  • Offset-based: ?page=3&limit=20 (simple, but skip is expensive)
  • Cursor-based: ?cursor=abc123&limit=20 (efficient, used by FB/Twitter)
  • Keyset-based: ?after_id=12345&limit=20 (good for sorted data)

Pros: Simple, well-understood, cacheable, great tooling Cons: Over-fetching/under-fetching, chatty for complex reads, no streaming

gRPC (Google Remote Procedure Call)

High-performance, strongly-typed service-to-service communication.

  • Uses Protocol Buffers (protobuf) for serialization — binary, compact
  • Built on HTTP/2 — multiplexing, streaming, header compression
  • Code generation — client/server stubs generated from .proto files
  • 4 communication patterns:
    • Unary (request-response)
    • Server streaming (one request, stream of responses)
    • Client streaming (stream of requests, one response)
    • Bidirectional streaming
protobuf
service UserService { rpc GetUser (GetUserRequest) returns (User); rpc ListUsers (ListUsersRequest) returns (stream User); } message User { string id = 1; string name = 2; string email = 3; }

When to use gRPC:

  • Internal microservice communication (high throughput)
  • Low-latency requirements
  • Polyglot environments (auto-generated clients)
  • Streaming data

When NOT to use:

  • Browser clients (limited browser gRPC support, use gRPC-Web)
  • Simple CRUD APIs (REST is simpler)
  • When human readability matters (binary protocol)

GraphQL

Client-driven query language for APIs.

graphql
query { user(id: "123") { name email orders(last: 5) { id total items { name price } } } }

Pros:

  • No over-fetching or under-fetching — client asks for exactly what it needs
  • Single endpoint — reduces number of round trips
  • Strong type system with introspection
  • Great for mobile apps (minimize data transfer)

Cons:

  • Complex server implementation
  • Caching is harder (no URL-based caching)
  • N+1 query problem (use DataLoader)
  • Rate limiting is harder (query complexity varies)
  • Security: malicious deep queries (use query depth/complexity limits)

Used by: GitHub, Shopify, Facebook, Airbnb


Asynchronous Communication

Message Queues (Point-to-Point)

Producer → Queue → Consumer
  • One message → one consumer (competing consumers pattern)
  • Message removed from queue after processing
  • Used for: task distribution, work queues, background jobs
  • Examples: RabbitMQ, Amazon SQS, Redis lists

Pub/Sub (Publish-Subscribe)

Publisher → Topic → Subscriber A
                  → Subscriber B
                  → Subscriber C
  • One message → multiple subscribers
  • Subscribers get independent copies
  • Used for: event broadcasting, notifications, fan-out
  • Examples: Kafka, Google Pub/Sub, SNS, Redis pub/sub

Event Streaming

Producer → Event Log (append-only) → Consumer Group A
                                    → Consumer Group B
  • Events are persisted in an ordered, immutable log
  • Consumers can replay from any point
  • Used for: event sourcing, data pipelines, analytics
  • Examples: Apache Kafka, Amazon Kinesis, Pulsar

Real-Time Communication Patterns

Short Polling

Client: "Any new data?" → Server: "No"
(wait 5 seconds)
Client: "Any new data?" → Server: "No"
(wait 5 seconds)
Client: "Any new data?" → Server: "Yes, here!"
  • Simple but wasteful — lots of empty responses
  • Good for: simple dashboards with low update frequency

Long Polling

Client: "Any new data?" → Server: (holds connection open...)
(30 seconds later)
Server: "Yes, here's new data!"
Client: "Any new data?" → Server: (holds connection open...)
  • More efficient than short polling
  • Server holds request until data is available (or timeout)
  • Good for: near real-time without WebSocket complexity

Server-Sent Events (SSE)

Client → Opens connection → Server pushes events continuously
  • One-way — server to client only
  • Built on HTTP — works through proxies, firewalls
  • Auto-reconnection built into browser API
  • Good for: live feeds, notifications, stock tickers

WebSockets

Client ↔ Server (full duplex, persistent)
  • Bidirectional — both sides send anytime
  • Requires connection management (heartbeats, reconnection)
  • Good for: chat, gaming, collaborative editing

Choosing the Right Pattern

ScenarioPatternWhy
Public APIRESTStandard, cacheable, well-understood
Internal microservicesgRPCPerformance, type safety, streaming
Mobile BFFGraphQLMinimize data transfer, flexible queries
Background processingMessage QueueAsync, retry, backpressure
Event fan-outPub/SubDecouple producers from consumers
Real-time chatWebSocketBidirectional, low latency
Live notificationsSSE or Long PollingServer-to-client, simpler than WS
Data pipelineEvent StreamingReplay, ordering, persistence

API Design Principles for Interviews

Idempotency

  • GET, PUT, DELETE — naturally idempotent
  • POST — use idempotency keys for safe retries
POST /payments
Idempotency-Key: abc-123-def

Versioning

  • URL: /api/v1/users (most common, simple)
  • Header: Accept: application/vnd.api.v1+json (cleaner, harder to use)
  • Query param: /api/users?version=1 (least common)

Rate Limiting Headers

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1625097600
Retry-After: 30

Error Response Format

json
{ "error": { "code": "VALIDATION_ERROR", "message": "Email is required", "details": [ { "field": "email", "issue": "required" } ] } }

Resources


Previous: 02 - Networking Essentials | Next: 04 - Data Storage Fundamentals