Date: 2026-06-05
Time: 14:05
url-shortener/url_shortener.pyThis file is a self-contained, in-memory URL shortening service — the kind you'd sketch on a whiteboard in a system design interview and then implement as a working prototype. It owns the full lifecycle of a shortened URL: creation (with two generation strategies), redirection with click tracking, analytics aggregation, expiration, and deletion. Everything lives in a single process with no external dependencies — the storage is a Python dict, not a database.
Base62A stateless utility for encoding integers into URL-safe strings using [0-9a-zA-Z]. The encode/decode pair is bijective — every positive integer maps to a unique string and back. This is the alphabet that turns a monotonic counter or hash digest into a short code like 0000001 or a3Bf9x2.
URLEntryA dataclass holding everything known about a single shortened URL: the mapping itself (shortcode → longurl), metadata (creation time, expiration, creator), and analytics state (clickcount, clickhistory). The click_history list is bounded to the last 1000 events — an implicit design decision that trades completeness for memory.
AnalyticsReportA read-only view computed on demand from a URLEntry. Aggregates clicks per day, recent clicks (last 10), and referrer frequency. This is a projection, not stored state — it's recomputed on every get_analytics call.
URLShortenerThe service itself. Constructor parameters control the domain prefix, default TTL, short code length, generation strategy ("counter" vs "hash"), and rate limit threshold. The two strategies reflect a real design tradeoff:
generatecounter_code): Monotonically incrementing integer → base62. Guaranteed unique on first attempt. Predictable (sequential), which leaks information about creation order and total URL count.generatehashcode): SHA-256 of the long URL, truncated to codelength base62 characters. Deterministic for a given URL (same input → same hash), but collisions are possible since we're truncating a 256-bit hash to ~41 bits (7 base62 chars). Handles collisions by appending a null-byte-separated attempt counter and rehashing.Strategy pattern for code generation — the strategy field selects between counter and hash at construction time, with dispatch in shorten() via a simple if/else. No formal interface; it's a lightweight variant.
Injectable time — every method that touches timestamps accepts an optional current_time parameter. This avoids mocking time.time() in tests and makes the service fully deterministic when you control the clock. This is a common pattern in SDI implementations where you need to test expiration and rate limiting.
Sliding window rate limiting — checkrate_limit keeps a list of timestamps per creator, prunes entries older than 60 seconds, and rejects if the window is full. This is a fixed-window approximation (the window slides on each call, but the pruning is eager, not lazy).
Bounded history — click_history is capped at 1000 entries with tail retention ([-1000:]). This prevents unbounded memory growth per URL but means early click data is silently dropped.
Imports: All stdlib — hashlib for SHA-256 in hash strategy, time for wall-clock defaults, collections.defaultdict for sparse counters, dataclasses for structured data, datetime for UTC day formatting in analytics, urllib.parse for URL validation.
Imported by: testurlshortener.py — the test suite is the only consumer. This module has no downstream dependents in the repo.
A typical lifecycle:
1. shorten(longurl) — Validates the URL (scheme + domain), checks rate limits if a creatorid is provided, generates a short code via the configured strategy, wraps it in a URLEntry, stores it in _urls[code], and returns the full short URL string (domain/code).
2. redirect(shortcode) — Looks up the code in urls, checks expiration against the current time, increments the click counter, appends a click event (with timestamp and optional referrer), trims history if it exceeds 1000 entries, and returns the original long URL. Returns None for missing or expired entries — no exception, no distinction between "never existed" and "expired."
3. getanalytics(shortcode) — Iterates the click history to compute per-day counts and referrer frequencies. Returns None for unknown codes.
http and https schemes are accepted. The domain must contain a dot (rejects localhost, bare hostnames).ValueError.rate_limit (default 60) shortening operations per creator per 60-second sliding window.codelength characters, so counter=1 → "0000001".urls until accessed via redirect or filtered out by listurls. No background reaper.All validation errors raise ValueError with descriptive messages — invalid URLs, bad custom aliases, rate limit violations, duplicate aliases. These are the only exceptions the module produces.
redirect and get_analytics return None for missing/expired entries instead of raising — the caller is expected to check. delete returns a boolean. There's no error type hierarchy or custom exceptions; ValueError is the single error channel.
Notably, there's no error handling for the hash collision loop — if the keyspace were exhausted, generatehash_code would loop forever. This is fine for an SDI prototype but would need a max-attempts guard in production.
url-shortener/testurlshortener.py — See how expiration, rate limiting, and both strategies are exercised in testsurl-shortener/urlshortener.py:generatehashcode — Understand the collision resolution loop and why SHA-256 truncation makes collisions plausible at ~3.5T codesurl-shortener/plan.md — Design decisions and tradeoffs documented before implementationcounter-vs-hash-tradeoffs — When predictability (counter) vs determinism (hash) matters in URL shortening — hash gives you deduplication for free, counter gives you guaranteed O(1) generationunique-id-generator/uniqueidgenerator.py — Compare ID generation strategies — the unique ID generator likely tackles similar encoding/distribution problems in a distributed contexturl-shortener-two-strategies — URLShortener supports two short code generation strategies: "counter" (monotonic base62) and "hash" (truncated SHA-256 with collision retry), selected at construction timeurl-shortener-expiration-lazy — Expired URLs are never eagerly removed from storage; expiration is checked at read time in redirect and list_urlsurl-shortener-click-history-bounded — Click history per URL is capped at 1000 entries; older events are silently dropped on each redirecturl-shortener-rate-limit-sliding-window — Rate limiting uses a per-creator sliding window of 60 seconds, pruned eagerly on each checkrate_limit callurl-shortener-no-dedup-counter — The counter strategy does not deduplicate: shortening the same long URL twice produces two different short codes