Thuta Learning
ProjectsProgrammingintermediate

Project: Build a URL Shortener

What you'll walk away with

  • Explain the core ideas behind Project: Build a URL Shortener
  • Study the sample diagram/code and analyze its trade-offs
  • Apply the technique correctly to the Tutorial Platform and production scenarios

Build the mental model

A URL shortener needs a way to turn each long URL into a short, unique code, and a way to map that code back to the original URL. The naive approach — hashing the long URL (e.g. MD5, truncated) — looks appealing because it's deterministic, but hashes collide: two different URLs can produce the same short hash, and every write then needs a collision check plus a retry loop, adding latency and complexity to the write path. A cleaner design sidesteps collisions entirely: assign each new URL the next value of an auto-incrementing counter, then base62-encode that integer (digits + upper + lower case letters) to get a short, URL-safe, guaranteed-unique code — no collision detection needed, ever. Storage is a simple key-value mapping from short_code to long_url; at small scale a hash map suffices, and a real deployment swaps it for a database or key-value store without changing the algorithm. The other key insight is traffic shape: redirects (reads) vastly outnumber new shortenings (writes) in practice, so the resolve path is what needs to be fast and cheap — which is exactly why caching hot short codes in front of the store matters so much here.

Connect it to a real scenario

This is a scaled-down version of how the Tutorial Platform could generate shareable short links for individual lessons — instead of pasting a long /tutorials/rust/ownership-and-borrowing URL into a chat or a certificate, a learner gets a short code like thta.io/aB3xZ. The counter-plus-base62 approach maps directly onto the platform's existing auto-incrementing lesson IDs in the database, so no new ID scheme is needed. And because lesson links get shared and clicked far more than they get created, the read-heavy traffic pattern here is the same one that would justify adding a cache layer in front of the platform's real link-resolution endpoint.

Try the working example

python
import string

class URLShortener:
    ALPHABET = string.digits + string.ascii_lowercase + string.ascii_uppercase  # base62

    def __init__(self):
        self._next_id = 1
        self._code_to_url = {}   # short_code -> long_url
        self._url_to_code = {}   # long_url -> short_code (avoid duplicate codes for same URL)

    def _encode_base62(self, num: int) -> str:
        if num == 0:
            return self.ALPHABET[0]
        digits = []
        base = len(self.ALPHABET)
        while num > 0:
            num, rem = divmod(num, base)
            digits.append(self.ALPHABET[rem])
        return "".join(reversed(digits))

    def shorten(self, long_url: str) -> str:
        if long_url in self._url_to_code:
            return self._url_to_code[long_url]
        short_code = self._encode_base62(self._next_id)
        self._next_id += 1
        self._code_to_url[short_code] = long_url
        self._url_to_code[long_url] = short_code
        return short_code

    def resolve(self, short_code: str) -> str:
        if short_code not in self._code_to_url:
            raise KeyError(f"Unknown short code: {short_code}")
        return self._code_to_url[short_code]


if __name__ == "__main__":
    shortener = URLShortener()
    urls = [
        "https://thutalearning.com/tutorials/rust/ownership-and-borrowing",
        "https://thutalearning.com/tutorials/system-design/project-url-shortener",
        "https://thutalearning.com/tutorials/elasticsearch/full-text-search",
    ]
    codes = [shortener.shorten(u) for u in urls]
    for url, code in zip(urls, codes):
        print(f"{url} -> {code}")
    for code in codes:
        print(f"{code} -> {shortener.resolve(code)}")
You should see
Prints each long URL alongside the short code it was assigned (1, 2, 3 base62-encoded), then prints each code resolving back to its original long URL.

5-minute try-it

Add a `custom_alias` option to `shorten()` so a caller can request a specific short code (e.g. 'my-course') instead of an auto-generated one, and make sure it still raises an error if that alias is already taken.

One important caution

Using a hash of the URL as the short code without a collision-detection retry loop: two different long URLs can hash to the same short string, silently overwriting one mapping with another.

Storing the counter only in memory (as this demo does) with no persistence: a server restart resets `_next_id` to 1 and starts handing out short codes that collide with ones already given out before the restart.

Wikipedia — URL shorteningSystem Design

Easy traps

  • Using a hash of the URL as the short code without a collision-detection retry loop: two different long URLs can hash to the same short string, silently overwriting one mapping with another.
  • Storing the counter only in memory (as this demo does) with no persistence: a server restart resets `_next_id` to 1 and starts handing out short codes that collide with ones already given out before the restart.
  • Validate your load/traffic assumptions before applying a design decision directly to a production system.

Exercise

Add a `custom_alias` option to `shorten()` so a caller can request a specific short code (e.g. 'my-course') instead of an auto-generated one, and make sure it still raises an error if that alias is already taken.

You'll know it worked when: Prints each long URL alongside the short code it was assigned (1, 2, 3 base62-encoded), then prints each code resolving back to its original long URL.

Project: Build a URL Shortener | Thuta Learning