System Design Approach¶
What this actually tests¶
System design isn't "memorize the architecture of Twitter" — it's a structured way to go from an ambiguous problem statement to a justified architecture, using the building blocks already covered elsewhere in this handbook (caching, load balancing, sharding, replication, message queues). The skill being exercised is turning ambiguity into concrete decisions, in a fixed order, so nothing critical gets skipped and every decision has a stated reason.
Step 1: Clarify requirements¶
Two categories, and skipping either produces a design that solves the wrong problem:
- Functional requirements — what the system must do. For a URL shortener: shorten a URL, redirect a short URL to the original. Sounds obvious, but "does it need custom aliases?", "do links expire?", "is analytics on click counts required?" all change the design, and none of them are in the one-sentence problem statement.
- Non-functional requirements — the qualities the system must have: how many users, what latency is acceptable, does it need to survive a data center outage, is strong consistency required or is eventual consistency fine. These numbers directly drive every later decision — see Capacity Estimation.
Skipping this step and jumping straight to "I'll use Kafka and Cassandra" is the most common failure mode — those might be completely wrong tools for a system that turns out to need 100 requests/second, not 100,000.
Step 2: Capacity estimation¶
Back-of-envelope math: given the non-functional requirements (users, growth rate), derive concrete numbers — queries per second (read and write separately, since they're usually very different), storage needed over time, bandwidth. See Capacity Estimation for the actual arithmetic and the reference numbers to memorize. This step is what turns "it needs to scale" into an actual number that tells you whether one server is enough or a distributed system is required at all.
Step 3: High-level design¶
A generic starting skeleton — client, load balancer, stateless API servers, a cache, a database, and a queue for anything that doesn't need to happen synchronously. Not every system needs every box (a queue is only relevant if some work can be deferred), but starting from this skeleton and removing/justifying what's unnecessary is more reliable than trying to invent the right shape from scratch under time pressure. Every box here maps to a knowledge area already covered: load balancing, caching, sharding and replication.
Step 4: Deep dive¶
Pick the 1-2 parts of the design that are actually interesting or contentious, and go deep — the specific algorithm for generating unique IDs, the exact caching strategy, how the database schema handles the access pattern. This is where the real differentiation happens; a high-level design with five boxes looks similar across most candidates, but the reasoning behind one specific hard decision reveals whether the design was actually thought through. See URL Shortener for what a deep dive looks like end to end.
Step 5: Identify bottlenecks and trade-offs¶
Every design has a weakest point — state it explicitly rather than waiting to be asked. "The single database is now the bottleneck once writes exceed X — at that point we'd shard by Y" is a stronger statement than a design that silently assumes infinite database capacity. This is also where CAP theorem-style trade-offs get applied concretely: this specific system, does it need strong consistency or is eventual consistency acceptable, and what does that choice cost or save.
Common pitfall¶
Treating this as a strictly linear checklist to complete once, top to bottom, rather than a loop — a detail discovered during the deep dive (step 4) often changes an earlier decision (e.g. the chosen database turns out not to support the access pattern well once examined closely). Revising an earlier step when new information surfaces is expected and looks more competent than defending an early decision that a later step has clearly undermined.