System Design for Beginners: How to Design a URL Shortener in 2026

Published: (March 23, 2026 at 01:06 AM EDT)
6 min read
Source: Dev.to

Source: Dev.to

System design interviews terrify most developers. The questions are open‑ended, the scope is massive, and there’s no single “right” answer.

But here’s the secret: every system‑design question follows the same framework. Once you learn it with a simple example, you can apply it to anything.

We’ll use the URL shortener — the most classic system‑design question — as our learning vehicle.

What Is System Design?

System design is the process of defining the architecture, components, modules, interfaces, and data flow for a system to satisfy given requirements.

In interviews you’re often asked to design systems such as:

  • URL shortener (bit.ly)
  • Twitter/X timeline
  • YouTube
  • Uber’s ride dispatch
  • Netflix video streaming

The System‑Design Framework (6 Steps)

1. Clarify Requirements
2. Estimate Scale
3. Define the API
4. Design the Data Model
5. Draw the High‑Level Architecture
6. Deep Dive into Components

Let’s apply each step to the URL shortener.

Step 1: Clarify Requirements

Functional requirements (what the system does)

  • Given a long URL, create a short URL (e.g., bit.ly/abc123).
  • Given a short URL, redirect to the original long URL.
  • Short URLs should expire after a configurable time (optional).
  • Users can create custom short URLs (optional).

Non‑functional requirements (how well it does it)

  • High availability – up 99.9 % of the time.
  • Low latency – redirects in

Takeaway: The system is read‑heavy, needs aggressive caching, and has moderate storage requirements.

Step 3: Define the API

POST /api/v1/urls
Request:
{
  "longUrl": "https://example.com/very/long/path",
  "expiresAt": "2027-01-01"
}
Response:
{
  "shortUrl": "https://bit.ly/abc123",
  "expiresAt": "2027-01-01"
}
GET /{shortCode}
Response: 301 Redirect to longUrl
DELETE /api/v1/urls/{shortCode}
Response: 200 OK

301 vs 302 redirect

  • 301 (Permanent) – browsers cache the redirect → fewer requests to our servers → cheaper.
  • 302 (Temporary) – browsers always hit our server → more accurate analytics.

Choose based on the product’s needs.

Step 4: Design the Data Model

Table schema (SQL example)

CREATE TABLE urls (
  short_code   VARCHAR(7)    PRIMARY KEY,
  long_url     VARCHAR(2048) NOT NULL,
  created_at   TIMESTAMP    DEFAULT NOW(),
  expires_at   TIMESTAMP,
  user_id      BIGINT,               -- nullable for anonymous users
  click_count  BIGINT       DEFAULT 0
);

CREATE INDEX idx_short_code ON urls(short_code);

The short_code is the primary key. Below are three common generation strategies.

Short‑Code Generation Strategies

Option 1 – Hash + Truncate

import hashlib

def generate_short_code(long_url: str) -> str:
    hash_value = hashlib.md5(long_url.encode()).hexdigest()
    return hash_value[:7]          # first 7 chars of MD5

Problem: hash collisions (different URLs → same short code).


Option 2 – Base62 Encoding of a Counter

BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

def encode_base62(num: int) -> str:
    if num == 0:
        return BASE62[0] * 7
    result = []
    while num > 0:
        result.append(BASE62[num % 62])
        num //= 62
    return ''.join(reversed(result)).zfill(7)

How it works: Atomically increment a counter in the DB, then encode. Guarantees uniqueness. ✅


Option 3 – Pre‑generated Keys (Key‑Generation Service)

  • Pre‑populate a keys_available table with millions of unique 7‑char strings.
  • When a short URL is needed, fetch a key, move it to keys_used.
  • Eliminates collision risk entirely.

Step 5: High‑Level Architecture

[Client]


[Load Balancer]

   ├─── [Write Service] ──── [Primary DB]
   │                               │
   └─── [Read Service] ──── [Redis Cache] ──── [Read‑Replica DB]

Write Path

  1. Client POSTs a long URL.
  2. Write service generates a short code (Base62 counter or KGS).
  3. Saves the mapping to the primary DB.
  4. Returns the short URL to the client.

Read Path

  1. Client GETs /{shortCode}.
  2. Read service looks up Redis first (≈ 99 % cache hit).
  3. On miss, query the read‑replica DB.
  4. Issue a 301/302 redirect.

Step 6: Deep Dive – Caching Strategy

With ~116 K reads/sec the database alone can’t keep up; Redis is essential.

import redis

cache = redis.Redis(host='localhost', port=6379)
CACHE_TTL = 3600          # 1 hour

def get_long_url(short_code: str) -> str | None:
    # 1️⃣ Try cache first
    cached = cache.get(f"url:{short_code}")
    if cached:
        return cached.decode()

    # 2️⃣ Cache miss → query DB
    long_url = db.query(
        "SELECT long_url FROM urls WHERE short_code = %s", short_code
    )

    # 3️⃣ Populate cache for future hits
    if long_url:
        cache.setex(f"url:{short_code}", CACHE_TTL, long_url)

    return long_url

Cache eviction policy: LRU (Least Recently Used). By the 80/20 rule, ~20 % of URLs receive ~80 % of traffic – cache that hot 20 %.

Common Follow‑Up Questions

Q: How do you handle expired URLs?

  • Lazy deletion: On read, check expires_at; if past, return 410 Gone.
  • Background job: Periodically scan the table and delete expired rows (or move them to an archive).

Q: How would you support custom aliases?

  • Validate the requested alias for uniqueness and profanity.
  • Store it as short_code directly; if a conflict occurs, return an error.

Q: How can you make the system globally distributed?

  • Deploy read replicas and Redis caches in multiple regions.
  • Use a geo‑DNS or anycast load balancer to route users to the nearest region.
  • Employ a consistent‑hashing scheme for key generation to avoid collisions across data‑centers.

Q: How would you collect analytics (click counts, referrers, etc.)?

  • Increment a click_count column asynchronously via a message queue (e.g., Kafka).
  • Store detailed events in a time‑series DB or data warehouse for later analysis.

Q: What if the short‑code space (7 chars, Base62) runs out?

  • Switch to 8‑character codes (62⁸ ≈ 2.18 × 10¹⁴ possibilities).
  • Or recycle unused/expired codes after a safe grace period.

TL;DR

  1. Clarify functional & non‑functional requirements.
  2. Estimate traffic, storage, and latency needs.
  3. Define a clean, versioned API.
  4. Model the data (URL ↔ short code) and decide on a generation strategy.
  5. Sketch a high‑level architecture with separate write/read services, a primary DB, read replicas, and a Redis cache.
  6. Dive into critical components (caching, expiration handling, analytics, global distribution).

Follow this framework and you’ll be able to tackle any system‑design interview – starting with the classic URL shortener. 🚀

ed URLs nightly

Q: How do you prevent abuse?

  • Rate limiting per IP (Redis sliding‑window counter)
  • Block malicious URLs (Safe Browsing API)
  • CAPTCHA for anonymous users

Q: How do you scale to 1 B requests/day?

  • Add more read replicas
  • Geographic distribution (CDN for redirects)
  • Consistent hashing for distributed cache

Q: Custom short codes?

  • Check uniqueness before saving
  • Separate table for reserved/custom codes

What Interviewers Are Actually Evaluating

  • Communication – Can you walk through your thinking clearly?
  • Trade‑offs – Do you understand why each decision was made?
  • Scale awareness – Do you estimate before building?
  • Depth – Can you go deep on at least one component when asked?

You don’t need the perfect answer. You need to drive the conversation, make reasonable choices, and explain your reasoning.

Practice Problems (Increasing Difficulty)

  1. URL Shortener (this article) – Start here
  2. Pastebin – Text sharing
  3. Rate Limiter – Classic API design
  4. Twitter Timeline – Fan‑out problem
  5. Design YouTube – Video storage + streaming
  6. Design Uber – Real‑time matching

Managing freelance projects and clients while studying system design?
Freelancer OS keeps everything organized — CRM, projects, income, all in Notion. €19 one‑time.

0 views
Back to Blog

Related posts

Read more »

How Slack Rebuilt Notifications 📣

Introduction At Slack, notifications are how teams stay in the loop, but they can also become overwhelming when not designed with intention. Our goal was to ma...

The Confident Soup

It is 2:14 AM. Production is degraded. You are hunting for the edge of the fire, but there is no edge. Forty minutes have passed. The code compiles, the tests a...