Scaling a System from 10× to 100×

A system design interview guide to a repeatable method for growing a system by orders of magnitude — measure, find what breaks first, fix it with the cheapest change that works, and repeat — without rewriting everything at once.

“How would you scale this to 10×? To 100×?” is less a question about any one technique than about whether you have a method. The wrong answer is a shopping list — “add caching, sharding, and a queue” — recited without knowing which bottleneck each one relieves. The right answer is a loop: measure the system, find the single resource that saturates first, apply the least invasive fix that removes it, and repeat, because scaling never fails everywhere at once. It fails at one place — the database’s write capacity, a connection pool, a single hot key — and that place moves each time you grow. This guide lays out the loop and then walks the ladder of fixes in the order you actually reach for them, noting what tends to break first at each order of magnitude.

Contents

  1. The Method & Scope
  2. Back-of-the-Envelope
  3. The Scaling Ladder
  4. A Scaled Architecture
  5. Caching Layers
  6. Replicas, Sharding & Async
  7. Connection Limits & Backpressure
  8. Bottlenecks & Tradeoffs
  9. Summary

1. The Method & Scope

Scaling is an iterative process, not a one-shot design. The method is the answer; the techniques are just its steps.

  1. Measure. Instrument first. You cannot scale what you cannot see — find the resource that saturates (CPU, memory, disk I/O, network, database, locks) using latency percentiles and utilization, not guesses.
  2. Find the bottleneck. At any load exactly one thing is the limit. Everything else has headroom. Fixing anything but the bottleneck buys nothing.
  3. Apply the cheapest fix that works. Prefer the least invasive change — a bigger box, a cache, a read replica — over the most invasive — sharding, a rewrite. YAGNI: do not shard for a load you do not have.
  4. Repeat. The fix moves the bottleneck somewhere else. Re-measure and go again.

Scope. The goal is to grow throughput by 10× then 100× while holding latency and error rate within SLO and keeping cost sane. Correctness and the multi-region story are assumed handled elsewhere; here it is about capacity.

2. Back-of-the-Envelope

Numbers tell you which order of magnitude breaks which resource, so you can predict the next bottleneck instead of discovering it in production.

ScaleLoad (example)What tends to break first
~1K req/s, one app box + one DBFits on a single machine; nothing yet.
10×~10K req/sApp CPU and DB connections; single box runs out.
100×~100K req/sDB write throughput and hot rows; one primary can’t keep up.
Read/write mixoften 90% reads / 10% writesReads → caches + replicas; writes → sharding.
Cache hit rate90% hitsCuts DB read load ~10× — the biggest single lever.
Connection ceilingDB caps ~a few hundred connectionsThousands of app threads → pool exhaustion.

The pattern is consistent: reads scale out cheaply with caching and replicas, but writes are the wall. Somewhere around 100× a single write primary cannot keep up, and that is when the expensive fix — sharding — finally earns its cost.

3. The Scaling Ladder

The fixes come in a natural order, cheapest and least invasive first. You climb only as far as the load forces you — most systems never reach the top rungs.

Scaling ladder: measure, vertical scale, go stateless, horizontal plus load balancer, caching, read replicas, shard writes, async and queues
The ladder, cheapest fix first: measure, scale vertically, make the app stateless, scale horizontally behind a load balancer, add caching, add read replicas, shard the writes, and push slow work to async queues. Each rung moves the bottleneck to the next.

Everything below the app tier — caching, replicas, sharding, async — addresses the data layer, which is where scaling gets genuinely hard, and which the rest of this guide covers.

4. A Scaled Architecture

Assembled, the rungs form a familiar layered architecture. Each layer absorbs load so the layer below it sees less.

Layered architecture: clients, CDN, load balancer, stateless app tier, cache, queue, DB primary and read replica
Clients hit a CDN for static and edge-cacheable content; dynamic requests pass a load balancer to a horizontally scaled, stateless app tier; the app tier leans on a distributed cache, a queue for async work, and a database split into a write primary and read replicas (sharded when writes outgrow one node).

5. Caching Layers

Caching is the highest-leverage move because it removes work rather than adding capacity. It exists at every layer, and the discipline is to serve each request from the cheapest cache that can answer it.

Caching layers: browser, CDN, app cache, distributed cache, DB buffer pool, materialized views
From cheapest to most expensive hit: the browser’s private cache, the CDN edge, an in-process app cache, a shared distributed cache, the database’s own buffer pool, and precomputed materialized views. A request should stop as high up this stack as possible.

A 90% hit rate cuts backend read load roughly tenfold — often the single biggest lever available. But caching adds its own hard problems, and naming them signals maturity:

function read(key):
  v = cache.get(key)
  if v is not None:
    return v                          # fast path: the common case
  with single_flight(key):            # coalesce concurrent misses
    v = cache.get(key)                # re-check after acquiring
    if v is None:
      v = db.read(key)                # only one caller hits the DB
      cache.set(key, v, ttl=jitter()) # jitter avoids synchronized expiry
  return v

6. Replicas, Sharding & Async

When caching is not enough, you scale the data layer itself — reads first, then writes, then everything that does not need to be synchronous.

Interview tip. Reach for sharding last, and say why. The senior move is to exhaust the cheap options — vertical scaling, statelessness, caching, read replicas, async — before splitting the write path, because sharding permanently complicates every query, transaction, and migration. When you do shard, immediately name the shard-key choice and the cross-shard query problem; that is where the real difficulty and the follow-up questions live.

7. Connection Limits & Backpressure

Two failure modes bite specifically as you scale out, and both surprise people because they are not about raw compute.

Connection limits. Databases cap concurrent connections at a few hundred, but a horizontally scaled app tier can have thousands of threads each wanting one. Without a bound, the app exhausts the database’s connections and everything stalls. The fix is a connection pool — often a dedicated pooler — that multiplexes many app threads onto a small, bounded set of database connections. The pool size, not the thread count, is the real concurrency limit against the database.

Backpressure. When a downstream component saturates, an unbounded system keeps accepting work it cannot do, queues grow without limit, latency explodes, and it collapses — often taking callers with it in a cascading failure. A well-behaved system pushes back: bounded queues that reject when full, timeouts so callers do not wait forever, load shedding that drops low-priority work early, and circuit breakers that stop hammering a failing dependency. Failing fast under overload is healthier than failing slowly for everyone.

function handle(req):
  if inflight >= max_inflight:
    return 503                         # shed load early, don't queue forever
  with pool.acquire(timeout=50ms) as conn: # bounded DB concurrency
    return conn.query(req, timeout=200ms)  # deadline, don't wait forever

8. Bottlenecks & Tradeoffs

Scaling is a sequence of trades, and the discipline is to make the cheapest one that removes the current bottleneck — no more.

9. Summary

Scaling from 10× to 100× is a method, not a menu: measure, find the one thing that breaks first, apply the cheapest fix, and repeat. Reads scale out easily; writes are the wall you climb last.

ConcernMechanism
Where do we start?Measure and find the single saturating resource — never optimize blind.
What is the first fix?Vertical scaling to buy time, then statelessness to unlock horizontal scaling.
How do we scale reads?Caching at every layer (biggest lever) plus read replicas for the rest.
How do we scale writes?Shard by a high-cardinality, evenly-accessed key — the expensive fix, used last.
How do we handle slow work?Push it to async queues; absorb spikes and shorten the request path.
Why does scaling out stall?DB connection limits — bound them with a connection pool, not more threads.
How do we survive overload?Backpressure: bounded queues, timeouts, load shedding, circuit breakers — fail fast.
How do we stay affordable?Treat scaling as economics — hit the SLO at the lowest cost, sometimes by shrinking the work.
The recurring theme: scale is a moving bottleneck. Instrument so you can see it, fix the one place that is actually saturated with the least invasive change that works, and defer the expensive moves — sharding, rewrites — until the load genuinely demands them. Method beats menu.