LESSON 2.4
The high-level design
Draw the simplest set of boxes and arrows that serves every endpoint, then trace real requests through it.
5 min read
With requirements, entities and an API in hand, you draw the system. The goal of this step is narrow on purpose: a design that works, not one that scales to the moon. Scaling comes in the deep dives.
Start from the API
Take your endpoints one at a time and add only what each one needs.
POST /links needs something to run code (an API server) and somewhere durable to store the link (a database). It also needs a way to produce a unique code.
GET /{code} needs to look up the code and return a redirect. The same server and database can do it.
That is a valid first design. It is honest about being simple, and every endpoint is served.
Trace the requests
Now prove it works by walking a request through, out loud:
- The client sends
POST /linkswith the long URL. - The API server validates the URL, generates a 7-character code (more on how in the deep dives), and inserts
(code, url, created_at)into thelinkstable with a uniqueness constraint oncode. - If the insert conflicts, it retries with a new code; otherwise it returns
201with the short URL. - Later, a browser sends
GET /launch. The server selects the row bycode(a primary-key lookup) and returns302with the long URL, or404.
Tracing exposes gaps quickly. Where do custom aliases get checked? (Same uniqueness constraint.) What happens to expired links? (The read checks expires_at; a cleanup job can delete old rows later.)
Show the data, not just the boxes
For each storage box, note what it holds and how it is accessed. A tiny schema or key layout is enough:
links(
code text primary key, -- lookup by code on every redirect
url text not null,
created_at timestamptz not null,
expires_at timestamptz
)The access pattern (lookup by primary key, 100 reads per write) is what matters most for later decisions. It already tells you this data is easy to cache and easy to shard by code.
Resist adding everything now
It is tempting to draw a load balancer, a cache, a queue and three databases immediately. Hold off. Every box you add should answer "which requirement needs this?". In the high-level design, the honest answer for most of them is "none yet". Adding them in the deep dives, each with its reason, is both clearer and more convincing.
The exceptions are components a requirement already demands. If the brief says "users upload videos", object storage belongs in the first drawing, because no simpler design can serve that endpoint.
Try it
File Storage HLD
A design where one component (object storage) is required from the very first drawing.