Component Design
What is High-Level Design?
High-Level Design (HLD) defines the system architecture at a macro level. It identifies the major components, their responsibilities, and how they interact. For an SDE-1 interview, your job is to break down a vague problem into concrete components and explain the tradeoffs.
The Building Blocks
Every distributed system is composed of these fundamental layers:
| Layer | Components | Responsibility |
|---|---|---|
| Client | Web browser, Mobile app, Desktop app | User interface, input validation, rendering |
| Edge | CDN, Load Balancer, Reverse Proxy | Caching, traffic distribution, SSL termination |
| Gateway | API Gateway, Auth Service | Request routing, authentication, rate limiting |
| Services | Business logic, Microservices, Workers | Core domain logic, processing |
| Data | SQL DB, NoSQL DB, Cache, Search Index | Persistent storage, fast reads |
Typical Web Application Architecture
┌─────────────────────────────────────────────────────────────────┐
│ CLIENTS │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Web App │ │Mobile App│ │ 3rd Party│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
└────────┼──────────────┼──────────────┼──────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ CDN / EDGE │
│ ┌──────────┐ ┌──────────┐ │
│ │ CDN │ │ WAF │ (CloudFront, Cloudflare) │
│ └────┬─────┘ └────┬─────┘ │
└────────┼──────────────┼─────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ LOAD BALANCER │
│ ┌──────────────────────────┐ │
│ │ ALB / NLB / HAProxy │ (Health checks, SSL, routing) │
│ └────────────┬─────────────┘ │
└────────────────┼────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ API GATEWAY │
│ ┌──────────────────────────┐ │
│ │ Kong / AWS API Gateway │ (Auth, Rate Limit, Routing) │
│ └────────────┬─────────────┘ │
└────────────────┼────────────────────────────────────────────────┘
│
┌────────┼────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Service A │ │ Service B │ │ Service C │
│ (Users) │ │ (Orders) │ │ (Payments)│
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ DATA LAYER │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ MySQL │ │ Redis │ │ S3 │ │ Kafka │ │
│ │ (SQL) │ │ (Cache)│ │ (Blob) │ │ (Queue)│ │
│ └────────┘ └────────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────────────────────────────┘
Component Deep Dive
1. Client Layer
- Renders UI, collects user input
- Performs client-side validation (reduces bad requests)
- Calls backend APIs via HTTP/HTTPS
- Example: React SPA, iOS/Android native app
2. CDN (Content Delivery Network)
- Caches static assets (images, CSS, JS) at edge locations
- Reduces latency by serving content from the nearest node
- Offloads ~80% of traffic from origin servers
- Examples: CloudFront, Cloudflare, Akamai
3. Load Balancer
- Distributes incoming requests across multiple backend servers
- Performs health checks and removes unhealthy instances
- Algorithms: Round Robin, Least Connections, IP Hash
- Layer 4 (TCP) vs Layer 7 (HTTP) load balancing
4. API Gateway
- Single entry point for all client requests
- Handles authentication (JWT validation), rate limiting, request routing
- Can aggregate responses from multiple microservices
- Translates public API to internal service calls
5. Service Layer
- Contains the business logic
- Can be monolithic (single deployable) or microservices (independent)
- Each service owns its data store (database per service pattern)
- Communicates via sync (REST/gRPC) or async (queues) patterns
6. Data Layer
- Relational DB (MySQL, PostgreSQL): Structured data, ACID transactions
- NoSQL (DynamoDB, MongoDB): Flexible schema, horizontal scaling
- Cache (Redis, Memcached): Sub-millisecond reads, session storage
- Message Queue (Kafka, SQS): Async communication, event streaming
Monolithic vs Microservices
| Aspect | Monolith | Microservices |
|---|---|---|
| Deployment | Single unit, simple to deploy | Independent services, complex orchestration |
| Scaling | Scale entire app | Scale individual services |
| Development | Faster for small teams | Better for large teams, parallel work |
| Tech Stack | One language/framework | Polyglot (each service chooses) |
| Debugging | Easier (single codebase) | Harder (distributed tracing needed) |
| Fault Isolation | One bug can crash everything | Failure contained to single service |
| Latency | In-process calls (fast) | Network calls (slower, needs gRPC/async) |
| When to Use | MVP, small team, <10 services | Large scale, many teams, complex domain |
Rule of thumb: Start with a monolith. Extract microservices when you have a clear boundary and the team is large enough to justify the operational overhead.
Real Example: E-Commerce Architecture
Customer → Web/App → CDN (product images)
→ Load Balancer
→ API Gateway (auth, rate limit)
→ Product Service → PostgreSQL (product catalog)
→ Cart Service → Redis (session/cart state)
→ Order Service → PostgreSQL (orders)
→ Payment Service → Payment Gateway (Stripe)
→ Notification Service → SQS → Email/SMS Worker
Each service has its own database. The Order Service publishes an OrderCreated event to Kafka. The Notification Service consumes it and sends a confirmation email. This is the essence of event-driven microservices.
Data Flow Patterns
Overview
Data flow describes how information moves through your system. Choosing the right pattern affects latency, reliability, and complexity.
Pattern 1: Request-Response (Synchronous)
The client sends a request and waits for a response. This is the most common pattern.
Client ──HTTP Request──▶ Service ──Query──▶ Database
Client ◀──HTTP Response── Service ◀──Result── Database
Characteristics:
- Simple, easy to reason about
- Client blocks until response arrives
- Tight coupling: if Service B is down, Service A fails
- Best for: CRUD operations, user interactions that need immediate feedback
Example: User loads their profile page. Browser sends GET /users/123, waits for JSON response.
Pattern 2: Event-Driven (Asynchronous)
Services produce events without knowing who consumes them. Consumers process events at their own pace.
Service A ──Publish Event──▶ Message Broker ──Deliver──▶ Service B
──Deliver──▶ Service C
Characteristics:
- Decoupled: producer and consumer don't know about each other
- Resilient: if consumer is down, messages queue up
- Eventually consistent: data may be stale for a short time
- Best for: notifications, analytics, audit logs, cross-service workflows
Example: User places an order. Order Service publishes OrderCreated event. Payment Service, Inventory Service, and Notification Service each consume it independently.
Pattern 3: Publish-Subscribe (Pub-Sub)
A specific form of event-driven where multiple subscribers receive the same event.
Publisher ──Topic: order.created──▶ Message Broker
│ │
│ ├──▶ Subscriber: Payment Service
│ ├──▶ Subscriber: Inventory Service
│ └──▶ Subscriber: Analytics Service
Characteristics:
- One event, many consumers (fan-out)
- Each subscriber processes the same event independently
- Subscribers can be added without modifying the publisher
- Best for: when multiple systems need the same data change
Pattern 4: CQRS (Command Query Responsibility Segregation)
Separate the write model (commands) from the read model (queries).
Write Path: Client ──▶ Command Handler ──▶ Write DB ──▶ Event ──▶ Read DB
Read Path: Client ──▶ Query Handler ──▶ Read DB (optimized for reads)
Characteristics:
- Write and read databases can be different (normalization vs denormalization)
- Read model is pre-computed for fast queries
- Adds complexity but dramatically improves read performance
- Best for: systems with very different read/write patterns (e.g., social media feeds)
Pattern Comparison Table
| Pattern | Latency | Consistency | Complexity | Best For |
|---|---|---|---|---|
| Request-Response | Low (direct) | Strong | Low | CRUD, user-facing queries |
| Event-Driven | Medium (queue) | Eventual | Medium | Workflows, cross-service sync |
| Pub-Sub | Medium (queue) | Eventual | Medium | Fan-out, notifications |
| CQRS | Low reads | Eventual | High | Read-heavy systems, analytics |
Choosing the Right Pattern
- Need immediate response? → Request-Response (REST/gRPC)
- Multiple services need the same data? → Pub-Sub
- Long-running or background work? → Event-Driven with queue
- Read-heavy with different read/write patterns? → CQRS
- Don't know all consumers upfront? → Event-Driven (add consumers later)
Real-World Example: Order Processing Pipeline
1. Client ──POST /orders──▶ Order Service (Request-Response)
→ Returns 201 Created with order ID immediately
2. Order Service ──Publish OrderCreated──▶ Kafka (Event-Driven)
3. Kafka delivers to:
├── Payment Service (charges card)
├── Inventory Service (reserves stock)
├── Notification Service (sends email)
└── Analytics Service (records metrics)
4. Each service updates its own database independently
5. Client polls GET /orders/{id} (Request-Response) to check status
This hybrid approach gives you the best of both worlds: fast user experience (immediate response) with reliable background processing (event-driven).
Communication Patterns
Synchronous vs Asynchronous Communication
| Aspect | Synchronous | Asynchronous |
|---|---|---|
| Flow | Sender waits for response | Sender sends and continues |
| Coupling | Tight (both must be up) | Loose (queue absorbs failures) |
| Latency | Bounded (known timeout) | Unbounded (depends on processing) |
| Use Case | User-facing queries | Background processing, events |
| Protocols | REST, gRPC, GraphQL | Kafka, SQS, RabbitMQ, Redis Streams |
REST (Representational State Transfer)
The standard for HTTP APIs. Uses verbs (GET, POST, PUT, DELETE) on resources.
GET /users/123 → Fetch user
POST /users → Create user
PUT /users/123 → Update user
DELETE /users/123 → Delete user
Pros:
- Universal: every language and framework supports it
- Human-readable (JSON)
- Stateless: each request contains all needed info
- HTTP/2 support for multiplexing
Cons:
- Text-based (larger payloads)
- No built-in streaming
- Contract is implicit (docs needed)
When to use: Public APIs, simple CRUD, when you need broad compatibility.
gRPC (Google Remote Procedure Call)
Binary protocol using Protocol Buffers. Enables efficient service-to-service communication.
// user.proto
syntax = "proto3";
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (stream User); // Server streaming
}
message GetUserRequest {
string user_id = 1;
}
message User {
string id = 1;
string name = 2;
string email = 3;
}
# Server
import grpc
from user_pb2 import GetUserRequest, User
from user_pb2_grpc import UserServiceServicer
class UserServicer(UserServiceServicer):
def GetUser(self, request, context):
return User(id=request.user_id, name="Alice", email="alice@example.com")
Pros:
- 10x smaller payloads than JSON (binary)
- Strongly typed with code generation
- Supports streaming (server, client, bidirectional)
- Built-in deadline propagation and cancellation
Cons:
- Not human-readable
- Requires protobuf tooling
- Browser support limited (needs gRPC-Web proxy)
When to use: Internal microservices, high-throughput communication, streaming data.
Message Queues (SQS, RabbitMQ)
Point-to-point communication. One producer, one consumer per message.
Producer ──▶ Queue ──▶ Consumer
(FIFO)
Characteristics:
- At-least-once delivery: message retried if consumer fails
- Dead letter queue: failed messages go here for debugging
- Visibility timeout: message hidden while being processed
- Batching: send/receive multiple messages at once
# AWS SQS Example
import boto3
sqs = boto3.client('sqs')
# Send
sqs.send_message(
QueueUrl='https://sqs.us-east-1.amazonaws.com/123456/orders',
MessageBody='{"orderId": "123", "amount": 99.99}'
)
# Receive
response = sqs.receive_message(
QueueUrl='https://sqs.us-east-1.amazonaws.com/123456/orders',
MaxNumberOfMessages=10,
WaitTimeSeconds=20 # Long polling
)
for msg in response['Messages']:
process(msg)
sqs.delete_message(QueueUrl=..., ReceiptHandle=msg['ReceiptHandle'])
When to use: Task queues, order processing, email sending, any background job.
Event Streaming (Kafka)
Distributed commit log. Messages are retained for a configurable time. Multiple consumers can read the same stream.
Producer ──▶ Topic: orders ──▶ Partition 0 ──▶ Consumer Group A (Payment)
──▶ Partition 1 ──▶ Consumer Group A (Payment)
──▶ Partition 2 ──▶ Consumer Group B (Analytics)
Key Concepts:
- Topic: logical channel (e.g.,
order-events) - Partition: parallel unit within a topic
- Consumer Group: set of consumers that share partitions
- Offset: position in the partition (tracks what's been read)
- Retention: messages kept for N days regardless of consumption
Pros:
- Replay: reprocess events from any point in time
- High throughput: millions of messages per second
- Durable: messages persisted to disk
- Multiple consumer groups read independently
Cons:
- Operational complexity (Kafka cluster management)
- Ordering guarantees only within a partition
- Eventual consistency
When to use: Event sourcing, real-time analytics, log aggregation, audit trails.
When to Use Each Pattern
| Scenario | Recommended Pattern |
|---|---|
| User fetches their profile | REST (request-response) |
| Service-to-service internal call | gRPC (fast, typed) |
| Process background jobs | SQS (message queue) |
| Multiple services react to same event | Kafka (event streaming) |
| Real-time notifications | WebSocket + Redis Pub/Sub |
| Batch data processing | Kafka → Spark/Flink consumer |
| Third-party integration | REST (widely supported) |
Hybrid Example: Ride-Sharing App
Rider App ──REST──▶ API Gateway ──gRPC──▶ Trip Service (state machine)
──gRPC──▶ Driver Service (matching)
──gRPC──▶ Pricing Service (surge calc)
Trip Service ──Kafka──▶ Driver Service (location updates)
──Kafka──▶ Notification Service (SMS push)
──Kafka──▶ Analytics Service (metrics)
Driver App ──WebSocket──▶ Real-time location stream ──▶ Kafka ──▶ Trip Service
- REST: Client-facing APIs (simple, compatible)
- gRPC: Internal service calls (fast, typed)
- Kafka: Event streaming (location updates, trip events)
- WebSocket: Real-time bidirectional communication
Practice Problems
Design a scalable High-Level Architecture system. Cover high-level architecture, data model, and API design.
Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliabilityHow would you scale High-Level Architecture to handle 10x the current load? Identify bottlenecks and solutions.
Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decompositionAnalyze potential failure modes for High-Level Architecture and design mitigation strategies.
Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradationQuiz
1. Which component sits between the client and backend services to handle authentication, rate limiting, and request routing?
2. When should you choose gRPC over REST for internal service communication?
3. An Order Service needs to notify Payment, Inventory, and Notification services when an order is placed. Which pattern best fits this scenario?
4. What is the key difference between a message queue (SQS) and event streaming (Kafka)?
5. In the e-commerce architecture example, why does each microservice have its own database?
6. What is the purpose of a CDN in a web application architecture?
Flashcards
Question
What are the 5 fundamental layers of a web application architecture?
Click to reveal answer
Answer
Client (UI), Edge (CDN/Load Balancer), Gateway (API Gateway/Auth), Services (Business Logic), Data (Databases/Cache/Queues)
Question
Monolith vs Microservices: When to choose which?
Click to reveal answer
Answer
Monolith: MVP, small team, <10 services. Microservices: large scale, many teams, complex domain, need independent scaling.
Question
REST vs gRPC: Key differences?
Click to reveal answer
Answer
REST: text/JSON, human-readable, browser-friendly, universal. gRPC: binary/protobuf, 10x smaller payloads, strongly typed, streaming support, best for internal services.
Question
When to use synchronous vs asynchronous communication?
Click to reveal answer
Answer
Sync: user needs immediate response (CRUD, queries). Async: background processing, cross-service workflows, when you can tolerate eventual consistency.
Question
What is the database-per-service pattern?
Click to reveal answer
Answer
Each microservice owns its own database. Services cannot directly access each other's data. Prevents tight coupling, enables independent evolution and scaling. Tradeoff: eventual consistency.
Question
Kafka vs SQS: When to use which?
Click to reveal answer
Answer
Kafka: event streaming, replay, multiple consumer groups, high throughput, audit trails. SQS: task queues, simple background jobs, at-least-once delivery, dead letter queues.
Revision Notes
Key Takeaways
- 1.Start with a monolith. Extract microservices only when you have clear boundaries and team size justifies it.
- 2.Use REST for public APIs and simple CRUD. Use gRPC for internal high-performance service calls.
- 3.Prefer async (event-driven) for cross-service communication to decouple services and improve resilience.
- 4.Kafka for event streaming and replay. SQS for simple task queues and background jobs.
- 5.Every architecture decision is a tradeoff. In an interview, explain the tradeoffs, not just the choice.
- 6.The database-per-service pattern is key to microservices: each service owns its data.
- 7.CDNs offload ~80% of traffic. Always consider caching at the edge.
Interview Tips
- •Always start by clarifying requirements: scale, latency, consistency needs, and team size.
- •Draw the architecture diagram first. Identify the 5 layers and populate with specific technologies.
- •Explain tradeoffs for every decision. Interviewers want to see your reasoning, not just the answer.
- •When asked about communication patterns, mention both sync and async options and explain when each is appropriate.
- •For microservices questions, always mention database-per-service and eventual consistency.
- •Mention scalability: how does your design handle 10x traffic? Think horizontal scaling, caching, and queueing.
- •Don't over-engineer. A simple design that works is better than a complex one that doesn't.
Cheat Sheet
High-Level Architecture Cheat Sheet
5 Layers: Client → Edge (CDN/LB) → Gateway (API Gateway) → Services → Data
Monolith: Single deployable, simple, good for small teams.
Microservices: Independent services, database-per-service, complex but scalable.
Communication Patterns:
- REST: HTTP/JSON, universal, request-response, good for public APIs
- gRPC: Binary/protobuf, 10x smaller, streaming, good for internal services
- SQS: Message queue, at-least-once, dead letter queue, good for task processing
- Kafka: Event streaming, replay, multiple consumer groups, good for audit/analytics
Data Flow:
- Request-Response: Client waits for answer (CRUD)
- Event-Driven: Publish event, consumers react independently
- Pub-Sub: One event, many subscribers (fan-out)
- CQRS: Separate read/write models for optimized queries
Key Design Decisions:
- CDN for static assets (images, CSS, JS)
- Load Balancer for traffic distribution and health checks
- API Gateway for auth, rate limiting, routing
- Database-per-service for decoupled microservices
- Async for background work, sync for user-facing queries