Topic Overview

Horizontal vs Vertical Scaling

Understand the difference between horizontal (scale-out) and vertical (scale-up) scaling, their trade-offs, and when to use each approach.

Scaling is the process of increasing system capacity to handle more load. Horizontal scaling adds more servers, while vertical scaling adds more resources to existing servers.


Horizontal Scaling (Scale-Out)

Horizontal scaling adds more servers to handle increased load.

How it works:

Before: 1 Server
After:  Multiple Servers (2, 4, 8, ...)

Example:

1 Server → 2 Servers → 4 Servers → 8 Servers

Characteristics:

  • Add more machines: Increase number of servers
  • Distributed: Load distributed across servers
  • Load balancing: Need load balancer
  • Stateless: Servers should be stateless

Vertical Scaling (Scale-Up)

Vertical scaling adds more resources to existing server.

How it works:

Before: 2 CPU, 4GB RAM
After:  4 CPU, 8GB RAM

Example:

Small server → Medium server → Large server → XL server

Characteristics:

  • Upgrade hardware: More CPU, RAM, disk
  • Single server: Still one server
  • No load balancer: Not needed
  • Stateful OK: Can maintain state

Comparison

FeatureHorizontal ScalingVertical Scaling
MethodAdd more serversUpgrade hardware
CostLower (commodity hardware)Higher (premium hardware)
ScalabilityNearly unlimitedLimited (hardware limits)
DowntimeMinimal (add servers)Requires downtime (upgrade)
Fault toleranceHigh (multiple servers)Low (single point of failure)
ComplexityHigher (load balancing, state)Lower (single server)
Use caseWeb applications, microservicesDatabases, stateful apps

When to Use Each

Use Horizontal Scaling When:

  • High availability needed: Multiple servers provide redundancy
  • Unpredictable load: Easy to add/remove servers
  • Stateless applications: Web servers, APIs
  • Cost-effective: Commodity hardware cheaper
  • Cloud deployment: Easy to add instances

Examples:

  • Web servers
  • API servers
  • Microservices
  • Stateless applications

Use Vertical Scaling When:

  • Stateful applications: Databases, file servers
  • Single-threaded apps: Can't use multiple CPUs
  • Low complexity: Simpler than distributed system
  • Hardware limits not reached: Can still upgrade
  • Legacy systems: Hard to make distributed

Examples:

  • Databases (often)
  • File servers
  • Single-threaded applications
  • Legacy systems

Examples

Horizontal Scaling Architecture

// Load balancer distributes requests
class HorizontalScaling {
  private servers: Server[];
  private loadBalancer: LoadBalancer;
  
  constructor() {
    this.servers = [
      new Server('server1'),
      new Server('server2'),
      new Server('server3')
    ];
    
    this.loadBalancer = new LoadBalancer(this.servers);
  }
  
  async scaleOut(): Promise<void> {
    // Add new server
    const newServer = new Server(`server${this.servers.length + 1}`);
    this.servers.push(newServer);
    this.loadBalancer.addServer(newServer);
  }
  
  async scaleIn(): Promise<void> {
    // Remove server
    const server = this.servers.pop();
    this.loadBalancer.removeServer(server);
  }
  
  async handleRequest(request: Request): Promise<Response> {
    // Load balancer routes to server
    const server = this.loadBalancer.selectServer();
    return await server.handle(request);
  }
}

Vertical Scaling

class VerticalScaling {
  private server: Server;
  
  constructor() {
    this.server = new Server({
      cpu: 2,
      ram: 4, // GB
      disk: 100 // GB
    });
  }
  
  async scaleUp(): Promise<void> {
    // Upgrade server resources
    this.server.upgrade({
      cpu: 4,  // Double CPU
      ram: 8,  // Double RAM
      disk: 200 // Double disk
    });
    
    // Requires downtime for upgrade
    await this.server.restart();
  }
}

Hybrid Approach

class HybridScaling {
  // Vertical scaling for database
  private database: Database; // Scale up
  
  // Horizontal scaling for application servers
  private appServers: Server[]; // Scale out
  
  async scale(): Promise<void> {
    // Scale database vertically
    await this.database.scaleUp();
    
    // Scale application servers horizontally
    await this.scaleOutAppServers();
  }
}

Common Pitfalls

  • Only using one approach: Not considering both. Fix: Use hybrid approach when appropriate
  • Horizontal scaling stateful apps: Difficult. Fix: Make stateless or use vertical scaling
  • Vertical scaling too expensive: Hardware costs. Fix: Consider horizontal scaling
  • Not planning for scaling: System can't scale. Fix: Design for scalability from start

Interview Questions

Beginner

Q: What is the difference between horizontal and vertical scaling?

A:

Horizontal Scaling (Scale-Out):

  • Add more servers: Increase number of machines
  • Distributed: Load distributed across servers
  • Example: 1 server → 2 servers → 4 servers

Vertical Scaling (Scale-Up):

  • Upgrade hardware: Add more resources to existing server
  • Single server: Still one machine
  • Example: 2 CPU → 4 CPU, 4GB RAM → 8GB RAM

Key Differences:

FeatureHorizontalVertical
MethodAdd serversUpgrade hardware
CostLowerHigher
ScalabilityUnlimitedLimited
Fault toleranceHighLow
ComplexityHigherLower

When to use:

  • Horizontal: Web servers, APIs, stateless apps
  • Vertical: Databases, stateful apps, single-threaded

Intermediate

Q: Explain the trade-offs between horizontal and vertical scaling. When would you choose each?

A:

Horizontal Scaling Trade-offs:

Advantages:

  • Nearly unlimited: Can add many servers
  • High availability: Multiple servers provide redundancy
  • Cost-effective: Commodity hardware cheaper
  • No downtime: Add servers without downtime
  • Fault tolerant: Failure of one server doesn't bring down system

Disadvantages:

  • Complexity: Need load balancing, state management
  • Network overhead: Communication between servers
  • Stateless required: Hard to scale stateful apps
  • Data consistency: Harder with distributed system

Vertical Scaling Trade-offs:

Advantages:

  • Simplicity: Single server, no load balancing
  • Stateful OK: Can maintain state easily
  • No network overhead: All on one machine
  • Lower complexity: Easier to manage

Disadvantages:

  • Limited: Hardware limits (can't scale infinitely)
  • Expensive: Premium hardware costs more
  • Downtime: Requires downtime for upgrade
  • Single point of failure: One server fails, system down
  • Diminishing returns: More resources don't always help

When to choose:

  • Horizontal: High availability, unpredictable load, stateless apps
  • Vertical: Stateful apps, databases, simplicity needed

Senior

Q: Design a scaling strategy for a high-traffic web application that needs to handle 10 million requests per day. How do you combine horizontal and vertical scaling, handle state, and ensure high availability?

A:

class ScalableWebApplication {
  private appServers: AppServer[]; // Horizontal scaling
  private database: Database; // Vertical scaling (or sharding)
  private cache: CacheCluster; // Horizontal scaling
  private loadBalancer: LoadBalancer;
  
  constructor() {
    // Application servers: Horizontal scaling
    this.appServers = [
      new AppServer('app1'),
      new AppServer('app2'),
      new AppServer('app3')
    ];
    
    // Database: Vertical scaling (or horizontal with sharding)
    this.database = new Database({
      cpu: 8,
      ram: 32, // GB
      disk: 500 // GB
    });
    
    // Cache: Horizontal scaling
    this.cache = new CacheCluster([
      new CacheNode('cache1'),
      new CacheNode('cache2'),
      new CacheNode('cache3')
    ]);
    
    this.loadBalancer = new LoadBalancer(this.appServers);
  }
  
  // 1. Horizontal Scaling for App Servers
  async scaleAppServers(): Promise<void> {
    // Monitor load
    const avgLoad = await this.getAverageLoad();
    
    if (avgLoad > 0.8) {
      // Scale out: Add servers
      const newServer = await this.createAppServer();
      this.appServers.push(newServer);
      this.loadBalancer.addServer(newServer);
    } else if (avgLoad < 0.3) {
      // Scale in: Remove servers
      const server = this.appServers.pop();
      this.loadBalancer.removeServer(server);
      await this.destroyServer(server);
    }
  }
  
  // 2. Vertical Scaling for Database
  async scaleDatabase(): Promise<void> {
    const dbLoad = await this.database.getLoad();
    
    if (dbLoad > 0.8) {
      // Scale up: Upgrade hardware
      await this.database.upgrade({
        cpu: this.database.cpu * 2,
        ram: this.database.ram * 2
      });
    }
  }
  
  // 3. Hybrid Approach
  async scale(): Promise<void> {
    // Auto-scale based on metrics
    const metrics = await this.getMetrics();
    
    // Scale app servers horizontally
    if (metrics.appServerLoad > 0.8) {
      await this.scaleOutAppServers();
    }
    
    // Scale database vertically (or horizontally with sharding)
    if (metrics.databaseLoad > 0.8) {
      if (this.canScaleDatabaseVertically()) {
        await this.scaleUpDatabase();
      } else {
        // Switch to horizontal scaling (sharding)
        await this.shardDatabase();
      }
    }
    
    // Scale cache horizontally
    if (metrics.cacheLoad > 0.8) {
      await this.scaleOutCache();
    }
  }
  
  // 4. Stateless Application Servers
  class AppServer {
    // Stateless: No local state
    // State in: Database, cache, session store
    
    async handleRequest(request: Request): Promise<Response> {
      // Get session from shared store
      const session = await this.sessionStore.get(request.sessionId);
      
      // Process request
      const result = await this.process(request, session);
      
      // Save session to shared store
      await this.sessionStore.set(request.sessionId, session);
      
      return result;
    }
  }
  
  // 5. Database Sharding (Horizontal for DB)
  async shardDatabase(): Promise<void> {
    // When vertical scaling not enough, shard
    const shards = await this.createShards(4);
    
    // Route queries to appropriate shard
    this.databaseRouter = new DatabaseRouter(shards);
  }
  
  // 6. High Availability
  ensureHighAvailability(): void {
    // Multiple app servers (redundancy)
    this.appServers = [/* multiple servers */];
    
    // Database replication
    this.database = new ReplicatedDatabase({
      primary: this.primaryDB,
      replicas: [this.replica1, this.replica2]
    });
    
    // Multiple load balancers
    this.loadBalancers = [/* primary, secondary */];
  }
}

Architecture:

Load Balancer
  ├─ App Server 1 (stateless)
  ├─ App Server 2 (stateless)
  └─ App Server 3 (stateless)
    Database (vertical scaling or sharding)
    Cache Cluster (horizontal scaling)

Features:

  1. Horizontal scaling: App servers, cache
  2. Vertical scaling: Database (initially)
  3. Hybrid: Combine both approaches
  4. Stateless: App servers stateless for easy scaling
  5. High availability: Multiple servers, replication

Key Takeaways

  • Horizontal scaling: Add more servers (scale-out), distributed, high availability
  • Vertical scaling: Upgrade hardware (scale-up), single server, simpler
  • Horizontal advantages: Unlimited, fault tolerant, cost-effective
  • Vertical advantages: Simpler, stateful OK, no network overhead
  • Hybrid approach: Use both (horizontal for apps, vertical for database)
  • Stateless: Make app servers stateless for easy horizontal scaling
  • Best practices: Design for scalability, use horizontal when possible, combine both approaches

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.