← Back to Design Thinking

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.

Intermediate25 min read

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, notification
  • PaymentProcessed → Triggers shipping, notification
  • OrderShipped → 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

  1. Understand requirements: What are you building?
  2. Identify constraints: Scale, latency, team size
  3. Consider trade-offs: Pros and cons of each pattern
  4. Start simple: Don't over-engineer
  5. 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:

  1. Knows patterns: Understands common patterns and when to use them
  2. Combines patterns: Uses multiple patterns together
  3. Adapts patterns: Modifies patterns to fit context
  4. Starts simple: Doesn't over-engineer
  5. Evolves: Patterns evolve as system grows

Best Practices

  1. Know common patterns: Understand when to use each
  2. Combine patterns: Use multiple patterns together
  3. Adapt to context: Modify patterns to fit your needs
  4. Start simple: Don't over-engineer
  5. Evolve: Patterns can evolve as system grows
  6. 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.