ayushsalampuriya.xyz Revise

System Design · Design Question

Design a URL Shortener

TinyURL-style service: create short links, redirect fast, handle collisions, and scale reads with cache + DB.

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 chars. 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 or Snowflake-like ID, base62 encode. 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

Hottest path. Cache code→URL in Redis with 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

Quick revision

  • Clarify write QPS vs redirect QPS; redirects dominate.
  • 7-char base62 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.