Skip to main content

URL Shortening Service like TinyURL

Scalable system design for creating short URLs, redirecting users, and collecting analytics.

synchronous: caller waits
asynchronous: fire & forget

Denser dots = higher throughput. Hover any edge for its full contract.

//The Contract

Requirements

Functionalwhat the system must do
  • A user submits a long URL and receives a short, unique alias they can share
  • Visiting a short URL redirects the browser to the original long URL
  • A user can optionally choose a custom alias or set an expiration date for a link
  • The system records click analytics per short URL — count, referrer, and rough geography
Non-functionalhow well it must do it
  • Redirects must feel instant — sub-50ms p99, since the user waits on this hop before anything else loads
  • Short codes must be globally unique with zero collisions at any write rate, with no retry-on-conflict loop
  • Read traffic vastly outweighs write traffic, so the read and write paths must scale independently
  • No single component failure (cache, key generator, or a database node) should take the whole service down
//Back of the Envelope

Scale Estimates

Rough, order-of-magnitude numbers. The point is to justify the architecture, not to be exact.

Short URLs created
1M/day
drives write throughput and key-generation rate
Read : write ratio
100 : 1
redirects dwarf creations — the read path is what must scale
Peak redirect QPS
~10K/s
justifies the Redis cache tier
Short code space
7 chars
Base62^7 ≈ 3.5 trillion codes
Storage growth
~150GB/yr
~500 bytes per mapping × 1M new links/day
Cache hit rate
>80%
a small set of links absorbs most redirect traffic
//Why It Looks Like This

System Evolution

No system is born with queues and caches. Every box was added because something was about to break. Play a stage and the diagram above shrinks to what existed at that point in the story.

01

The MVP — a single application server behind a gateway

The simplest version of a URL shortener is barely more than a hash map with an HTTP front door: a client posts a long URL, one Application Server computes a code and writes the pair straight into the Database, and reading it back just reverses the lookup. A Load Balancer sits in front from day one — cheap to add now, expensive to retrofit later — even though at this stage it only ever has one target.

This survives a demo, but every redirect is a synchronous database round-trip, and a single Application Server caps how much traffic the whole system can take.

02

Cache the read path and scale the app tier

Scale pressure: Redirects outnumber creations roughly 100:1, and every single one was hitting the database directly — read latency and database load both climbed with traffic.

A Redis Cache goes in front of the database on every read, so the hot fraction of links — the ones actually getting clicked — never touch the database at all. A second Application Server joins behind the Load Balancer, which finally has real work to distribute.

Redirects get fast again, but URL creation still races the database for a free code under load, and nothing recycles the codes that fall out of use.

03

Take code generation off the write path

Scale pressure: Generating a collision-free code inline on every write means checking for conflicts under load — and at high write volume, retries start piling up.

A standalone Key Generation Service pre-computes unique Base62 codes ahead of time and hands them out on request, so assigning a code becomes a cheap, guaranteed-unique lookup instead of a check-and-retry loop against the database.

Creation is now fast and collision-free at any write rate — but every click and every new link still disappears without a trace, and expired links just sit in the database forever.

04

Add analytics and reclaim expired links

Scale pressure: Product wants click analytics, but nothing in the request path can afford to slow down for it — and the database is quietly filling up with links nobody will ever click again.

Every creation and redirect now fires an event onto a Message Queue without waiting for a response, and an Analytics Service consumes those events on its own schedule. A Cleanup Service periodically sweeps the database for expired links, deletes them, and returns their codes to the Key Generation Service's pool instead of wasting the keyspace.

This is the complete system: every synchronous hop stays on the critical path only when it has to, and everything else happens off to the side.

//Follow the Data

Request Flows

A diagram shows what exists; a trace shows what happens. Press play on a flow, or click any step, and the diagram above lights up the exact path that request takes.

Flow 01

Create a short URL

From submitting a long URL to receiving a shareable short link.

Flow 02

Redirect a short URL

From clicking a short link to landing on the original page.

Flow 03

Expire and recycle a link

The background path that reclaims codes from links nobody uses anymore.

//Component Breakdown

Key Components

Every box on the canvas, and the job it does. Click a card to locate it in the diagram; click the node itself for the full deep dive.

client×2
network×2
compute×5
database×1
cache×1
queue×1
//Deep Dive

Architecture Breakdown

Overview

This system turns long URLs into short, shareable codes and redirects visitors back to the original destination. Traffic is heavily read-skewed — redirects vastly outnumber creations — so the architecture optimizes the read path with caching while keeping the write path simple and collision-free.

Write Path — Creating a Short URL

A client submits a long URL through the API Gateway, which authenticates the request and forwards it through a Load Balancer to an available Application Server. The server pulls a pre-generated, collision-free code from the Key Generation Service and writes the mapping to the Database. The new short URL is returned immediately, and the creation event is logged asynchronously for analytics.

Read Path — Redirecting a Short URL

A redirect request follows the same Gateway → Load Balancer → App Server route. The server checks Redis first; a cache hit returns the original URL in about a millisecond. On a miss, it falls back to the Database, returns the result, and repopulates the cache so the next request for that code is fast.

Async Paths — Analytics and Cleanup

Every creation and redirect event is pushed onto a Message Queue without blocking the response, and an Analytics Service consumes those events for click counts, referrers, and geography. A separate Cleanup Service periodically scans the Database for expired links, deletes them, and recycles their short codes back into the Key Generation Service's pool instead of wasting the keyspace.

Key Trade-offs

  • Short codes are pre-generated offline by a dedicated service instead of hashed from the URL, trading a small operational component for guaranteed collision-freedom
  • The database is NoSQL, chosen for horizontal scale and simple key-value access over relational features the system never needs
  • Redirects use HTTP 302, not 301, so every click still reaches the backend and can be counted
  • Analytics and cleanup are both off the critical path — neither can slow down a redirect or a creation
//Trade-offs

Design Decisions

An architecture is a record of trade-offs. For every major choice here: what won, what lost, and why the constraints made it so.

01

Short code generation

ChoseOffline Key Generation Service (KGS) pre-computing Base62 codes
OverHash the long URL (MD5/SHA + truncate)Random string + collision check on write

Hashing a URL and truncating it reintroduces collisions, forcing a retry loop on every write. Random strings have the same problem at scale. A KGS pre-generates unique codes offline and hands them out on request, so assigning one is O(1) and collision-free by construction — the cost is one extra internal hop and a standby replica for availability.

02

Database choice

ChoseNoSQL key-value store (DynamoDB / Cassandra)
OverRelational database (PostgreSQL / MySQL)

The access pattern is almost entirely single-key lookups and inserts — shortUrl in, longUrl out. A NoSQL store gives that pattern horizontal scale and predictable low-latency reads without needing joins or transactions the system never uses. A relational database would work at small scale but becomes the first thing to bottleneck as write volume grows.

03

Redirect status code

ChoseHTTP 302 (temporary) redirect
OverHTTP 301 (permanent) redirect

A 301 tells browsers to cache the redirect permanently and skip the server on future visits, which would break click analytics — a core requirement. 302 forces every click back through the backend so it can be counted, at the cost of a marginally larger response on repeat visits.

04

Caching layer

ChoseRedis with LRU eviction, checked on every read before the database
OverNo cache — read straight from the databaseClient-side/browser caching only

With a 100:1 read:write ratio, letting every redirect hit the database directly would make it the bottleneck long before the app tier. A small hot set — roughly 20% of links — drives most traffic, so an LRU cache absorbs the vast majority of reads and keeps p99 redirect latency near a millisecond.

05

Expired link cleanup

ChoseLazy, periodic batch cleanup by a background worker
OverEager deletion the instant a link expiresDatabase-native TTL on every row

Actively hunting down every link the moment it lapses adds constant background write pressure for no user-facing benefit — storage is cheap, and a stale, unvisited mapping costs nothing sitting idle. A periodic Cleanup Service reclaims codes during low-traffic windows instead, keeping the write path free of cleanup overhead.

//What Breaks First

Bottlenecks & Failure Modes

Reading a system means sensing where it cracks under 10× load. These are the pressure points of this design, and how it holds.

Risk 01

Viral link redirect stampede

Failure mode

A single link going viral can drive thousands of redirects per second at one short code, all landing on the same cache key and, on a cold cache, the same database partition.

Mitigation

Redis absorbs the steady-state load since a hot key simply stays resident. For the cold-start case, request coalescing at the app-server layer collapses concurrent misses into a single database read instead of one per request.

Risk 02

Key Generation Service outage

Failure mode

The KGS is a single logical component. If it becomes unreachable, application servers can't mint new short codes, so URL creation grinds to a halt even though redirects keep working fine.

Mitigation

A standby KGS replica takes over via leader election, and each app server pre-fetches a small batch of unused keys locally, so a brief KGS blip is invisible to users creating links.

Risk 03

Database hot partitions from non-uniform keys

Failure mode

If short codes were assigned sequentially — an auto-increment counter, say — instead of pseudo-randomly, writes and the newest, most-clicked links would all concentrate on the same shard.

Mitigation

Base62 codes from the KGS are effectively randomly distributed, and the database uses hash-based partitioning on the short code itself, spreading both writes and hot reads evenly across shards.

Risk 04

Analytics backlog under traffic spikes

Failure mode

A sudden spike in redirects — a viral link, a bot crawl — can produce click events faster than the Analytics Service can consume them, growing an unbounded backlog on the Message Queue.

Mitigation

Logging is fire-and-forget and off the critical path by design — a growing queue delays dashboards, never redirects. The Analytics Service scales its consumer group horizontally to drain backlog, and Kafka's retention window buys time before any event is lost.

Risk 05

Malicious mass link creation

Failure mode

An attacker can script thousands of short-link creations per second to generate phishing URLs that borrow the service's domain reputation, or simply to burn through the key space faster than intended.

Mitigation

The API Gateway rate-limits creation requests per API key/IP, and newly created URLs are screened against a blacklist/safe-browsing check before the mapping is persisted.

//Break Things on Purpose

Failure Lab

Most of this architecture exists because any one box can die. Take a component offline and the diagram above shows the blast radius: what goes down, what degrades, and what the user feels.

App Server 1

REST API

No visible impact — the Load Balancer stops routing to this instance and every request goes to the surviving server instead.

Key Generation Service

Service

Redirects are completely unaffected. Creating new short URLs keeps working too — for a while — until application servers exhaust the small batch of keys they pre-fetched locally.

Redis Cache

Redis

Redirects keep working, but every request now falls through to the database — p99 redirect latency jumps from about 1ms to roughly 20ms.

Database

DynamoDB

New short URLs can't be created at all, and any redirect not already sitting in the cache fails outright — the service degrades to serving only its currently-hot links.

Message Queue

Kafka

Users notice nothing at all — creation and redirection both keep working exactly as before. Click analytics and dashboards just go stale until the queue recovers.

//Active Recall

Test Yourself

If you can answer these without scrolling back up, the architecture is yours. Try each one out loud before revealing.

//Go Deeper

Further Reading

//A Word

From the Creator

Every great system starts as a sketch on a whiteboard. The ability to zoom out and see the whole picture (how services connect, where data flows, what breaks and why) is what separates engineers who build features from engineers who build systems. This diagram is more than boxes and arrows. It's a map of decisions, trade-offs, and intentional design.

Studying architectures isn't just about passing interviews. It's about training your intuition. The more systems you take apart, the better you get at sensing where a monolith will crack, where a queue belongs, or when a cache is hiding a deeper problem. You start seeing patterns instead of chaos.

So keep reading, keep tracing those edges, keep asking "why this way and not that way." The engineers who truly understand large systems are the ones who never stop being curious about how things fit together. That curiosity is the only ingredient that really matters.

Atharva Arbat@arbat_atharva

//Explore

More Patterns