System design template

URL shortener architecture.

The canonical read-heavy system: roughly a hundred reads for every write, and a redirect that has to be fast enough that nobody notices it happened.

Download for macOS
v0.1.33 · .dmg · Apple Silicon & Intel
Signed & notarized by Apple · opens without a Gatekeeper warning
sha256 698955a0187bc039f4c74f5d05a9f10fbb27376a45788a0a241d1326b73873c7
Download for Windows instead
$curl -fsSL https://lucidtrain.com/install.sh | sh
URL shortener architecture. 10 components across 4 tiers.
URL shortener architecture. Rendered by the same ELK layout engine the app runs: the agent emits components, tiers and edges, and the engine places them, so the boxes cannot overlap.

The components

Every row below is read from the graph that produced the diagram above, so the two cannot disagree.

ComponentTierWhy it is there
ClientClientFollows a 301 to the long URL
CDNEdgeCaches hot redirects at the edge
Load balancerEdgeSupporting component
Shorten APIApplicationAllocates a key, writes once
Redirect APIApplicationRead-heavy, ~100:1 vs writes
Key generatorApplicationPre-allocated ranges, no coordination per write
RedisDatakey to URL, the hot path
PostgresDataSource of truth
Click eventsDataAsync, never on the redirect path
WarehouseDataSupporting component

Design decisions worth arguing about

A diagram shows what was chosen. It does not show what it cost, and that is usually the part that matters in a review or an interview.

Counter ranges beat hashing the URL

Hashing the long URL and taking the first seven characters is the obvious approach and it forces you to handle collisions on every single write, which means a read before each insert. Handing each write node a pre-allocated block of counter values instead removes coordination from the hot path entirely: a node burns through its range locally and only talks to the allocator when it runs out. The cost is that keys are no longer a function of the URL, so shortening the same link twice gives two different keys unless you add a lookup, and gaps appear in the key space whenever a node dies holding an unused range.

301 or 302 changes what you can measure

A 301 is permanent, so browsers and intermediaries cache it and subsequent visits never reach your servers. That is excellent for load and fatal for analytics, because your click counts quietly become click-once counts. A 302 keeps every visit coming back to you and keeps the numbers honest, at the price of serving traffic you could have avoided. Most products want 302 for the analytics and then push the caching problem to a CDN they control.

Analytics must not be on the redirect path

Writing a click row synchronously ties your redirect latency to your analytics database, and it means an analytics outage becomes a redirect outage. Publishing to a log and letting a consumer aggregate into the warehouse keeps the two failure domains apart. What you give up is immediacy: the dashboard is now seconds or minutes behind, and you need a story for what happens to events buffered in a process that gets killed.

The cache is the system

With a hundred-to-one read ratio and a strongly skewed key distribution, a small cache absorbs almost everything. That makes the database sizing question much less interesting and the cache invalidation question much more interesting, particularly around deletes and expiry: a removed link that stays in cache is a link you have failed to remove, which matters when the reason for removal was abuse.

How it changes with scale

The write path barely moves as you grow, because shortening is rare. The read path is entirely about cache hit rate and edge distribution: once the CDN is serving the popular keys, your origin sees only the long tail, and the long tail is where the database work is. The first thing that actually hurts is the analytics pipeline, because click volume grows with reads rather than writes.

Where it breaks first

Cache eviction under a traffic spike. A viral link is a single hot key, and if it falls out of cache every request behind it lands on the same database row at once. The symptom is a redirect p99 that jumps from single-digit milliseconds to hundreds while the average looks fine, which is why this is a problem you find in percentiles and not in a mean.

Draw this yourself

Open the Diagram tab and describe the system. The agent emits a semantic graph rather than coordinates, so you can edit the components and the layout re-solves instead of drifting.

shell
$ Diagram a URL shortener: shorten and redirect APIs, key generation, a Redis cache in front of Postgres, and an async click analytics pipeline into a warehouse.

When the shape is right, Implement in code turns the canvas into a markdown specification, every component, every relationship and the notes, and starts a real turn in the Code tab with it.

FAQ

Questions about this design

How long should the short key be?

Seven characters of base62 gives about 3.5 trillion combinations, which is far more than almost any product will ever need. The more common constraint is aesthetic: shorter keys look better and run out faster, so many services start at five or six characters and lengthen as the space fills.

Do you need a relational database for this?

No. The access pattern is a key lookup, so almost any store works, and a key-value store is arguably the better fit. Postgres tends to win anyway because the surrounding features, accounts, ownership, expiry, abuse reports, are relational even though the core lookup is not.

How do you handle abusive or malicious links?

A blocklist check on the write path and an asynchronous rescan afterwards, because a URL that was safe when shortened can be repointed later. The rescan is the part people forget, and it is the reason deletes have to invalidate the cache rather than only the database row.

Is this a good system design interview question?

It is the most common one, which cuts both ways. The architecture is simple enough to draw in a few minutes, so interviews turn on the follow-ups: key generation without coordination, the 301 versus 302 tradeoff, and what happens to a single hot key.

More templates