ayushsalampuriya.xyz Revise

System Design · Design Question

Design a URL Shortener

A URL shortener turns a long web address into a compact link; TinyURL and Bitly are real examples.

In simple words: Store a small code that points to a long URL. Redirects are the busy path, so cache them; creation can stay simple and collision-safe.

Concrete example: https://short.ly/aB3xY9 redirects to a long product URL. Creation is one write; millions of redirects hit cache first because reads are ~100× writes.

1. Clarify requirements

Functional

Non-functional

Back-of-envelope (example)

100M new URLs / month ≈ 40 QPS write average (peaks higher) Redirects 100x writes ≈ 4k QPS average Store: 100M * ~500B ≈ 50GB / year + growth → SQL or KV fine with sharding later

2. API

POST /api/v1/urls body: { "longUrl": "...", "customCode": "optional", "ttlDays": 30 } → 201 { "shortUrl": "https://short.ly/aB3xY9", "code": "aB3xY9" } GET /{code} → 301 Location: <longUrl> (or 302 if you want analytics every hit) GET /api/v1/urls/{code}/stats (auth) → { "clicks": 123, "createdAt": "..." } DELETE /api/v1/urls/{code} (auth)

3. Short code design

Alphabet: [a-zA-Z0-9] = 62 characters. Length 7 → 62^7 ≈ 3.5e12 possibilities — enough for years at 100M/month.

ApproachHowTrade-off
Hash + truncatehash(longUrl), take 7 chars, retry on collisionSimple; need collision handling
Base62(counter)Distributed ID → encodeNo collision; IDs predictable unless salted
Random 7 charsGenerate, insert if freeSimple; retries under load

Interview favorite: pre-generate a Snowflake-like ID, then Base62-encode it. For custom aliases: unique constraint; reject if taken.

4. Data model

urls code PK varchar(16) long_url text user_id nullable created_at timestamp expires_at timestamp nullable click_count bigint (or separate analytics store) UNIQUE(code) INDEX(expires_at) for cleanup jobs

5. High-level design

Client | v API / LB --create--> URL Service --> DB (primary) | | | +--> Cache put (code → long_url) | +--GET /{code}--> Redirect Service --> Cache hit? --yes--> 301 \--miss--> DB --> fill cache

6. Deep dives

Redirect path

This is the hottest path. Cache code→URL in Redis with a TTL. Use 301 if you want browsers/CDNs to cache the redirect; 302 if every click must hit you for analytics. Many systems use 302 + async click event to a queue.

Collisions

insert code; on unique violation → regenerate / lengthen / use next ID Never return a code that maps to a different long_url

Analytics

Do not increment click_count synchronously on the redirect hot path at huge scale. Emit click(code, ts, meta) to Kafka/SQS; aggregate asynchronously.

7. Scaling

8. Edge cases

9. Failure modes

Interview talking points

  • Lead with: State the read-heavy workload, then optimize the redirect path.
  • Clarify write QPS vs redirect QPS; redirects dominate.
  • A 7-character Base62 code is usually enough; handle collisions explicitly.
  • API: POST create, GET redirect, optional stats/delete.
  • Cache code→long_url on the hot path; async analytics.
  • 301 vs 302 depends on caching vs counting every click.
  • Scale: LB + Redis + shard DB by code when needed.
  • Custom aliases = unique constraint + reject on conflict.