Skip to content
C Codeloom
System Design

System Design: Design a URL Shortener (TinyURL/Bit.ly)

Design a URL shortener like TinyURL or Bit.ly. Covers ID generation, storage, read-heavy scaling, caching, analytics, and tradeoffs you should defend in an interview.

·5 min read · By Yash Kesharwani
Intermediate 11 min read

What you'll learn

  • Generate short, collision-free IDs at scale
  • Pick a storage engine for a read-heavy workload
  • Cache hot links and absorb viral traffic
  • Design the redirect path for sub-50ms latency
  • Talk through tradeoffs an interviewer expects

Prerequisites

  • Comfort with HTTP and REST. See [What is REST](/blog/what-is-rest).
  • Basic database knowledge. See [SQL Indexes and Performance](/blog/sql-indexes-and-performance).

TinyURL and Bit.ly take a long URL and return something like bit.ly/3xK9pQa. The service is mostly redirects — billions of GETs against a tiny payload — and the interesting design work is in ID generation, caching, and survival under viral spikes.

Functional Requirements

  • Shorten a long URL into a unique short code.
  • Redirect a short code to the original URL with HTTP 301 or 302.
  • Optional custom alias (vanity codes).
  • Optional expiration time per link.
  • Click analytics: total clicks, referrer, country, device.

Out of scope for a 45-minute interview: account management, billing, abuse detection beyond basics.

Non-Functional Requirements

  • Read-heavy: roughly 100:1 read to write. Assume 100M new links per month and 10B redirects per month.
  • Write QPS: about 40 writes per second average, 200 peak.
  • Read QPS: about 4000 average, 40000 peak during viral events.
  • Redirect latency: p99 under 50 ms.
  • Storage: 100M links per month at roughly 500 bytes each is 50 GB per month, 600 GB per year.
  • Availability target: 99.99 percent. Reads matter more than writes — a failed shorten is annoying, a failed redirect breaks the internet.

High-Level Architecture

  • Edge: CDN and anycast DNS to terminate TLS close to the user.
  • API gateway and load balancer in front of stateless app servers.
  • Write path: app server calls an ID generation service, persists code, long_url, owner, created_at, expires_at to the primary database, and pushes the row into the cache.
  • Read path: app server checks cache by code, falls back to database, returns a 301/302 redirect.
  • Analytics path: redirect emits an event to a message queue. A consumer aggregates clicks into a separate analytics store. This keeps the hot path lean.

The redirect service should be its own deployment so it can scale independently from the write API.

Data Model

The core table is tiny:

CREATE TABLE links (
  code        VARCHAR(10) PRIMARY KEY,
  long_url    TEXT NOT NULL,
  owner_id    BIGINT,
  created_at  TIMESTAMP NOT NULL,
  expires_at  TIMESTAMP
);

CREATE INDEX idx_links_owner ON links(owner_id);

Analytics is append-only and lives elsewhere:

CREATE TABLE clicks (
  code        VARCHAR(10),
  ts          TIMESTAMP,
  country     CHAR(2),
  referrer    TEXT,
  ua_hash     BIGINT
);

Code length matters. Base62 over 7 characters gives roughly 3.5 trillion combinations, plenty for any realistic system.

Key APIs

POST /api/v1/links
  body: long_url, custom_alias (optional), expires_at (optional)
  returns: short_code, short_url

GET /:code
  returns: 301 redirect to long_url

GET /api/v1/links/:code/stats
  returns: total_clicks, by_country, by_day

Use 301 if you want browsers and proxies to cache the redirect; use 302 if you need every click to hit your servers for analytics. Most production shorteners pick 302 for that reason.

Scaling and Tradeoffs

ID generation. Three reasonable options:

  1. Counter plus base62 encode. A central counter (Redis INCR or a database sequence) is simple but a single point of contention. Pre-allocate ranges per app server to avoid bottlenecking.
  2. Hash the URL with MD5 or SHA1 and take the first 7 base62 chars. Same URL collapses to the same code, but collisions need a retry loop.
  3. Snowflake-style 64-bit IDs (timestamp plus worker plus sequence) then base62 encode. Decentralized, no contention, slightly longer codes.

I default to pre-allocated ranges from a counter service. It is the simplest model that scales and produces short codes.

Caching. Redis or Memcached in front of the database. The working set is heavily skewed — a small fraction of links drive most traffic. A 10 GB cache often serves 95 percent of reads. Use an LRU policy and a long TTL since rows are immutable. For an explainer on why caches help, see Big O Notation Explained — lookups are O(1) but only if the data fits in memory.

Sharding. Once the database is hot, shard by code. A consistent-hash sharding scheme keeps a single code on a single node and lets you rebalance without rewriting every key.

Replication. Async read replicas for fan-out. Accept that a freshly created link may take a second to appear on a replica — the API server can write-through to cache so the very next redirect still works.

Hot key problem. A viral link can take down a single shard. Mitigate with an edge cache, replicated copies of hot keys across shards, or a probabilistic local cache on each app server with a 1-second TTL.

Custom aliases. Check uniqueness in a single round trip — INSERT ... ON CONFLICT rather than SELECT then INSERT. The latter races under concurrency.

What to Say in an Interview

  • Lead with read-write skew. Say it is read-heavy and that the redirect path must be fast and cacheable. This frames every later choice.
  • Pick ID generation deliberately. State that you prefer counter ranges for simplicity, and that hashing the URL is fine if deduplication matters.
  • Bring up the 301 vs 302 tradeoff. It signals you have actually run one of these.
  • Mention the hot key problem and at least one mitigation. Most candidates miss this.
  • Separate analytics from the hot path with a queue. Do not write to the clicks table inline.

Wrap up

A URL shortener is mostly a key-value store with a redirect API. The work is in keeping reads cheap: small codes, an aggressive cache, a sharded primary, and analytics kicked to a queue. If you can defend ID generation, the hot key problem, and 301 versus 302, you have covered what an interviewer wants to hear.