All questions
#2Web & StorageEasy

Design a URL Shortener like TinyURL

Turn long links into short codes and redirect billions of clicks fast.

Google logo
Amazon logo
Microsoft logo
Meta logo

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.

MetricEstimate
New links per second100M / (30 × 86,400) ≈ 40 writes/s
Redirects per second40 × 100 ≈ 4,000 reads/s, peaks of 20k/s
Storage per link~500 bytes (long URL, code, metadata)
Storage for 10 years100M × 12 × 10 × 500 B ≈ 6 TB

This is a read-heavy system with modest storage. The redirect path is where the design effort should go.

API design#

http
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#

sql
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#

Browser
Load balancer
Link servicestateless
Redishot codes
Link service
Links storecode → URL
Key generatorpre-made codes
Links store
Writes go through the API to the database; redirects are mostly served from cache
  1. Create: the link service takes a code from the key generator, writes the row, and returns the short URL.
  2. 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 code so 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_at and 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.