Design 01 · System Design
Designing a URL Shortener
A worked walkthrough from requirements to trade-offs, where the architecture evolves as the design does.
Cloud-agnostic first, with a possible Azure mapping at the end. The design is a worked example, and all capacity figures are labelled assumptions rather than measurements.
~9 min read
Start by deciding what the system must do, and how well it must do it. For a URL shortener the functional core is small. The non-functional requirements are what shape the architecture.
Functional
- Create a short link for a long URL
- Redirect a short link to its original URL
- Optionally accept a custom alias
- Optionally expire a link
Non-functional
- Redirects are fast and highly available
- The workload is read-heavy
- Short codes are unique
- Mappings are durable
- Creation can tolerate slightly higher latency than redirects
Begin with the simplest structure that could work: a stateless application service behind a load balancer, backed by one database. It is easy to reason about, and it has a clear path to scale.
This baseline cannot yet distinguish read traffic from write traffic, says nothing about how short codes are produced, and sends every redirect to a single database. Each of those gaps drives a later step.
Separating the read path from the write path lets each be scaled and protected independently. Creation is lower volume. Redirect is latency-critical.
| Component | Responsibility | Design note |
|---|---|---|
| Load balancer | Distribute requests, check health | Stateless services allow horizontal scaling |
| Create service | Validate the URL, obtain a key, store the mapping | Write path, lower volume |
| Redirect service | Look up the code and return the redirect | Read path, latency-critical |
| Key generator | Produce unique short codes | Options compared below |
| Database | Durable mapping store | Unique constraint on the code |
Choosing how codes are generated
| Approach | How it works | Strength | Weakness |
|---|---|---|---|
| Hash the URL | Hash and truncate | Same URL gives the same code | Truncation raises collision odds, which must be handled |
| Random code | Generate a random base62 code, insert if unused | Hard to guess | Collision retries and an extra check on write |
| Counter encoded in base62 | Convert an increasing ID to base62 | No collisions, compact | Sequential codes are guessable; needs coordination |
For this worked example, each service instance is allocated a range of IDs and encodes them in base62. That avoids collision checks and coordination on every write. Predictability is the cost, and the security step returns to it.
Access is dominated by one pattern: look up a long URL by its short code. That fits a table keyed by the code, or any key-value store.
short_links short_code VARCHAR(10) PRIMARY KEY long_url TEXT NOT NULL created_at TIMESTAMP NOT NULL expires_at TIMESTAMP NULL
Access patterns
- Read by short code (dominant)
- Insert a new mapping
- Remove or ignore expired mappings
Consistency needs
- Codes must be unique at creation
- Reads can usually tolerate slight staleness
- A just-created link should resolve promptly
A uniqueness constraint on the code also protects custom aliases: a clash surfaces as a conflict rather than silently overwriting an existing link.
The public contract is small: create a link and follow a link. Making creation safely repeatable matters, because clients and networks retry.
POST /api/v1/links
Idempotency-Key: <client-generated key>
{
"longUrl": "https://example.com/some/long/path",
"customAlias": "docs", // optional
"expiresAt": "2030-01-01T00:00:00Z" // optional
}
201 Created
{ "code": "abc1234", "shortUrl": "https://short.example/abc1234" }GET /abc1234 302 Found Location: https://example.com/some/long/path
| Status | Meaning |
|---|---|
| 201 | Link created |
| 302 | Redirect to the stored destination |
| 400 | Invalid URL or request |
| 404 | Unknown code |
| 409 | Custom alias already taken |
| 410 | Link expired |
| 429 | Client is being rate limited |
Capacity: a worked example
| Assumption (input) | Value |
|---|---|
| New links per month | 100 million |
| Read to write ratio | 100 : 1 |
| Peak to average traffic | 5 : 1 |
| Average record size | 500 bytes |
| Retention | 5 years |
| Derived (arithmetic) | Result |
|---|---|
| Average writes per second | about 39 |
| Average redirects per second | about 3,900 |
| Peak redirects per second | about 19,000 |
| Records after 5 years | 6 billion |
| Storage for records | about 3 TB |
| Short codes available at 7 base62 characters | about 3.5 trillion |
The numbers indicate shape rather than precision. Writes are modest, redirects are thousands per second at peak, and storage is measured in terabytes. One database serving every redirect will be the first thing to strain.
Step up: add a cache
Redirect traffic is likely to be concentrated on a small set of popular links. That is an assumption to validate, but it is what makes caching effective.
The redirect service uses a cache-aside pattern: check the cache, and on a miss read the database and populate the cache. Capping each entry's time to live by the link's expiry keeps expired links from being served.
| Gives | Costs |
|---|---|
| Lower latency and far less database load on popular links | Stale results after a link is disabled or changed |
| A buffer in front of the database | Cold-start load after restarts; stampedes on a hot key unless requests are coalesced |
| Cheap horizontal read capacity | Memory cost and another component to operate |
Step up: partition the data
Replicas add read capacity. When data or write volume outgrows a single node, the table is partitioned. Hashing the short code spreads keys evenly and suits key lookups, and consistent hashing limits how much data moves when shards are added.
| Gives | Costs |
|---|---|
| Capacity beyond one node | Queries across shards, such as all links for one owner, need another index or store |
| Even distribution of keys | Resharding is operational work; a very hot key can still overload one shard |
| Independent failure domains | More moving parts to monitor and recover |
Design for each component failing, and decide in advance which capability matters most. Here, redirects matter more than creation, so the design degrades by protecting redirects.
| Failure | Effect | Mitigation |
|---|---|---|
| Application instance fails | Capacity drops | Stateless services, load balancer health checks, instances in multiple zones |
| Cache unavailable | More database load and higher latency | Fall back to the database, cap concurrent lookups, coalesce requests for hot keys |
| Shard or primary unavailable | Creates and uncached redirects affected | Replication and failover; cached redirects continue to be served |
| Key generator unavailable | Creation could fail | Pre-allocated ID ranges let instances keep creating for a time |
| Duplicate create requests | Duplicate links | Idempotency key stored with the outcome |
| Slow dependency | Requests and threads pile up | Timeouts, bounded retries with backoff and jitter, circuit breaking |
Durability is handled separately: replicated storage and tested backups protect the mappings, which are the system's real asset.
A public redirect service is an attractive tool for abuse. Security here is mostly about limiting what the service can be used to do.
| Threat | Mitigation |
|---|---|
| Links to phishing or malware | Accept only http and https, optionally check destinations against reputation or block lists, and provide a way to report and disable links |
| Code enumeration | Sequential codes are easy to scan; scramble IDs or use random codes where guessing matters. Do not rely on an unguessable URL to protect private data |
| Resource abuse | Rate limit creation per client or API key, and apply protective limits at the edge |
| Open redirect misuse | Redirect only to destinations stored at creation, never to a destination supplied in the redirect request |
| Transport and access | TLS everywhere, authenticated creation, least-privilege database credentials, secrets kept in a vault |
| Accountability | Audit creation and disabling events |
Good design is a record of choices and what each one cost. These are the main ones in this walkthrough.
| Decision | Chosen here | What it costs |
|---|---|---|
| Redirect status | 302 temporary redirect | More traffic reaches the service than with 301, in exchange for control over expiry and change |
| Key generation | ID ranges encoded in base62 | Predictable codes unless scrambled; a range allocator to operate |
| Caching | Cache-aside with expiry-aware TTL | Possible stale results; stampede handling |
| Partitioning | Hash of the short code | No range queries; resharding effort |
| Consistency | Replicas may lag for redirects | A new link may briefly not resolve on a lagging replica; read from the primary or cache on create |
| Data model | Key-value style access | Limited ad-hoc querying |
If requirements change, for example link analytics or per-user management, several of these choices would be revisited, starting with the data model and the partitioning scheme.
Possible Azure Mapping
The design above is cloud-agnostic. This maps each component to Azure services that could implement it.
| Architectural component | Possible Azure services | Note |
|---|---|---|
| Edge and load balancing | Azure Front Door, Azure Application Gateway | Terminates TLS and routes traffic |
| Create and redirect services | Azure Container Apps, App Service, Azure Kubernetes Service | Stateless, horizontally scaled |
| Cache | Azure Cache for Redis | Cache-aside for redirects |
| Partitioned datastore | Azure Cosmos DB (partition key: short code), or Azure SQL Database | Key-based lookups |
| Key range allocator | A small service backed by durable storage | Depends on the chosen store |
| Rate limiting and API management | Azure API Management | Policies per client or key |
| Secrets | Azure Key Vault | Credentials outside source control |
| Observability | Azure Monitor, Application Insights | Metrics, traces, logs |
Next in the design library
Rate Limiter, Notification System, Event-Driven Order Processing, Distributed Cache and a Document Processing / Knowledge Platform are coming soon.