Sign in
TodayMapLearnPracticeReview
Library
16 MINinterviewCase StudiesNot started

Design URL Shortener

Design a bit.ly-style URL shortener that turns long URLs into short codes, serves millions of redirects per second with single-digit-millisecond latency, and tracks click analytics. The case study walks through hash-based short codes, 301 vs 302 redirects, a Redis read-through cache for hot URLs, and the trade-offs between base-62 encoding, counter-based IDs, and collision handling.

Why this matters

URL shorteners are the 'Hello World' of system design interviews because they compress a surprising number of real concerns into a tiny surface area: a read-heavy workload (typically 100:1 read:write), a globally unique ID generator, hot-key caching, eventual consistency for analytics, and a CDN-friendly redirect path. Mastering it gives you a template for every other key-value-shaped system.

Prerequisites
  • Consistent Hashing
  • Cache Aside (Lazy Loading)
  • Load Balancers
Related
  • Design Key-Value Store
Used in

Foundational.

Lesson

How it works

What are we designing? A service that takes a long URL like https://www.example.com/very/long/path?with=query&params=here and returns a short alias such as https://bit.ly/aB3x9. When anyone visits the short URL, the service redirects them to the original long URL. Alongside the redirect, the service records click analytics (referrer, geolocation, timestamp, user-agent) so marketers can measure campaign performance.

Two functional primitives define the system: a shorten endpoint (write path) and a redirect endpoint (read path). The interesting design tension comes from three facts: reads dwarf writes by roughly 100:1, every short code must be globally unique forever, and the redirect latency budget is brutal because a redirect adds a round trip to the user's actual destination.

Functional requirements.

  • A user submits a long URL and receives a short code (e.g. aB3x9) that maps back to the original.
  • Visiting https://short.io/<code> returns an HTTP redirect to the long URL.
  • Users may optionally pick a custom alias (e.g. short.io/my-summit-2024).
  • The service records click events: timestamp, IP, referrer, user-agent.
  • Links can be expired or deleted by their owner.

Non-functional requirements.

  • Read latency: p99 < 30 ms for redirects (the redirect is on the critical path of the user's browser).
  • Availability: 99.99% for reads (a dead short link breaks every SMS / email that embeds it).
  • Durability: shortened URLs must keep resolving for years; we cannot lose a mapping.
  • Write latency: < 200 ms is fine — shortening is not latency-critical.
  • Analytics freshness: clicks visible in dashboards within ~1 minute (eventual).
  • Scale: 100M new URLs/month, 10B redirects/day at peak (read-heavy, 100:1).

Non-goals (v1). No A/B redirect variants, no password-protected links, no per-link rate limiting. These can be layered on later without changing the core.

Capacity estimation. Assume 100M new short links/month and a 100:1 read:write ratio.

Writes. 100M / 30 days / 86400 s = ~40 shortens/sec average, peak ~200/sec. Trivial — a single database node could handle this.

Reads. 100M x 100 = 10B redirects/month = ~3,850 redirects/sec average, peak ~20,000/sec (3-5x for marketing bursts, US-east daytime). At 20K QPS a single Redis shard is borderline; we will need a small cache cluster.

Storage. Each mapping is ~500 bytes (short code, long URL, owner, created_at, expires_at, metadata). 100M/month x 500 B = 50 GB/month = ~600 GB/year. Five years = 3 TB. Fits in a sharded PostgreSQL cluster or a DynamoDB-style KV store; no need for Hadoop-class storage.

Bandwidth. Redirect response is ~1 KB (HTTP headers + 302 + cache headers). 20,000 QPS x 1 KB = 20 MB/s = ~160 Mbps peak egress. Add CDN caching for hot links and origin egress drops 10x.

Analytics writes. 10B clicks/day, each event ~200 bytes -> 2 TB/day of click logs. These go to a streaming pipeline (Kafka -> S3/warehouse), not the redirect path.

Short-code keyspace. With 7-character base-62 codes (a-z, A-Z, 0-9 = 62 chars), the keyspace is 62^7 = 3.5 trillion. Plenty for years of growth; 6 chars (62^6 = 56 billion) would also suffice but leaves less collision margin.

APIs.

code
POST /v1/shorten
  body: { long_url, custom_alias?, expires_at?, owner_id? }
  resp: { short_code, short_url, long_url, created_at }

GET  /<short_code>           -> 301/302 redirect to long_url

DELETE /v1/links/<short_code>   -> mark deleted (returns 410 on redirect)

GET  /v1/links/<short_code>/stats?from=&to=   -> click counts, top referrers

Two design choices worth flagging. First, 301 vs 302: a 301 Moved Permanently is browser-cached, which is great for latency but means we lose the click event (the browser never hits us again). 302 Found is not cached, so every click traverses our service — required for analytics. bit.ly uses 301 with a separate + suffix (e.g. bit.ly/aB3x9+) for the stats page. We will use 302 with Cache-Control: private, max-age=30 so clicks are counted but a single user's repeat clicks within 30s don't double-hit origin.

Second, the redirect endpoint is unauthenticated and on the hot path; it must not touch the database on every request. A Redis cache-aside in front of the mapping table absorbs >95% of reads.

Data model.

Link mappings (sharded SQL, primary store):

code
links (
  short_code   VARCHAR(7)  PRIMARY KEY,
  long_url     TEXT        NOT NULL,
  owner_id     BIGINT,
  created_at   TIMESTAMP,
  expires_at   TIMESTAMP NULL,
  deleted      BOOLEAN     DEFAULT false
)

Index (owner_id, created_at) for the user dashboard.

ID generator. Two options:

  1. MD5(long_url) then base-62 encode the first 7 chars — deterministic, idempotent (same long URL -> same short code), but collision-prone and you cannot choose the length.
  2. Counter-based: a global auto-increment (Ticket Server pattern, like Flickr's ticket servers) produces a 64-bit ID, then base-62 encode it. Deterministic, collision-free, sortable by creation time.

We pick the counter-based approach with two independent ticket-server DBs (odd/even IDs) for HA, then base-62 encode. 7 chars of base-62 supports 3.5 trillion URLs.

Click events (analytics path, separate pipeline):

code
click_events (
  event_id   BIGINT,
  short_code VARCHAR(7),
  ts         TIMESTAMP,
  ip         INET,
  referrer   TEXT,
  ua         TEXT
)

Sharded into Kafka -> S3 / ClickHouse for analytics.

Deep dive: short-code generation and collision handling. The single hardest design decision is how to mint short codes that are unique, short, and unguessable enough to discourage enumeration. Three families of approach:

  1. Hash-and-truncate (MD5(long_url)[:7]). Pros: deterministic, idempotent, no coordinator. Cons: collisions grow quadratically (birthday paradox — 62^7 keyspace means ~50% collision probability around 70M codes); also reveals that two users shortened the same URL.

  2. Counter + base-62 encode (our choice). A 64-bit monotonic counter from a ticket server is encoded as [0-9a-zA-Z]. Pros: globally unique by construction, lexicographically sortable, no collisions ever, 7 chars cover 3.5T URLs. Cons: requires a coordination service (the ticket server), and codes are enumerable — an attacker can scan bit.ly/aaa, bit.ly/aab, ... and harvest every link. Mitigation: either accept this (most public shorteners do, and add abuse detection) or XOR the counter with a per-service secret to obfuscate ordering.

  3. Random 7-char codes with retry on collision. Pick uniformly from 62^7, check the DB, retry if exists. Pros: no coordinator, codes unguessable. Cons: as the keyspace fills, collision retries explode (birthday paradox again); also requires a unique-index check on every write.

Ticket server HA. Flickr's pattern: two MySQL masters, one hands out even IDs, the other odd. Either can fail and the system keeps minting IDs. The ticket server is the one true write-bottleneck — at 200 writes/sec it is nowhere near saturation, but we still want redundancy.

Cache-aside specifics. On a redirect: (1) GET short_code from Redis. (2) On miss, SELECT long_url FROM links WHERE short_code=?; if found, SET into Redis with TTL of 1 hour (so stale-then-deleted links clear themselves). (3) Return 302. We pre-warm the cache for any code that trends; the hot set is small (Pareto: top 1% of links = 80% of traffic).

Anti-abuse. A shortener is a phisher's best friend. We add a Google-Safe-Browsing-style blocklist check on shorten, and on redirect we check a bloom filter of known-bad codes. Reports of malicious links are pushed to the blocklist within seconds.

Bottlenecks and failure modes.

  • Hot short code. A celebrity tweets a bit.ly link -> 100K redirects in 60 seconds, all to one Redis key. The shard owning that key becomes a hotspot. Mitigation: detect hot keys (top-K window) and replicate them to a pool of read-only 'shadow' keys (aB3x9__1, aB3x9__2); the redirect service picks one at random. CDN also absorbs most of this if the link owner opted into longer cache TTLs.

  • Ticket server failure. If both ticket DBs are down, no new links can be created (but redirects still work). Mitigation: each ticket server pre-allocates a block of 10K IDs into an in-memory counter on the shorten service, so the service can mint IDs for several minutes even with the ticket server gone.

  • Redis cache failure. If Redis dies, all reads fall through to the DB. At 20K QPS this will probably crush the DB. Mitigation: Redis cluster with replicas + client-side circuit breaker that, on Redis failure, returns 302 to a stale cached long URL from a local LRU (better to redirect to the right place than to 5xx). If no local cache, return 503 with Retry-After and degrade.

  • CDN misconfiguration caching a 404. If we cache 404 Not Found for a code that doesn't exist yet (eventually consistent propagation), a later shorten of that code will return 404 for the CDN TTL. Mitigation: never cache 4xx/5xx at the CDN; only cache 302s.

  • Analytics pipeline backpressure. If Kafka backs up, the redirect service must not block. Mitigation: fire-and-forget analytics writes via an async client with a tiny in-memory buffer; drop events rather than stall a redirect.

  • Link rot. A long URL 404s years later. We can periodically crawl long URLs in the background and flag dead links.

Scaling strategy and trade-offs.

Read path scaling. The cache hierarchy is CDN -> Redis cluster -> DB. Each layer absorbs roughly 10x of the previous: CDN catches 60% (repeat-clicks by the same user within TTL), Redis catches 35% of what's left, the DB only sees 5%. At 20K QPS this means ~1K QPS hits the DB — comfortable for a 3-shard PostgreSQL cluster.

Write path scaling. The bottleneck is the ticket server. We can shard ticket servers by geography (US-east mints IDs 0..N, EU mints N+1..2N) but then IDs are no longer globally sortable. Alternative: use Snowflake-style IDs (timestamp + worker_id + sequence) — no coordinator, sortable, fits in 64 bits.

Database sharding. Shard links by short_code hash with consistent hashing so adding a shard doesn't rehash everything. Each shard is a primary + 2 replicas. Cross-shard queries (e.g. 'all links owned by user X') require a secondary index shard keyed by owner_id.

Multi-region. Reads must be fast globally. We deploy the redirect service + Redis cache in 5+ regions (us-east, us-west, eu-west, ap-south, ap-northeast). The links DB is replicated asynchronously to read replicas in each region. Writes (shorten) go to a single primary region to keep ID assignment simple; this is fine because writes are not latency-sensitive.

Trade-offs made explicit.

  • We chose 302 over 301 — gained accurate click analytics, lost free browser caching (mitigated by Cache-Control: max-age=30).
  • We chose counter-based codes over hash-based — gained no collisions and sortability, lost idempotency (the same long URL shortened twice yields two codes) and accepted enumerability.
  • We chose Redis cache-aside over read-through — gained simplicity, lost automatic freshness (a deleted link can be served stale for up to Redis TTL). We mitigate by issuing DEL on delete.
  • We chose single-region writes — gained simple ID assignment, lost write availability if the primary region fails (acceptable: writes are <1% of traffic and not on the user's critical path).
Check yourself
interview

You discover that one short code is generating 80% of your redirect traffic after a celebrity tweet. The Redis shard owning that key is at 100% CPU. What is the best mitigation?

Pick one answer.

Check yourself
solid

Why does bit.ly return a 301 redirect while we chose 302? What trade-off is being made?

Pick one answer.

Engineering mental model

Mental model. Think of Design URL Shortener as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does Design URL Shortener mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”

Design lens

Before choosing Design URL Shortener, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.

Original NO CAP systems visual for Design URL Shortener.
Image unavailable. Original NO CAP systems visual for Design URL Shortener.
Design URL Shortener: a compact system-thinking visual.— Original NO CAP visual.
// Pseudocode
request = receive()
result = design_url_shortener(request)
return result

// Production questions:
// 1. What happens on timeout?
// 2. Can this operation be retried safely?
// 3. What is the bottleneck?
A minimal engineering sketch for reasoning about Design URL Shortener.

Back-of-the-envelope reasoning

Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.

Interactive sandboxdeterministic

Interactive thought experiment: Design URL Shortener

Change the variables below and predict what breaks first in Design URL Shortener. The production lab can later reuse these same inputs.

System pressure6%
Try this

Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.

Hint

If you are stuck on Design URL Shortener, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.

Check yourself
solid

You increase traffic by 10× in a system using Design URL Shortener. What should you inspect first?

Pick one answer.

Check yourself
interview

Which statement is the safest engineering habit when using Design URL Shortener?

Pick one answer.

Try this
interview

You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.

Production scenario: your system uses Design URL Shortener, traffic suddenly spikes, and p99 latency doubles. What is your first move?

Interview drill

Answer this without notes: When would you choose Design URL Shortener, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.

Engineering lens

A useful engineering lens for Design URL Shortener: define the problem it solves, the simpler design that fails first, the constraint that forces you to introduce this concept, and the new failure modes the concept creates.

Numerical sanity check

Back-of-the-envelope reasoning beats fake precision. State your traffic, payload, concurrency and growth assumptions explicitly, then calculate enough to know whether the current architecture is orders of magnitude away from the target.

Check yourself
interview

Do not optimize for a memorized definition. Reason from the workload and failure mode.

Imagine the simplest version of a system using Design URL Shortener. What breaks first as traffic grows by 10×, and what would you change before reaching 100×?

Pick one answer.

Trade-offs

What you gain, what you pay

Pros
  • +Counter-based short codes are collision-free, sortable, and fit in 7 chars for trillions of URLs.
  • +Cache-aside with CDN fronting absorbs >95% of reads off the database.
  • +Async analytics side-channel keeps the redirect path latency at single-digit ms.
  • +Stateless redirect service autoscales cleanly with traffic.
Cons
  • −Counter-based codes are enumerable; abuse detection is mandatory.
  • −Same long URL shortened twice yields two different codes (no idempotency).
  • −Ticket server is a write bottleneck and a single point of failure without HA setup.
  • −302 redirects cost more origin load than 301s and require careful cache headers.
Failure modes

How this breaks in production

  • Hot short code saturates one Redis shard — needs shadow-key replication.
  • Ticket server failure stops new link creation — needs HA pair + ID pre-allocation.
  • Redis failure collapses traffic onto DB — needs circuit breaker + local LRU fallback.
  • CDN caching a 404 for a not-yet-created code — never cache 4xx/5xx at CDN.
  • Analytics pipeline backpressure stalls redirects — must be fire-and-forget.
Common mistakes

Don't fall into these traps

  • •Using MD5(long_url)[:7] without collision handling — birthday paradox hits around 70M codes.
  • •Forgetting that Redis cluster shards by key, so hot keys don't redistribute automatically.
  • •Caching 4xx responses at the CDN — propagates transient errors for the cache TTL.
  • •Blocking the redirect on the analytics write — Kafka must be async.
  • •Single-region writes without HA on the ticket server.
Where you see it

Real systems using this

bit.ly, TinyURL, Rebrandly.Twitter's t.co wrapper for all outbound links.Marketing campaign link tracking (Mailchimp, Hubspot).SMS short links for delivery reports (Twilio, MessageBird).
Teardowns

How real systems implement this

  • bit.ly — Counter-based 6-7 char codes, 301 redirects with a `+` suffix for stats, Redis fronted Cassandra cluster for mappings.
  • Flickr ticket servers — Two MySQL masters with auto-increment-offset and auto-increment-increment to mint globally unique 64-bit IDs without a coordinator.
  • Twitter t.co — Wraps every outbound URL in a t.co short link for abuse screening and click analytics; uses Snowflake-style IDs.
Interview prompts

Practice saying it out loud

  • Q1Design a URL shortener like bit.ly. How do you generate the short codes?
  • Q2How would you handle a single short link that suddenly gets 1M clicks/second?
  • Q3How do you make sure a deleted short link stops resolving everywhere immediately?
  • Q4Walk me through the read path latency budget for a redirect.
Research

Further reading & references

System Design Primer
Open source
ByteByteGo — Scale from zero to millions
ByteByteGo
System Design Tutorial
GeeksforGeeks
System Design Roadmap
roadmap.sh
Case Studies reference
Reference
Case Studies reference
Reference
Case Studies reference
Reference

Core explanations are original NO CAP material. External references are provided for deeper study and standards.

What next?

Mark as understood once the mental model clicks.

Next recommended

Design Key-Value Store