# Horizontal vs. Vertical Scaling

> Bigger machines (vertical) vs. more machines (horizontal). Complexity, cost, failure impact, speed, and limits trade-offs for scaling strategies.

- **Category**: Trade-offs
- **Subcategory**: Cloud Infrastructure
- **Canonical URL**: https://designpattern.fyi/trade_offs/horizontal-vs-vertical-scaling/

---

## Description
**Intent**: Choose between scaling up (vertical - bigger machines) versus scaling out (horizontal - more machines) based on complexity, cost, failure impact, and scaling limits.

**Context**: Vertical scaling (scale up) uses bigger machines — low complexity, superlinear cost (large instances premium), high failure impact (single point), instant speed, but hard ceiling (largest instance type). Horizontal scaling (scale out) uses more machines — high complexity (distribution, coordination), near-linear cost, low failure impact, slower provisioning, but effectively unlimited scaling.

**Solution**: Scale vertically first — simpler and often sufficient. Design for horizontal scaling from the start even if not used immediately — stateless services, externalized sessions, idempotent operations. For LLM serving, vertical scaling often beats horizontal until single-node memory limits. Use Kubernetes resource requests/limits for easy vertical scaling.



## Use Cases
Web application starting with vertical scaling for simplicity. Microservices architecture designed for horizontal scaling from day one. LLM inference using vertical scaling (more GPU memory) before horizontal scaling.



## Implementation Example

```javascript
// Horizontal vs. Vertical Scaling Strategies

class ScalingManager {
  constructor() {
    this.instances = [];
    this.currentInstanceType = 't3.medium';
  }

  // Vertical Scaling: Scale up existing instance
  async scaleVertically(newInstanceType) {
    console.log(`Scaling from ${this.currentInstanceType} to ${newInstanceType}`);

    // Check if new instance type is larger
    if (!this.isLargerInstance(newInstanceType, this.currentInstanceType)) {
      throw new Error('New instance must be larger');
    }

    // Scale up (process varies by cloud provider)
    await this.resizeInstance(newInstanceType);
    this.currentInstanceType = newInstanceType;

    return {
      oldType: this.currentInstanceType,
      newType: newInstanceType,
      action: 'vertical_scale'
    };
  }

  // Horizontal Scaling: Add more instances
  async scaleHorizontally(targetCount) {
    const currentCount = this.instances.length;

    if (targetCount > currentCount) {
      // Scale out: add instances
      const instancesToAdd = targetCount - currentCount;
      for (let i = 0; i < instancesToAdd; i++) {
        const instance = await this.launchInstance(this.currentInstanceType);
        this.instances.push(instance);
      }
    } else if (targetCount < currentCount) {
      // Scale in: remove instances
      const instancesToRemove = currentCount - targetCount;
      for (let i = 0; i < instancesToRemove; i++) {
        const instance = this.instances.pop();
        await this.terminateInstance(instance);
      }
    }

    return {
      oldCount: currentCount,
      newCount: this.instances.length,
      action: 'horizontal_scale'
    };
  }

  // Auto-scaling: Automatic horizontal scaling based on metrics
  async autoScale(metrics) {
    const cpuUtilization = metrics.cpu;
    const memoryUtilization = metrics.memory;

    // Scaling policies
    if (cpuUtilization > 70 || memoryUtilization > 80) {
      // Scale out
      const targetCount = Math.ceil(this.instances.length * 1.5);
      return await this.scaleHorizontally(targetCount);
    } else if (cpuUtilization < 30 && memoryUtilization < 40 && this.instances.length > 1) {
      // Scale in (but keep minimum 1 instance)
      const targetCount = Math.max(1, Math.floor(this.instances.length * 0.7));
      return await this.scaleHorizontally(targetCount);
    }

    return { action: 'no_change' };
  }

  // Hybrid approach: Vertical scaling within instance family, horizontal across instances
  async hybridScale(currentLoad) {
    // First try vertical scaling (simpler)
    const nextInstanceType = this.getNextLargerInstance(this.currentInstanceType);
    if (nextInstanceType && currentLoad < 0.8) {
      return await this.scaleVertically(nextInstanceType);
    }

    // If vertical scaling insufficient or maxed out, scale horizontally
    const additionalInstances = Math.ceil(currentLoad * 2);
    return await this.scaleHorizontally(this.instances.length + additionalInstances);
  }

  isLargerInstance(newType, currentType) {
    const instanceHierarchy = [
      't3.nano', 't3.micro', 't3.small', 't3.medium',
      't3.large', 't3.xlarge', 't3.2xlarge', 'm5.large',
      'm5.xlarge', 'm5.2xlarge', 'm5.4xlarge'
    ];
    return instanceHierarchy.indexOf(newType) > instanceHierarchy.indexOf(currentType);
  }
}

// Kubernetes Resource Management (for easy vertical scaling)
const k8sDeployment = {
  apiVersion: 'apps/v1',
  kind: 'Deployment',
  metadata: { name: 'app-deployment' },
  spec: {
    replicas: 3, // Horizontal scaling
    template: {
      spec: {
        containers: [{
          name: 'app',
          image: 'app:latest',
          resources: {
            requests: {
              cpu: '500m',   // Vertical scaling
              memory: '512Mi'
            },
            limits: {
              cpu: '1000m',  // Vertical scaling limits
              memory: '1Gi'
            }
          }
        }]
      }
    }
  }
};

```



## Trade-offs


### Advantages

- map[Vertical:simple, instant, low complexity]

- map[Vertical:no distributed systems challenges]

- map[Horizontal:near-linear cost scaling]

- map[Horizontal:low failure impact, effectively unlimited]




### Considerations & Drawbacks

- map[Vertical:superlinear cost (premium pricing)]

- map[Vertical:hard ceiling (largest instance type limit)]

- map[Vertical:high failure impact (single point of failure)]

- map[Horizontal:high complexity (coordination, distribution)]







