Design a URL Shortener like TinyURL
Turn long links into short codes and redirect billions of clicks fast.
Asked at Google, Amazon and 3 more
4 min read·8 sections
A URL shortener takes a long link like https://example.com/blog/2026/10/a-very-long-post-title?ref=newsletter and returns something like sho.rt/aZ3kP9. Anyone who opens the short link is redirected to the original. It sounds tiny, but it is a great interview question because it touches ID generation, storage choice, caching and read-heavy scaling in under an hour.
Requirements#
Functional
- Given a long URL, return a unique short URL.
- Opening a short URL redirects to the long one.
- Users can optionally pick a custom alias, like
sho.rt/launch. - Links can expire after a set time.
Non-functional
- Low latency redirects. A redirect should take a few milliseconds on our side.
- High availability. If redirects fail, every link on the internet that uses us breaks.
- Short codes are not guessable in sequence, so people can't enumerate private links.
Back-of-the-envelope estimates#
Assume 100 million new links a month and a 100:1 read to write ratio.
This is a read-heavy system with modest storage. The redirect path is where the design effort should go.
API design#
POST /api/v1/links
{ "longUrl": "https://example.com/...", "alias": "launch", "expiresAt": "2027-01-01" }
→ 201 { "shortUrl": "https://sho.rt/launch" }
GET /{code}
→ 301 or 302, Location: https://example.com/...301 vs 302 matters. A 301 (permanent) lets browsers cache the redirect, which cuts our traffic but hides repeat clicks from analytics. A 302 (temporary) sends every click through us. Pick 302 if analytics matter, 301 if cost matters.
Data model#
CREATE TABLE links (
code VARCHAR(10) PRIMARY KEY,
long_url TEXT NOT NULL,
user_id BIGINT,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP
);Every read is a lookup by code, there are no joins, and the data is huge but simple. A key-value or wide-column store (DynamoDB, Cassandra) fits well, though a sharded relational database works too at this size.
High-level design#
- Create: the link service takes a code from the key generator, writes the row, and returns the short URL.
- Redirect: the service checks Redis first. On a miss it reads the database, fills the cache and returns the redirect.
Generating short codes#
A 7-character code over [a-zA-Z0-9] gives 62⁷ ≈ 3.5 trillion combinations, which is plenty. There are three common ways to produce one.
Option 1: hash the URL
Take base62(md5(longUrl)) and keep the first 7 characters. It is simple, and the same URL gets the same code. The catch is collisions: two URLs can share a prefix, so every insert has to check and retry with a salt.
Option 2: a counter encoded in base62
A global counter, 1, 2, 3..., encoded as base62 never collides. But codes become sequential and guessable, and a single counter is a bottleneck. You can hand out ranges (server A gets 1 to 1M, server B gets 1M to 2M) through ZooKeeper or a database row, and shuffle the bits so codes don't look sequential.
Option 3: a key generation service
A background worker pre-generates random unique codes into a table of unused keys. App servers grab a batch into memory and hand them out. Inserts never collide, and the only cost is a little waste if a server crashes holding a batch.
Scaling the read path#
- Cache aggressively. Link popularity follows a power law, so a small Redis cluster holding the top 20% of codes serves most traffic. Use LRU eviction.
- Shard by code. Hash-partition the links table on
codeso reads spread evenly. - Replicate reads. Each shard gets read replicas; the slight replication lag is fine for links.
- Edge caching. For 301s, a CDN can answer redirects without reaching our servers at all.
Trade-offs and extensions#
- Expiry: store
expires_atand check it on read; a nightly job removes expired rows and returns their codes to the pool. - Analytics: publish a click event to Kafka on each redirect and aggregate counts asynchronously, so the redirect never waits on analytics writes.
- Abuse: check new URLs against malware and phishing lists, and rate-limit link creation per user and IP.
- Custom aliases: reserve them in the same table; the primary key enforces uniqueness for free.
Finished this walkthrough?
Mark it as read to track your progress in the list.