pythonadvanced90 minutes

Build a Scalable URL Shortener Service in Python

Create a mini-project to develop a URL shortener service that generates unique short URLs, supports custom aliases, and includes analytics features such as click tracking and expiration dates.

Challenge prompt

Create a Python-based URL shortener service that supports the following features: 1) Generating unique shortened URLs from original long URLs; 2) Allow users to specify custom aliases for the shortened URLs where possible; 3) Store all URL mappings and track the number of times each short URL is accessed; 4) Implement optional expiration dates after which the shortened URLs become inactive; 5) Provide functions to resolve shortened URLs back to their original URLs and retrieve click statistics. Your implementation should optimize for scalability and handle potential collision in alias generation gracefully.

Guidance

  • Use hashing or base encoding techniques to generate compact and unique short codes.
  • Maintain a persistent mapping (in-memory dictionary or file/db-based for persistence) between short codes and original URLs, along with metadata such as click counts and expiration.
  • Ensure thread-safe operations if you simulate concurrent accesses or updates.
  • Implement clean error handling for cases such as alias conflicts, expired links, or non-existent short URLs.

Hints

  • Consider using base62 encoding (alphanumeric characters) for compact URL code generation.
  • For custom aliases, check if the alias already exists before creating a new short URL.
  • Store timestamps to manage expiration and periodically clean up expired URLs from storage.

Starter code

import time

class URLShortener:
    def __init__(self):
        self.url_map = {}
        self.clicks = {}
        self.expirations = {}
        self.counter = 1
        self.alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

    def _encode(self, num):
        # encode integer to base62 string
        if num == 0:
            return self.alphabet[0]
        arr = []
        base = len(self.alphabet)
        while num:
            num, rem = divmod(num, base)
            arr.append(self.alphabet[rem])
        arr.reverse()
        return ''.join(arr)

    def shorten(self, original_url, custom_alias=None, expire_in_seconds=None):
        pass  # Implement this

    def resolve(self, short_url):
        pass  # Implement this

    def get_clicks(self, short_url):
        pass  # Implement this

    def _is_expired(self, short_url):
        pass  # Implement this

Expected output

short_url = shortener.shorten('https://www.example.com/some/long/path') print(short_url) # e.g. "b9" print(shortener.resolve(short_url)) # 'https://www.example.com/some/long/path' print(shortener.get_clicks(short_url)) # 1 after resolve call custom_short = shortener.shorten('https://www.example.org', custom_alias='exOrg') print(custom_short) # 'exOrg' print(shortener.resolve('exOrg')) # 'https://www.example.org'

Core concepts

Hashing and encoding techniquesData structures and mappingsState management and persistenceConcurrency and collision handling

Challenge a Friend

Send this duel to someone else and see if they can solve it.