Playbook / General system design / Design a URL shortener

Design a URL shortener

Expected question

"Design a URL shortener (bit.ly-style). How do you generate short codes, redirect with low latency, and track analytics at scale?"

Variant forms

Same design, different framing:

  • "Design a URL shortener for 100M new links/month and 10B redirects/month."
  • "How do you generate short codes — hash, counter, or random — and avoid collisions?"
  • "Design custom branded domains per enterprise customer."
  • "Our short links were used for malware — architect abuse detection and takedown."
  • "How do you expire links and support UTM analytics without slowing redirects?"
  • "Design read-heavy redirect path with <10ms P99 globally."
  • "Scale the analytics pipeline separately from the redirect hot path."

Where this actually gets asked

Canonical warm-up / screening system-design question (Bitly-style) across Google, Amazon, Meta, and startups. Often used before a harder follow-up in the same loop. Staff+ depth: ID generation, read-heavy caching, 301 vs 302, analytics, and abuse.

Executive summary

30-second thesis

I'd mint Base62 codes from allocated counter ranges, cache code→URL on the redirect hot path, default 302 + async click logs — and treat create-path abuse as a first-class concern.

2-minute answer

Clarify create QPS vs redirect QPS (often ~100:1 H), custom aliases, and analytics accuracy. Default: range-allocated IDs (or Snowflake → Base62), durable mapping, Redis/CDN in front of redirects. Prefer 302 when analytics matter or destinations can change; 301 only when immutable and you're okay with browser/CDN caching forever. Analytics off the hot path via queue. Custom aliases need a reservation transaction; reconcile if DB write wins and cache fill fails. Rate-limit creates; async malware scan with suspend. Viral codes: edge cache + shard awareness so one hot key doesn't melt a single DB partition.

Quantitative trade-offs

DecisionTrade-off and reversal evidenceEvidence class
302 vs 301302 keeps counts/flexible targets; reverse to 301 when immutable and analytics are approximate OK.H
Counter/Base62 vs hash-and-retryRanges avoid collision storms; reverse to hash only for tiny scale.H
Cache-aside redirect vs always-hit DBCache for p99 <10–50ms (H); reverse only if consistency of destination changes forces shorter TTL + purge.H

What I'd ask them

  1. Can destinations change after create?
  2. Custom aliases and enterprise branded domains in v1?
  3. Redirect SLA and analytics lag tolerance?

Requirements

Functional

  • Create a short code for a long URL; optional custom alias and TTL.
  • Redirect short URL to long URL quickly.
  • Basic click analytics (count, optional referrer/country aggregates).

Non-functional

  • Extremely read-heavy (≈100:1 (H)); redirects P99 < 10–50ms with cache.
  • Unique short codes; no collisions serving wrong destinations.
  • High availability for redirects even if write path is degraded.
  • Abuse: malware URLs, spam creation rate limits.

Core entities

  • Link: short_code, long_url, owner_id, created_at, expires_at, status.
  • Click event: short_code, ts, metadata (coarse).
  • Alias reservation: custom code lock during create.

API / interface

POST /v1/links
Authorization: Bearer <token>
{ "url":"https://example.com/very/long", "custom_code?":"launch", "ttl_days?":90 }
→ 201 { "code":"aB3xY9", "short_url":"https://short.ly/aB3xY9" }
→ 409 alias_taken | 400 invalid_url

GET /{code}
→ 302 Location: <long_url>  (or 301 if immutable / analytics-light)
→ 404 | 410 gone

GET /v1/links/{code}/stats
→ { "clicks":123456, "by_day":[...] }

Staff+ callout: say whether redirects are 301 (cacheable, weaker analytics) or 302 (accurate counts).

Data Flow

Create allocates code → persist mapping → cache. Redirect reads cache then DB; async click log.

Rendering architecture diagram…

High-level design

Rendering architecture diagram…

Deep dive 1: ID generation

Base62 encode a counter (Snowflake-style or pre-generated ranges per instance) beats hash-and-retry for uniqueness at scale. Custom aliases need a reservation transaction. See also 05.

Deep dive 2: cache and 301/302

Cache long URLs by code with TTL; negative-cache 404 briefly. 301 lets browsers/CDNs cache forever — cheap but bad for accurate analytics and destination changes. 302 + async analytics is the usual product choice for Bitly-like services.

Deep dive 3: abuse

Rate-limit creates per owner/IP; scan destinations against malware lists asynchronously (mark suspended). Cap custom alias namespace squatting.

Shard by short_code; a viral link can melt one shard — edge/CDN cache + read replicas + redirect rate limits. Custom aliases need transactional uniqueness; if DB write succeeds and cache fill fails, a reconcile job must repair (avoid redirect 404). In 45 minutes, IDs + cache + 301/302 is the core; analytics is async.

What's expected at each level

  • Mid-level: hash URL, store map, redirect.
  • Senior: Base62 IDs, cache, basic stats.
  • Staff+: collision-free ID ranges, 301/302 trade-off, write/read HA split, abuse.
  • Principal: global replication strategy and analytics cost at 100B redirects/day.

Follow-up questions to expect

  • "How long should codes be?" — 62^7 is ~3.5T (H); pick length from years of creates × safety margin, not vibes.
  • "Can destinations change?" — If yes, don't 301 (or purge CDN hard). Mutable targets want 302.