System Design
Intermediate 13 min read Scaling fundamentals

Autoscaling and capacity planning

In one line

Capacity planning decides how much you need for the expected peak plus headroom; autoscaling adds and removes instances as load changes, but it reacts in minutes, so known spikes must be pre-scaled.

What it is

Capacity planning answers: how many servers, database connections, partitions and how much storage do we need for the next peak? You measure what one instance can handle at an acceptable latency (by load testing), forecast peak demand, and add headroom for failures and surprises.

Autoscaling changes capacity automatically based on a signal: CPU, requests per second per instance, queue depth or consumer lag, or a schedule. In Kubernetes the Horizontal Pod Autoscaler (HPA) scales pods, KEDA feeds it event signals like Kafka lag and can scale to zero, and the Cluster Autoscaler or Karpenter adds nodes when pods cannot be scheduled. On AWS, Auto Scaling groups do the same for EC2 instances.

The key limit is reaction time. The metric must rise, be collected (15 to 60 seconds), the autoscaler must decide, a node may need to boot (1 to 3 minutes), and the app must start and warm caches. Scaling from the alarm to useful capacity often takes 2 to 5 minutes. A traffic spike that triples load in 30 seconds, like a flash sale opening or a wicket in a cricket final, outruns reactive autoscaling. Those events are handled by planning and pre-scaling, with load shedding as the safety net.

Stateless tiers scale easily. Databases, Kafka partitions, connection pools and third-party quotas do not. Good capacity planning finds the component that will hit its limit first and plans for it, rather than only adding app servers.

Why interviewers ask

Interviewers want numbers: 'we need about 40 instances at peak' backed by a per-instance throughput and a headroom rule. They also probe whether you know autoscaling is not instant, which metric to scale on for which workload, and what does not autoscale (the database, partitions, provider limits). India-scale events such as IPL finals, Tatkal booking at 10 AM and Big Billion Days make this a favourite topic at Indian product companies, and cost discipline makes it a favourite everywhere.

How it works

  1. 1Measure per-instance capacity with a load test that mirrors real traffic: find the request rate at which p99 latency crosses your target (say 300 ms) or errors appear. That is the instance's limit; plan to run at 50 to 70% of it.
  2. 2Forecast the peak: from last year's peak and growth, marketing plans and event calendars. Use Little's Law (in-flight requests = arrival rate x latency) to size concurrency, threads and connection pools.
  3. 3Compute instances needed = peak RPS / (per-instance RPS x target utilisation), then add N+1 or zone headroom so losing one availability zone does not overload the rest.
  4. 4Check every downstream limit at that peak: database CPU and connections (instances x pool size must stay below max_connections or go through a pooler like PgBouncer), cache throughput, Kafka partitions (max useful consumers per group = partitions), and third-party rate limits (payment gateway, SMS provider, LLM tokens per minute).
  5. 5Choose the scaling signal. CPU works for CPU-bound services. For I/O-bound services (waiting on databases or LLMs) use requests per second per instance or in-flight requests. For workers, use queue depth or consumer lag (via KEDA).
  6. 6Configure the autoscaler: min and max replicas, target value, and stabilisation windows. Kubernetes HPA computes desired = ceil(current x currentMetric / targetMetric); by default it scales up quickly and waits 5 minutes before scaling down to avoid flapping.
  7. 7Pre-scale for known events with scheduled scaling: raise min replicas an hour before, warm caches, increase database capacity and request higher provider quotas days before.
  8. 8Keep the safety net: rate limits, load shedding and graceful degradation for when demand beats even the plan. Review after each event: forecast vs actual, first component to saturate, cost.

Worked example · Sizing a checkout API for a festive sale

  1. Normal peak: 3,000 checkout requests per second. Marketing expects 4x at the sale's opening minute: 12,000 requests per second.
  2. Load test: one pod (2 vCPU) handles 400 requests per second with p99 under 250 ms; above 500 the latency climbs sharply. Plan at 60% utilisation: 240 requests per second per pod.
  3. Pods needed: 12,000 / 240 = 50 pods. Spread across 3 zones, losing one zone leaves 2/3 capacity, so plan 50 x 1.5 = 75 pods to survive a zone loss at peak.
  4. Little's Law check: 12,000 requests per second x 0.15 s average latency = 1,800 requests in flight across the fleet, about 24 per pod; size thread pools accordingly.
  5. Database check: 75 pods x 20 connections = 1,500 connections, above Postgres's practical comfort zone. Put PgBouncer in transaction mode in front and cap real server connections at about 300.
  6. Payment gateway: confirm the provider's limit for 12,000 calls per second in advance; agree a higher limit or route a share to a second gateway.
  7. Plan: scheduled scale-up to 75 pods 60 minutes before opening, HPA on requests per pod (target 240) with max 120, scale-down disabled for 2 hours after opening, and load shedding of non-critical endpoints (recommendations, reviews) if p99 passes 500 ms.

Picture it

A restaurant on Diwali night does not wait for the queue outside to grow before calling extra cooks; cooks take an hour to arrive. It books staff in advance from last year's numbers, keeps a couple of cooks on call for surprises, and if it still overflows, stops taking orders for the slowest dishes.

Key patterns

Target tracking on the right metric

When: Steady, gradual load changes

How: CPU for CPU-bound work; requests or in-flight requests per instance for I/O-bound APIs; lag or queue depth per worker for consumers.

Scheduled and predictive pre-scaling

When: Known events and daily cycles: sales, match starts, 10 AM booking windows

How: Raise minimum capacity ahead of time; warm caches and connection pools; keep scale-down delayed until the event is over.

Headroom for failure

When: Multi-zone deployments

How: Size so the remaining zones can carry peak when one zone is lost: with 3 zones, run at most about 66% of total capacity at peak.

Scale to zero

When: Bursty background workers, preview environments, internal tools

How: KEDA or serverless with min 0; accept a cold-start delay on the first job.

Real-world use

Live cricket streaming (JioHotstar)

Hotstar engineers have spoken publicly about pre-provisioning capacity ahead of big matches based on expected concurrency, because viewer counts can jump by millions within minutes and reactive autoscaling is too slow for that.

Kubernetes HPA, KEDA and Karpenter

HPA scales pods on CPU or custom metrics, KEDA adds event sources such as Kafka lag and SQS depth with scale-to-zero, and Karpenter provisions right-sized nodes (including spot) when pods are pending.

AWS Auto Scaling groups

Offer target tracking, step scaling, scheduled scaling, predictive scaling based on daily and weekly patterns, and warm pools of pre-initialised instances to cut scale-out time.

Netflix

Netflix has written about predictive autoscaling (Scryer) that forecasts traffic from daily patterns and scales ahead of demand, with reactive scaling as a backup.

IRCTC Tatkal bookings

Tatkal windows open at fixed times (10 AM for AC classes, 11 AM for non-AC), so the spike is known in advance. The right answer for such systems is planned capacity, queueing and admission control, not hoping an autoscaler catches up.

In a project

You own an AI document summariser. Workers read jobs from Kafka and call an LLM; each job takes 5 to 20 seconds. CPU-based HPA keeps the workers at 3 pods even when 50,000 jobs are waiting, because the pods mostly wait on network and their CPU stays at 10%.

  1. Switch the signal to consumer lag with KEDA: a Kafka trigger on the jobs topic with a target of about 50 messages of lag per pod.
  2. Raise the topic from 12 to 48 partitions so up to 48 consumers can work in parallel (in a consumer group, extra consumers beyond the partition count sit idle).
  3. Set min 1 and max 48 pods, and a 5-minute cooldown so short dips do not cause flapping.
  4. Check the LLM provider's tokens-per-minute limit: 48 pods x 3 concurrent calls each must stay under it; add a token-bucket limiter in the LLM gateway so scaling up does not just produce 429 errors.
  5. Let Karpenter add spot nodes for the burst, with on-demand fallback, and handle SIGTERM so a reclaimed spot node finishes or releases its jobs.
  6. Dashboard: lag, pods, LLM 429 rate, cost per 1,000 jobs.

Impact: Backlog clears in minutes instead of hours, idle cost at night drops close to zero, and the team discovers the real ceiling is the provider quota, not the worker count, before customers hit it.

Trade-offs

Use it when

  • Traffic varies through the day or week and paying for peak all the time is wasteful
  • Background workers with bursty queues
  • Stateless tiers that start quickly (seconds, not minutes)
  • As a backup to planned capacity for events larger than forecast

Avoid it when

  • Relying on reactive autoscaling alone for spikes that rise faster than instances can start
  • Stateful systems like primary databases or Kafka brokers, where adding a node means data movement; plan these manually
  • Scaling on CPU for I/O-bound services; it will never trigger
  • Scaling app servers past what the database or a third-party quota can take; you just move the failure downstream

Common mistakes

  • Giving no numbers: saying 'we autoscale' without per-instance throughput, peak forecast and headroom.
  • Assuming autoscaling handles a flash-sale opening or a match start within seconds.
  • Scaling only the stateless tier and forgetting database connections, partitions and third-party rate limits.
  • Running at 90% utilisation with no room for a zone failure or a retry storm.
  • Aggressive scale-down that flaps capacity up and down every minute and drops warm caches.
  • Adding more Kafka consumers than partitions and expecting more throughput.

Interview questions

Quick revision

  • Instances = peak RPS / (per-instance RPS x 0.6-0.7), then add zone headroom.
  • Little's Law: in-flight = arrival rate x latency.
  • Spike to new capacity: often 2-5 minutes. Pre-scale known events.
  • Signal: CPU for CPU-bound; RPS or in-flight for I/O-bound; lag for workers.
  • HPA: desired = ceil(current x metric / target); default 5-minute scale-down window.
  • KEDA: event-driven scaling and scale to zero. Karpenter/Cluster Autoscaler: nodes.
  • Kafka: useful consumers <= partitions.
  • Check DB connections, partitions and third-party quotas at peak.
  • Load shedding is the safety net when demand beats the plan.

Check yourself

1. A pod handles 500 requests per second before latency degrades. You plan at 60% utilisation for a 15,000 requests per second peak. How many pods before zone headroom?

2. Workers mostly wait on an LLM API and stay at 10% CPU while a backlog grows. What should the autoscaler use?

3. Why is reactive autoscaling not enough for the first minute of a flash sale?

4. A Kafka topic has 12 partitions. You scale the consumer group from 12 to 30 pods. What happens?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free