Topic Overview

Monolith vs Microservices

Compare monolithic and microservices architectures. Understand when to choose each, migration strategies, and the trade-offs involved in architectural decisions.

Choosing between monolithic and microservices architecture is a critical decision. Each has trade-offs, and the right choice depends on your specific requirements.


Monolithic Architecture

Monolith is a single, unified application.

Characteristics

  • Single codebase: All code in one repository
  • Single deployment: Deploy entire application
  • Shared database: All modules use same database
  • Tight coupling: Modules depend on each other
  • Single technology: One technology stack

Advantages

  • Simple: Easier to develop and test
  • Performance: No network calls between modules
  • Transactions: Easy ACID transactions
  • Debugging: Easier to debug (single process)
  • Deployment: Simple deployment

Disadvantages

  • Scaling: Must scale entire application
  • Technology lock-in: Hard to change technology
  • Team coordination: Large teams work on same codebase
  • Deployment risk: One bug affects entire system
  • Fault isolation: Failure affects everything

Microservices Architecture

Microservices are small, independent services.

Characteristics

  • Multiple services: Each service is separate
  • Independent deployment: Deploy services separately
  • Database per service: Each service has its own database
  • Loose coupling: Services communicate via APIs
  • Technology diversity: Different technologies per service

Advantages

  • Scalability: Scale services independently
  • Technology diversity: Use best tool for each service
  • Fault isolation: Failure in one service doesn't cascade
  • Team autonomy: Teams work independently
  • Faster deployment: Deploy services separately

Disadvantages

  • Complexity: Distributed system complexity
  • Network overhead: Network calls between services
  • Data consistency: Distributed transactions are hard
  • Testing: More complex testing
  • Deployment: More services to deploy

When to Choose Each

Choose Monolith When:

  • Small team: Few developers
  • Simple application: Not complex
  • Fast development: Need to move quickly
  • Strong consistency: Need ACID transactions
  • Low scale: Not expecting high traffic

Choose Microservices When:

  • Large team: Multiple teams
  • Complex application: Many features
  • Different scaling needs: Services scale differently
  • Technology diversity: Need different technologies
  • High scale: Expecting high traffic

Migration Strategy

Start with Monolith

1. Build monolith
2. Identify boundaries
3. Extract services gradually
4. Migrate to microservices

Benefits:

  • Learn domain first
  • Identify service boundaries
  • Avoid premature optimization

Examples

Monolithic Application

// Single application
class ECommerceApp {
  private userService: UserService;
  private orderService: OrderService;
  private paymentService: PaymentService;
  
  async createOrder(userId: string, items: Item[]): Promise<Order> {
    // All in same process
    const user = await this.userService.getUser(userId);
    const order = await this.orderService.createOrder(userId, items);
    await this.paymentService.processPayment(order.id);
    
    return order;
  }
}

Microservices Application

// Separate services
class OrderService {
  async createOrder(userId: string, items: Item[]): Promise<Order> {
    // Call user service (HTTP/gRPC)
    const user = await this.userServiceClient.getUser(userId);
    
    // Create order
    const order = await this.database.createOrder(userId, items);
    
    // Call payment service
    await this.paymentServiceClient.processPayment(order.id);
    
    return order;
  }
}

Common Pitfalls

  • Premature microservices: Too early, adds complexity. Fix: Start with monolith, extract when needed
  • Over-microservicing: Too many small services. Fix: Start with fewer, larger services
  • Shared database: Services sharing database. Fix: Database per service
  • Synchronous calls everywhere: Creating call chains. Fix: Use async messaging

Interview Questions

Beginner

Q: What is the difference between monolithic and microservices architecture?

A:

Monolithic Architecture:

  • Single application: All code in one codebase
  • Single deployment: Deploy entire application
  • Shared database: All modules use same database
  • Tight coupling: Modules depend on each other

Microservices Architecture:

  • Multiple services: Each service is separate
  • Independent deployment: Deploy services separately
  • Database per service: Each service has its own database
  • Loose coupling: Services communicate via APIs

Key Differences:

FeatureMonolithMicroservices
CodebaseSingleMultiple
DeploymentSingle unitIndependent
DatabaseSharedPer service
ScalingScale entire appScale services independently
ComplexityLowerHigher

When to use:

  • Monolith: Small team, simple app, fast development
  • Microservices: Large team, complex app, different scaling needs

Intermediate

Q: Explain the trade-offs between monolith and microservices. When would you migrate from monolith to microservices?

A:

Monolith Trade-offs:

Advantages:

  • Simple: Easier to develop, test, debug
  • Performance: No network calls between modules
  • Transactions: Easy ACID transactions
  • Deployment: Simple deployment

Disadvantages:

  • Scaling: Must scale entire application
  • Technology lock-in: Hard to change
  • Team coordination: Large teams on same codebase
  • Fault isolation: Failure affects everything

Microservices Trade-offs:

Advantages:

  • Scalability: Scale services independently
  • Technology diversity: Different technologies
  • Fault isolation: Failure doesn't cascade
  • Team autonomy: Teams work independently

Disadvantages:

  • Complexity: Distributed system complexity
  • Network overhead: Network calls between services
  • Data consistency: Distributed transactions hard
  • Testing: More complex

Migration Strategy:

1. Start with monolith
2. Identify service boundaries (domain-driven design)
3. Extract services gradually
4. Migrate to microservices

When to migrate:

  • Team size: Multiple teams need independence
  • Scaling needs: Different services scale differently
  • Technology needs: Need different technologies
  • Complexity: Monolith becoming too complex

Senior

Q: Design a migration strategy from monolith to microservices for a large e-commerce platform. How do you identify service boundaries, handle data migration, and ensure zero downtime?

A:

class MonolithToMicroservicesMigration {
  private monolith: Monolith;
  private services: Map<string, Service>;
  private dataMigrator: DataMigrator;
  
  constructor() {
    this.monolith = new Monolith();
    this.services = new Map();
    this.dataMigrator = new DataMigrator();
  }
  
  // 1. Identify Service Boundaries
  identifyBoundaries(): ServiceBoundary[] {
    // Use domain-driven design
    // Identify bounded contexts
    return [
      { name: 'user', domain: 'User Management' },
      { name: 'order', domain: 'Order Processing' },
      { name: 'payment', domain: 'Payment Processing' },
      { name: 'inventory', domain: 'Inventory Management' }
    ];
  }
  
  // 2. Strangler Fig Pattern
  async migrateService(serviceName: string): Promise<void> {
    // Step 1: Extract service (parallel to monolith)
    const service = await this.extractService(serviceName);
    
    // Step 2: Route traffic gradually
    await this.routeTrafficGradually(serviceName, service);
    
    // Step 3: Migrate data
    await this.migrateData(serviceName);
    
    // Step 4: Decommission monolith code
    await this.decommissionMonolithCode(serviceName);
  }
  
  // 3. Gradual Traffic Routing
  async routeTrafficGradually(serviceName: string, service: Service): Promise<void> {
    // Start with 0% traffic
    let percentage = 0;
    
    while (percentage < 100) {
      // Route percentage to new service
      await this.loadBalancer.setRouting(serviceName, {
        monolith: 100 - percentage,
        microservice: percentage
      });
      
      // Monitor
      await this.monitor(service);
      
      // Increase gradually
      percentage += 10;
      await this.sleep(60000); // 1 minute
    }
  }
  
  // 4. Data Migration
  class DataMigrator {
    async migrateData(serviceName: string): Promise<void> {
      // Dual write: Write to both monolith and service
      await this.enableDualWrite(serviceName);
      
      // Sync existing data
      await this.syncExistingData(serviceName);
      
      // Verify data consistency
      await this.verifyConsistency(serviceName);
      
      // Switch reads to service
      await this.switchReads(serviceName);
      
      // Stop writing to monolith
      await this.disableMonolithWrite(serviceName);
    }
  }
  
  // 5. Zero Downtime
  ensureZeroDowntime(): void {
    // Run services in parallel
    // Gradual traffic migration
    // Health checks
    // Rollback capability
  }
}

Features:

  1. Service boundaries: Domain-driven design
  2. Strangler fig: Gradual migration
  3. Traffic routing: Gradual traffic shift
  4. Data migration: Dual write, sync, verify
  5. Zero downtime: Parallel running, rollback

Key Takeaways

  • Monolith: Single application, simpler, easier to develop
  • Microservices: Multiple services, scalable, more complex
  • Trade-offs: Monolith simpler, microservices more scalable
  • When to choose: Monolith for small/medium, microservices for large/complex
  • Migration: Start with monolith, extract services gradually
  • Best practices: Don't over-engineer, start simple, migrate when needed

About the author

InterviewCrafted helps you master system design with patience. We believe in curiosity-led engineering, reflective writing, and designing systems that make future changes feel calm.