Design Thinking
High-Level Architecture Patterns
Learn the big patterns you'll repeatedly use in real products and interviews: event-driven architecture, layered architecture, CQRS, microservices, real-time systems, and batch-processing systems.
This helps candidates see the big patterns they will repeatedly use in real products and interviews.
Why Architecture Patterns Matter
Architecture patterns are reusable solutions to common problems:
- Speed: Don't reinvent the wheel
- Best practices: Patterns encapsulate lessons learned
- Communication: Common vocabulary for discussing designs
- Scalability: Patterns designed for scale
Pattern 1: Event-Driven Architecture
What It Is
Event-driven architecture uses events to trigger and communicate between services:
- Services publish events
- Other services subscribe to events
- Loose coupling between services
- Asynchronous processing
Architecture
graph TB
Service1[Service 1] --> EventBus[Event Bus]
Service2[Service 2] --> EventBus
Service3[Service 3] --> EventBus
EventBus --> Service4[Service 4]
EventBus --> Service5[Service 5]
EventBus --> Service6[Service 6]
When to Use
- Real-time updates: Prices, notifications, feeds
- Decoupled services: Services don't need to know about each other
- High volume: Millions of events per second
- Event sourcing: Need to replay events
Example: E-commerce Order System
Events:
OrderCreated→ Triggers payment, inventory, notificationPaymentProcessed→ Triggers shipping, notificationOrderShipped→ Triggers notification, analytics
Benefits:
- Services are decoupled (order service doesn't know about shipping)
- Can add new services (analytics) without changing existing ones
- Handles high volume (millions of orders per day)
Trade-offs
- ✅ Loose coupling
- ✅ Scalability
- ✅ Flexibility
- ❌ Eventual consistency
- ❌ Complex debugging
- ❌ Event ordering challenges
Pattern 2: Layered Architecture
What It Is
Layered architecture organizes code into layers:
- Presentation Layer: UI, API
- Business Layer: Business logic
- Data Layer: Database, cache
- Each layer depends on layers below
Architecture
graph TB
Presentation[Presentation Layer] --> Business[Business Layer]
Business --> Data[Data Layer]
When to Use
- Simple systems: Clear separation of concerns
- Small teams: Easy to understand
- Monoliths: Good for monoliths
- Traditional applications: Web apps, APIs
Example: E-commerce API
Layers:
- Presentation: REST API, request/response handling
- Business: Order processing, payment logic, inventory management
- Data: Database access, caching, file storage
Benefits:
- Clear separation of concerns
- Easy to understand
- Good for small teams
Trade-offs
- ✅ Simple and clear
- ✅ Easy to understand
- ✅ Good for monoliths
- ❌ Can become bloated
- ❌ Hard to scale layers independently
- ❌ Tight coupling between layers
Pattern 3: CQRS (Command Query Responsibility Segregation)
What It Is
CQRS separates reads and writes:
- Command Side: Handles writes (mutations)
- Query Side: Handles reads (queries)
- Different models for reads and writes
- Can use different databases
Architecture
graph TB
Client[Client] --> Command[Command Side]
Client --> Query[Query Side]
Command --> WriteDB[(Write Database)]
Query --> ReadDB[(Read Database)]
WriteDB --> Sync[Sync]
Sync --> ReadDB
When to Use
- Read/write asymmetry: Many more reads than writes
- Different query patterns: Complex queries for reads, simple writes
- Scale reads independently: Need to scale reads separately
- Event sourcing: Want to replay events
Example: Social Media Feed
Command Side:
- Create post (write to database)
- Like post (update database)
- Simple writes
Query Side:
- Get feed (complex query, join multiple tables)
- Get user posts (filter, sort, paginate)
- Complex reads
Benefits:
- Scale reads independently (add read replicas)
- Optimize reads (denormalized data, materialized views)
- Optimize writes (simple, normalized data)
Trade-offs
- ✅ Scale reads/writes independently
- ✅ Optimize each side separately
- ✅ Handle read/write asymmetry
- ❌ Data synchronization complexity
- ❌ Eventual consistency
- ❌ More infrastructure
Pattern 4: Microservices
What It Is
Microservices break application into small, independent services:
- Each service owns a domain
- Independent deployment
- Independent scaling
- Different technologies per service
Architecture
graph TB
Client[Client] --> Gateway[API Gateway]
Gateway --> Service1[User Service]
Gateway --> Service2[Order Service]
Gateway --> Service3[Payment Service]
Gateway --> Service4[Shipping Service]
Service1 --> DB1[(User DB)]
Service2 --> DB2[(Order DB)]
Service3 --> DB3[(Payment DB)]
Service4 --> DB4[(Shipping DB)]
When to Use
- Large teams: Multiple teams work independently
- Different scaling needs: Services scale differently
- Technology diversity: Different stacks per service
- Fault isolation: Service failure doesn't affect others
Example: E-commerce Platform
Services:
- User Service: Authentication, profiles
- Product Service: Catalog, search
- Order Service: Order management
- Payment Service: Payment processing
- Shipping Service: Shipping, tracking
Benefits:
- Teams work independently
- Scale services based on demand
- Use different technologies
- Fault isolation
Trade-offs
- ✅ Independent scaling
- ✅ Technology freedom
- ✅ Team autonomy
- ✅ Fault isolation
- ❌ Increased complexity
- ❌ Network latency
- ❌ Distributed system challenges
- ❌ Operational overhead
Pattern 5: Real-Time Systems
What It Is
Real-time systems process and respond to events immediately:
- WebSocket connections
- Server-sent events
- Message queues
- Low latency (< 100ms)
Architecture
graph TB
Client[Client] --> WS[WebSocket Gateway]
WS --> Queue[Message Queue]
Queue --> Service[Real-Time Service]
Service --> DB[(Database)]
Service --> Cache[Cache]
When to Use
- Chat applications: Real-time messaging
- Collaborative editing: Google Docs, Figma
- Live updates: Stock prices, sports scores
- Gaming: Real-time multiplayer
Example: Chat Application
Components:
- WebSocket Gateway: Handles connections
- Message Queue: Routes messages
- Chat Service: Processes messages
- Presence Service: Tracks online/offline users
Benefits:
- Real-time communication
- Low latency
- Bidirectional communication
Trade-offs
- ✅ Real-time communication
- ✅ Low latency
- ✅ Bidirectional
- ❌ Connection management complexity
- ❌ Scale challenges (millions of connections)
- ❌ State management
Pattern 6: Batch-Processing Systems
What It Is
Batch-processing systems process large volumes of data in batches:
- Scheduled jobs
- ETL pipelines
- Data warehouses
- Analytics processing
Architecture
graph TB
Source[Data Source] --> Queue[Message Queue]
Queue --> Worker1[Worker 1]
Queue --> Worker2[Worker 2]
Queue --> Worker3[Worker 3]
Worker1 --> DB[(Database)]
Worker2 --> DB
Worker3 --> DB
When to Use
- ETL pipelines: Extract, transform, load
- Analytics: Process large datasets
- Reports: Generate daily/weekly reports
- Data migration: Move large amounts of data
Example: Analytics Pipeline
Components:
- Data Source: Application logs, events
- Message Queue: Kafka (buffers events)
- Workers: Process events in parallel
- Data Warehouse: Store processed data
Benefits:
- Process large volumes
- Parallel processing
- Fault tolerant (retry on failure)
Trade-offs
- ✅ Handle large volumes
- ✅ Parallel processing
- ✅ Fault tolerant
- ❌ Not real-time (batch processing)
- ❌ Latency (process in batches)
- ❌ Complex orchestration
How to Choose a Pattern
Decision Framework
- Understand requirements: What are you building?
- Identify constraints: Scale, latency, team size
- Consider trade-offs: Pros and cons of each pattern
- Start simple: Don't over-engineer
- Evolve: Patterns can evolve as system grows
Example: Choosing a Pattern
Problem: Build a social media feed
Requirements:
- Real-time updates
- High scale (millions of users)
- Complex queries (join, filter, sort)
Pattern Choice:
- Event-driven: For real-time updates (new posts trigger feed updates)
- CQRS: For read/write asymmetry (many reads, fewer writes)
- Microservices: For scale (feed service, post service, user service)
Combination: Use multiple patterns together
- Event-driven for updates
- CQRS for reads/writes
- Microservices for scale
Thinking Aloud Like a Senior Engineer
Let me walk you through how I'd actually choose and combine architecture patterns for a real problem. This is the messy reasoning that happens before you settle on a design.
Problem: "Design a social media platform with feeds, messaging, and real-time notifications."
My first instinct: "I'll use microservices! That's what everyone does for complex systems, right?"
But wait—that's pattern-driven thinking. Let me step back and think about what I actually need.
What are the requirements?
- Social feeds (posts from users you follow)
- Messaging (1-on-1 and group chats)
- Real-time notifications
- Scale: 100M users, billions of posts
Let me think about patterns one by one:
For feeds: "I need to show posts from users you follow, sorted by time. This is read-heavy, complex queries."
My first thought: "I'll use CQRS. Separate read and write models. Pre-compute feeds for fast reads."
But is that necessary? "Actually, I could start with a simple database query. JOIN users and posts, sort by time. It works for MVP."
I'm thinking: "Let me start simple, then add CQRS if I need it. This avoids over-engineering."
For messaging: "I need real-time delivery. Users send messages, recipients get them immediately."
My first thought: "I'll use event-driven architecture. Message sent → event published → recipient receives."
Actually, that makes sense: "Messaging is inherently event-driven. A message is an event. Multiple services can react (notifications, analytics, etc.)."
I'm choosing event-driven for messaging: "Because messaging is event-based, and I need loose coupling between services."
For real-time notifications: "I need to push notifications to users in real-time."
My first thought: "I'll use WebSocket. Real-time, bidirectional communication."
But how do I scale WebSockets? "I can't have millions of connections on a single server. I need a WebSocket gateway pattern."
I'm thinking: "Multiple WebSocket servers, message queue to route notifications, presence service to track which server has which user."
Now, should I use microservices? "Let me think about the services:
- Feed service
- Messaging service
- Notification service
- User service"
My first instinct: "Yes! Separate services, independent scaling, team autonomy."
But wait—what's the team size? "If it's a small team (5 engineers), microservices add too much complexity. If it's a large team (50+ engineers), microservices make sense."
Assuming small team: "I'll start with a monolith, split into services later if needed. This is the trade-off: simpler now, potential refactoring later."
But for messaging and notifications, I need real-time: "These are inherently distributed. I can't do real-time in a monolith easily."
I'm thinking: "Maybe a hybrid approach. Monolith for feeds (can start simple), microservices for messaging and notifications (need real-time)."
This is the trade-off I'm making: Use different patterns for different parts. Monolith for simple parts, microservices for complex parts.
Final architecture:
- Feeds: Monolith (start simple), can evolve to CQRS if needed
- Messaging: Event-driven microservice (inherently event-based)
- Notifications: Real-time microservice with WebSocket gateway
- Users: Part of monolith initially, can split later
This is how I combine patterns: I don't use one pattern everywhere. I choose patterns based on requirements, not popularity.
Notice how I didn't jump to "microservices everywhere." I thought about each part separately, chose patterns based on needs, and combined them appropriately.
How a Senior Engineer Uses Patterns
A senior engineer:
- Knows patterns: Understands common patterns and when to use them
- Combines patterns: Uses multiple patterns together
- Adapts patterns: Modifies patterns to fit context
- Starts simple: Doesn't over-engineer
- Evolves: Patterns evolve as system grows
Best Practices
- Know common patterns: Understand when to use each
- Combine patterns: Use multiple patterns together
- Adapt to context: Modify patterns to fit your needs
- Start simple: Don't over-engineer
- Evolve: Patterns can evolve as system grows
- Document: Explain why you chose a pattern
Common Interview Questions
Beginner
Q: What is event-driven architecture?
A: Event-driven architecture uses events to trigger and communicate between services. Services publish events, and other services subscribe to events. This creates loose coupling and enables asynchronous processing.
Intermediate
Q: When would you use CQRS?
A: I'd use CQRS when:
- Read/write asymmetry (many more reads than writes)
- Different query patterns (complex reads, simple writes)
- Need to scale reads independently
- Want to optimize reads and writes separately
Example: Social media feed (many reads, fewer writes, complex queries).
Senior
Q: How would you combine multiple architecture patterns in a single system?
A: I'd combine patterns based on requirements:
Example: E-commerce Platform
- Microservices: Break into services (user, product, order, payment)
- Event-driven: Services communicate via events (order created → payment, shipping)
- CQRS: Separate reads/writes (complex product queries, simple order writes)
- Real-time: WebSocket for order updates
- Batch processing: Daily analytics, reports
Each pattern solves a specific problem, and they work together to create a complete system.
Summary
Architecture patterns are reusable solutions to common problems:
- Event-Driven: Real-time updates, decoupled services
- Layered: Simple systems, clear separation
- CQRS: Read/write asymmetry, independent scaling
- Microservices: Large teams, independent scaling
- Real-Time: Chat, collaborative editing, live updates
- Batch Processing: ETL, analytics, reports
Key takeaways:
- Know common patterns and when to use them
- Combine patterns for complex systems
- Adapt patterns to fit your context
- Start simple, evolve as needed
- Document your pattern choices
FAQs
Q: Do I need to know all patterns?
A: Not all, but know the common ones (event-driven, microservices, CQRS, real-time). Understand when to use each and how they work together.
Q: Can I use multiple patterns together?
A: Yes. Most systems use multiple patterns. For example, microservices with event-driven communication and CQRS for reads/writes.
Q: How do I choose a pattern?
A: Consider:
- Requirements (scale, latency, team size)
- Constraints (budget, timeline, expertise)
- Trade-offs (complexity vs benefits)
- Start simple, evolve as needed
Q: What if a pattern doesn't fit my problem?
A: Adapt it. Patterns are starting points, not rigid rules. Modify to fit your context.
Q: Should I use microservices from day 1?
A: Usually no. Start with a monolith, evolve to microservices when needed. Don't over-engineer.
Q: How do I learn patterns?
A:
- Study real-world systems (Instagram, Netflix, Uber)
- Read architecture books
- Practice designing systems
- Get feedback from peers
Q: Are patterns the same as design patterns?
A: Similar but different. Architecture patterns are high-level (system structure), while design patterns are low-level (code structure). Both are useful.
Keep exploring
Design thinking works best when combined with practice. Explore more topics or apply what you've learned in our system design practice platform.