Vertical vs Horizontal
bigger box, or more boxesVertical scaling buys a bigger machine â more CPU, RAM, faster disk. Horizontal scaling adds more machines and spreads load across them. The industry reflex is "scale horizontally," but vertical is underrated early: it's a config change, no distributed-systems complexity, and modern hardware is enormous.
Statelessness
the property that makes horizontal trivialHorizontal scaling is easy only if any server can handle any request. That requires statelessness: the server holds no per-user data between requests, so the load balancer can route freely and you can add or kill nodes at will. The instant a server remembers something a client depends on, you've pinned that client to that server and lost the freedom.
MOVE STATE OFF THE APP SERVER:
session data ââļ Redis / signed JWT (not local memory)
uploaded files ââļ object storage (not local disk)
local cache ââļ treated as best-effort (a miss is fine, never a source of truth)
result: every app server is interchangeable â add/remove nodes freely,
the load balancer routes anywhere
The work is pushing state out: sessions into Redis or a signed JWT (Security), files into object storage (Data Stores), and any local cache treated as a disposable optimization, never the truth. Do that and app servers become cattle â interchangeable, replaceable, scalable by the dozen.
The Scaling Sequence
each step forced by a specific bottleneckSystems scale in a well-worn order. The value isn't memorizing it â it's naming the bottleneck that forces each step, because that's the reasoning an interviewer grades.
1. One server (app + DB together)
â bottleneck: they compete for CPU/memory
2. Split the database onto its own machine
â bottleneck: one app server maxes out
3. Load balancer + N stateless app servers
â bottleneck: repeated reads hammer the DB
4. Add a cache layer (Redis in front of the DB)
â bottleneck: read QPS still exceeds one DB
5. Read replicas (spread reads across copies)
â bottleneck: slow work blocks request threads
6. Move heavy work to async queues + workers
â bottleneck: one DB can't hold the data / write load
7. Shard the database
| Move | Forced by | See |
|---|---|---|
| Separate DB | App and DB contend on one box | â |
| LB + app servers | One app server saturated | Discovery |
| Cache layer | Repeated reads on the DB | Caching |
| Read replicas | Read QPS past one node | Replication |
| Async queues | Slow work blocking threads | Messaging |
| Sharding | Write/storage past one node | Sharding |
Autoscaling
scale-in is the dangerous directionAutoscaling adds and removes instances based on a metric â CPU, request rate, queue depth. Two realities make it trickier than "set a target and forget."
A new instance isn't instant â it boots, pulls code, warms caches and connection pools. If you scale out only once you're saturated, the new capacity arrives after the spike hurt. Keep headroom: run above bare minimum so you can absorb a burst while new nodes warm.
Scaling in too aggressively removes a node just before the next spike, or drops in-flight requests. Scale in slowly and conservatively â it's cheaper to run a little extra than to be caught short. Drain connections before terminating (Real-Time).
Rate Limiting & Throttling
protecting the system from its clientsScaling handles legitimate load; rate limiting handles the rest â abusive clients, runaway scripts, retry storms. It caps how many requests a client can make in a window, enforced at the gateway so bad traffic dies before it reaches your services (Discovery). Four algorithms, differing mainly in how they handle bursts.
| Algorithm | How it works | Burst behavior |
|---|---|---|
| Token bucket | Tokens refill at a steady rate; each request spends one | Allows bursts up to bucket size |
| Leaky bucket | Requests queue and drain at a fixed rate | Smooths bursts into a steady flow |
| Fixed window | Count per clock window (per minute) | Spike at window edges (double at the boundary) |
| Sliding window | Rolling count over the last N seconds | Smooth, no edge spike; more state |
429 with a Retry-After header so well-behaved clients back off (APIs). This is the heart of the Rate Limiter case study (Rate Limiter).Quick Reference
bottleneck â move, and rate-limit algorithms| Bottleneck | Move |
|---|---|
| App + DB contend | Separate the database |
| App server saturated | Load balancer + stateless app servers |
| Repeated reads on DB | Cache layer |
| Read QPS past one node | Read replicas |
| Slow work blocks threads | Async queues + workers |
| Write/storage past one node | Shard |
| Rate limit | Burst | Best for |
|---|---|---|
| Token bucket | Allowed, capped | General default, good UX |
| Leaky bucket | Smoothed away | Steady downstream load |
| Fixed window | Edge spike | Simple, approximate |
| Sliding window | Smooth | Accuracy, at more state cost |