Full Stack • Java • System Design • Cloud • AI Engineering

URL Shortener System Design - 1 Hour Interview Guide

Design a scalable URL Shortener like Bitly or TinyURL. Covers requirements, capacity estimation, API design, URL encoding, redirect flow, caching, database design, sharding, analytics, and trade-offs for a 1-hour system design interview.


1-Hour Interview Roadmap

Time Topic
0 – 5 min Requirements clarification
5 – 10 min Capacity estimation
10 – 15 min API design
15 – 25 min High-level architecture
25 – 35 min URL encoding + database design
35 – 45 min Redirect flow + caching
45 – 55 min Scaling + analytics + rate limiting
55 – 60 min Trade-offs + failure scenarios

What Is a URL Shortener?

A URL Shortener takes a long URL and produces a short, unique alias that redirects users to the original URL.

Example:

Input:   https://www.example.com/products/shoes/running/men/size-12?color=blue&promo=SAVE20
Output:  https://short.ly/xK3mP9

When a user visits https://short.ly/xK3mP9, the system looks up the original URL and redirects the browser in milliseconds.

Real-world examples: Bitly, TinyURL, t.co (Twitter), goo.gl (Google)


Why Is This a Good Interview Problem?

A URL Shortener is deceptively simple but covers the full breadth of system design:

  • Read-heavy workload with extreme redirect throughput
  • Unique ID generation at scale
  • Caching strategy
  • Database choice and sharding
  • Async analytics pipeline
  • Rate limiting and abuse prevention

Step 1 — Requirements

Functional Requirements

# Requirement
1 Given a long URL, generate a short URL (6–8 characters)
2 Redirect a short URL to the original long URL
3 Support custom aliases (e.g. short.ly/my-promo)
4 Support URL expiration (TTL)
5 Track click analytics (count, device, country, referrer)
6 Allow users to delete or disable their short URLs

Non-Functional Requirements

# Requirement
1 Redirect latency < 100ms (p99)
2 High availability — 99.99% uptime
3 Short codes must be globally unique
4 Eventual consistency acceptable for analytics
5 System must handle 100:1 read-to-write ratio
6 No predictable or guessable short codes

Out of Scope

  • User authentication UI
  • Full analytics dashboard UI
  • Billing or subscription management

Step 2 — Capacity Estimation

Assumptions

Metric Value
New URLs created per day 100 million
URL read/redirect ratio 100:1
Average URL size 500 bytes
Short code size ~50 bytes
Analytics record size ~200 bytes
Data retention 5 years

Write Volume

100 million URLs/day
= ~1,160 writes/second

Read Volume

100 million × 100 = 10 billion redirects/day
= ~115,000 reads/second (peak ~300,000/sec)

Storage (5 years)

URLs:      100M/day × 365 × 5 × 550 bytes  ≈ 100 TB
Analytics: 10B/day  × 365 × 5 × 200 bytes  ≈ 3.6 PB

Key Insight

This is an extremely read-heavy system. The redirect path must be optimized above all else. Caching is not optional — it is the core design decision.


Step 3 — API Design

Create Short URL

POST /api/v1/urls

Request:
{
  "long_url": "https://example.com/very-long-path",
  "custom_alias": "my-promo",          // optional
  "expires_at": "2025-12-31T00:00:00Z" // optional
}

Response 201:
{
  "short_url": "https://short.ly/xK3mP9",
  "short_code": "xK3mP9",
  "expires_at": "2025-12-31T00:00:00Z"
}

Redirect

GET /{short_code}

Response 302: Location: https://example.com/very-long-path

Get Analytics

GET /api/v1/urls/{short_code}/analytics

Response 200:
{
  "short_code": "xK3mP9",
  "total_clicks": 48291,
  "unique_visitors": 31042,
  "top_countries": ["US", "IN", "GB"],
  "top_devices": ["mobile", "desktop"]
}

Delete URL

DELETE /api/v1/urls/{short_code}

Response 204: No Content

Step 4 — High-Level Architecture

flowchart TD
    Client --> AG[API Gateway]
    AG --> US[URL Service]
    AG --> RS[Redirect Service]
    US --> DB[(Primary DB)]
    RS --> RC[(Redis Cache)]
    RC -->|cache miss| DB
    RS --> AQ[Analytics Queue\nKafka]
    AQ --> AS[Analytics Service]
    AS --> AD[(Analytics Store)]
    US --> IDG[ID Generator\nSnowflake]

Component Responsibilities

Component Responsibility
API Gateway Rate limiting, authentication, routing
URL Service Create, validate, and store short URL mappings
Redirect Service Look up short code → long URL, issue HTTP redirect
Redis Cache Cache hot short code → long URL mappings for fast lookups
ID Generator Generate globally unique, non-sequential IDs (Snowflake)
Analytics Queue Receive click events asynchronously (Kafka)
Analytics Service Process click events, write to analytics store
Analytics Store Time-series and aggregated click data (S3 + Redshift)

Step 5 — URL Encoding

The Core Problem

How do you generate a short, unique, non-guessable code for every URL?

Option Comparison

Approach Pros Cons
Auto-increment ID Simple, no collisions Predictable, enumerable
UUID Unique, no coordination needed Too long (32 chars)
MD5/SHA256 hash Deterministic Collisions possible, truncation required
Random string Unpredictable Collision probability grows with scale
Base62 + Snowflake Unique, compact, fast Requires distributed ID service

Step 1: Generate a unique 64-bit integer using a Snowflake ID generator.

Step 2: Encode that integer to Base62.

Base62 character set:

0-9  → 10 characters
a-z  → 26 characters
A-Z  → 26 characters
Total: 62 characters

Why Base62?

  • No special characters — URL-safe
  • 6 characters = 62⁶ = 56 billion unique codes
  • 7 characters = 62⁷ = 3.5 trillion unique codes

Snowflake ID

A Snowflake ID is a 64-bit number composed of:

| 41 bits timestamp | 10 bits machine ID | 12 bits sequence |
  • Monotonically increasing — no coordination between nodes needed
  • K-sortable — newer IDs are always larger
  • ~4 million IDs/second per node

Encoding Flow

flowchart LR
    LU[Long URL] --> US[URL Service]
    US --> SN[Snowflake\nID Generator]
    SN -->|64-bit ID| B62[Base62 Encoder]
    B62 -->|short_code| DB[(Database)]
    B62 -->|short_code| Resp[Return short URL]

Collision Handling

With Snowflake IDs, collisions cannot occur — each ID is guaranteed unique.

For custom aliases, check uniqueness before saving:

  • If alias already exists → return 409 Conflict
  • If alias is available → save and return 201 Created

Step 6 — Database Design

URL Mapping Table

Table: url_mappings

short_code   VARCHAR(8)   PRIMARY KEY
long_url     TEXT         NOT NULL
user_id      BIGINT       nullable (if authenticated)
created_at   TIMESTAMP    NOT NULL
expires_at   TIMESTAMP    nullable
is_active    BOOLEAN      DEFAULT true

Index: short_code — the only column queried on every redirect.

Analytics Table

Table: click_events

id           BIGINT       PRIMARY KEY
short_code   VARCHAR(8)   NOT NULL
clicked_at   TIMESTAMP    NOT NULL
country      VARCHAR(2)
device_type  VARCHAR(20)
referrer     TEXT
ip_hash      VARCHAR(64)  (hashed for privacy)

Database Choice

Factor Recommendation
Read pattern Point lookup by short_code — very fast
Write pattern Insert once, rarely update
Scale requirement Billions of rows (sharding needed)
Consistency Strong for URL creation, eventual for analytics
Recommendation PostgreSQL (primary) or DynamoDB (fully managed, auto-sharding)

Step 7 — Redirect Flow

The Critical Path

Every millisecond matters here. The redirect path must be as fast as possible.

flowchart TD
    User -->|GET /xK3mP9| RS[Redirect Service]
    RS -->|check| LC[Local Cache\nCaffeine]
    LC -->|hit| Redir[302 Redirect]
    LC -->|miss| RC[Redis Cache]
    RC -->|hit| Redir
    RC -->|miss| DB[(Database)]
    DB --> RC
    DB --> Redir
    Redir --> AQ[Kafka\nClick Event]

Cache Layers

Layer Technology TTL Capacity Purpose
Local cache Caffeine 60 sec ~10K entries/pod Fastest — avoid Redis for hot URLs
Distributed Redis 24 hrs Billions Shared across all redirect pods
Origin PostgreSQL / DynamoDB All data Source of truth

301 vs 302 Redirect

Type Meaning Browser Behavior When to Use
301 Permanent Browser caches it — no future requests Reduces server load; loses analytics
302 Temporary Browser always requests server Preserves analytics; more load

Recommendation: Use 302 to preserve click analytics. Use 301 only for permanent redirects where analytics are not needed.

Cache Key

Key:   short_code            →  "xK3mP9"
Value: long_url + expires_at →  "https://example.com/... | 2025-12-31"

Step 8 — Analytics Design

Problem

Analytics must not slow down redirects. Writing to a database synchronously on every redirect would add 20–50ms of latency.

Solution — Async Analytics Pipeline

flowchart LR
    RS[Redirect Service] -->|publish event| KF[Kafka Topic\nclick-events]
    KF --> AC[Analytics Consumer]
    AC --> AGG[Aggregation Service]
    AGG --> TS[(Time-Series DB\nRedshift / ClickHouse)]
    AGG --> RC2[Redis counters\nfor real-time counts]

Click Event Schema

{
  "short_code": "xK3mP9",
  "timestamp":  "2025-06-15T10:23:45Z",
  "ip_hash":    "sha256(...)",
  "country":    "US",
  "device":     "mobile",
  "referrer":   "https://twitter.com"
}

Click Count Accuracy

Approach Accuracy Complexity Use Case
Exact DB count 100% High Billing-critical
Redis counter ~99% Medium Real-time dashboards
HyperLogLog ~98% Low Unique visitor counts

Recommendation: Redis counters for real-time display; batch reconcile with Kafka-sourced counts nightly.


Step 9 — Scaling

The Challenge

115,000 redirects/second sustained — peaks to 300,000/second.

Read Scaling

flowchart TD
    LB[Load Balancer] --> R1[Redirect Pod]
    LB --> R2[Redirect Pod]
    LB --> R3[Redirect Pod]
    R1 --> Redis[(Redis Cluster)]
    R2 --> Redis
    R3 --> Redis
    Redis -->|miss| DBR[DB Read Replicas]
  • Redirect Service is stateless — scale horizontally to any number of pods
  • Redis Cluster handles billions of cached entries with sub-millisecond reads
  • Read replicas for DB — redirect service reads from replicas, not primary

Database Sharding

At billions of rows, a single database cannot handle the load.

Shard key: short_code (consistent hash)

flowchart TD
    RS[Redirect Service] --> SH[Shard Router]
    SH -->|hash xK3mP9| S1[Shard 1\nA-F]
    SH -->|hash mN7qR2| S2[Shard 2\nG-M]
    SH -->|hash tY9pW5| S3[Shard 3\nN-Z]
  • Consistent hashing avoids reshuffling all data when adding shards
  • Short code naturally distributes evenly across shards

For viral URLs (millions of hits per hour), push the redirect response to CDN edge nodes. The redirect happens at the CDN — zero load on origin.


Step 10 — Rate Limiting and Security

Rate Limiting

Applied at the API Gateway layer:

Operation Limit Reason
Create URL 100 requests/min per user Prevent spam / abuse
Redirect 1,000 requests/min per IP Prevent scraping / enumeration
Analytics 60 requests/min per user Protect analytics backend

Algorithm: Token Bucket — handles burst traffic naturally.

Security Concerns

Threat Defense
Malicious URL shortened Validate against Google Safe Browsing API on create
Enumeration of short codes Non-sequential Snowflake-based codes
Redirect bombing (DoS) Rate limit by IP at gateway
Phishing via short URLs Block known phishing domains, allow admin takedown
Private URL exposure Optional password-protected short URLs

Step 11 — Failure Scenarios

Failure Impact Mitigation
Redis goes down All redirects hit DB directly Local in-process cache absorbs short burst; DB read replicas handle sustained load
DB goes down Cache still serves cached URLs Read replicas + circuit breaker; write queue for new URLs
Kafka goes down Analytics events lost Redirect still works; use local buffer + retry; eventual consistency acceptable
ID Generator down Cannot create new URLs Multiple Snowflake nodes; fall back to secondary node
Single region down Full outage for that region Multi-region active-active; DNS failover

Final Architecture

flowchart TD
    Client --> CDN[CDN\nHot URL Redirects]
    Client --> AG[API Gateway\nRate Limiter + Auth]

    AG --> US[URL Service]
    AG --> RS[Redirect Service\nStateless × N pods]

    US --> IDG[Snowflake\nID Generator]
    US --> DB_P[(Primary DB\nPostgreSQL)]

    RS --> LC[Local Cache\nCaffeine]
    RS --> RC[(Redis Cluster)]
    RC -->|miss| DB_R[(DB Read Replicas)]
    DB_R --> DB_P

    RS -->|async| KF[Kafka\nclick-events]
    KF --> AC[Analytics Consumer]
    AC --> ADS[(Analytics Store\nRedshift + S3)]

Technology Stack

Layer Technology
API Gateway AWS API Gateway / Kong / NGINX
URL Service Spring Boot / Node.js
Redirect Service Spring Boot / Go (latency-optimized)
ID Generator Twitter Snowflake / Boundary Flake
Cache (local) Caffeine
Cache (distributed) Redis Cluster
Primary Database PostgreSQL / DynamoDB
Analytics Queue Apache Kafka / AWS Kinesis
Analytics Store Redshift + S3 / ClickHouse
CDN CloudFront / Cloudflare
Monitoring Prometheus + Grafana / CloudWatch
Deployment Kubernetes / AWS ECS

Key Trade-Offs

Decision Option A Option B Choice & Reason
Redirect type 301 Permanent 302 Temporary 302 — preserves analytics; browser won't cache
Short code generation UUID Base62 + Snowflake Base62 — compact, URL-safe, no collisions
Analytics write Synchronous Async via Kafka Async — never slow down the redirect critical path
Database SQL (PostgreSQL) NoSQL (DynamoDB) Either works; DynamoDB if fully managed scale needed
Caching Redis only Redis + local cache Both — local cache absorbs hot URL traffic
Consistency Strong everywhere Eventual for analytics Eventual for analytics; strong for URL creation

Common Interview Mistakes

  • ❌ Starting with the database schema before gathering requirements
  • ❌ Forgetting that this is a 100:1 read-heavy system — design for reads first
  • ❌ No caching layer — the DB cannot handle 115K redirects/second alone
  • ❌ Writing analytics synchronously on the redirect path
  • ❌ Using sequential IDs — predictable and enumerable
  • ❌ No collision strategy for custom aliases
  • ❌ No discussion of 301 vs 302 and the analytics trade-off
  • ❌ No rate limiting or abuse prevention
  • ❌ No failure scenario discussion

Interview Questions

  1. How do you generate unique short codes at scale?
  2. Why is Base62 preferred over UUID or MD5?
  3. Why is this system read-heavy and how does that affect design?
  4. Walk me through a redirect from browser to destination.
  5. What is the difference between a 301 and 302 redirect?
  6. What happens when Redis goes down?
  7. How do you handle hot URLs that receive millions of hits?
  8. Why should analytics be written asynchronously?
  9. How would you shard the URL database?
  10. How do you prevent malicious or phishing URLs?
  11. How do you support URL expiration?
  12. How do you support custom aliases and handle conflicts?
  13. How would you scale the redirect service to 300K req/sec?
  14. How does consistent hashing help when adding shards?
  15. What are the trade-offs between exact click counts and approximate counts?

Summary

A URL Shortener is a classic system design problem that tests your ability to reason about:

Concern Solution
Unique short codes Snowflake ID → Base62 encoding
Fast redirects Stateless service + Redis cache + local Caffeine cache
Redirect semantics 302 (preserves analytics)
Analytics Async Kafka pipeline — never on the critical path
Scale Horizontal pod scaling + DB read replicas + sharding
Abuse prevention Rate limiting + URL validation + admin takedown
High availability Multi-region, Redis cluster, DB replicas, CDN

The core principle: Keep the redirect path fast, cached, stateless, and always available. Everything else is secondary.