List the key functional requirements for the system (Ask the AI for hints if stuck)...
1) user should be able to input a long url and get a short url in exchange
2) user should be able to see the history of old inputs
3) redirection should happen with a 302 http method
4) user is able to get the long url associated with the generated short url
List the key non-functional requirements (performance, scalability, reliability, etc.)...
1) system should be able to handle hot spots for a viral short url 10M clicks per second
2) availability and partition tolerance is important and consistency can be eventual
3) Low redirect latency p99 < 100ms
4) high availability 99.9% (Three Nines)
Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
1M DAUs and let's say 1% are actively using to create short url and 10% people are redirecting at a time means 100k reads at a time max which means that we might not be needing a write heavy but ready should be taken care of
the qps would be 10k creates a day roughly 10 write a minute
reads would be 1m readys a day 12 reads a second
assuming each row is a short code + long url and some metdata 500 bytes
10K per day we need 5MB a dat 2 GB per year
for growth projections we can use a 2x multiplier
Define the APIs expected from the system. This is your chance to analyze and define the read and write paths so that you can come up with the high-level design...
GET API
/?longUrl=https://codemia.io/system-design/designing-a-simple-url-shortening-service-a-tinyURL-approach
Response 201 OK
{
shorturl: https://test.io/xyrbuw
}
GET /{shortCode} → 302 Found Location: https://original-long-url.com/...
Describe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
This follows a classic CQRS-like split (Command Query Responsibility Segregation):
URL (the core mapping)This is the central entity — the short code → long URL mapping.
| Attribute Type Notes | ||
short_code (PK) | String | The unique generated ID (e.g., aZ3kT9), the primary lookup key |
long_url | String/Text | The original destination URL |
created_by | String/UUID (FK → User, optional) | Who created it, if user accounts exist |
created_at | Timestamp | Creation time |
expires_at | Timestamp (nullable) | Optional TTL for the short link |
is_active | Boolean | Soft-delete/disable flag |
custom_alias | Boolean | Whether this was a user-chosen code vs. auto-generated |
Access pattern this entity serves: the read path does a single point lookup by short_code — this is the hottest, most latency-sensitive query in the whole system.
ClickAnalytics (per-click event log)Captures each redirect event, feeding the async analytics pipeline shown in the diagram.
| Attribute Type Notes | ||
event_id (PK) | UUID | Unique event identifier |
short_code (FK → URL) | String | Which URL was accessed |
timestamp | Timestamp | When the click happened |
ip_hash | String | Hashed/anonymized IP for geo stats, privacy-preserving |
user_agent | String | Browser/device info |
referrer | String (nullable) | Where the click came from |
country / city | String (nullable) | Derived from IP geolocation |
Access pattern: write-heavy, append-only, almost never updated, queried later in aggregate (e.g., "clicks per day," "top referrers") rather than by individual row lookup.
User (optional, if the system supports accounts)| Attribute Type Notes | ||
user_id (PK) | UUID | Unique user identifier |
email | String | Login/contact |
created_at | Timestamp | Account creation |
api_key | String (hashed) | For programmatic access, tied to the API Gateway's auth/rate-limit layer |
IDAllocation (Zookeeper-adjacent, if modeled explicitly)Not always a persisted "entity" in the traditional sense — Zookeeper here is more of a coordination service — but conceptually:
| Attribute Type Notes | ||
node_id / range_start / range_end | Integer | Each generator instance is handed a block of IDs to consume locally, avoiding a synchronized call to Zookeeper on every single request |
User (1) ────────< (many) URL
URL (1) ────────< (many) ClickAnalytics
short_code → long_url is a standalone lookup, which is intentional and important (see below).URL tableReasoning based on the diagram's access patterns:
short_code, return long_url, as fast as possible." There's no need for joins, complex filtering, or relational integrity here — this is the textbook use case for a key-value store, where short_code is the partition key.short_code hash, whereas a single SQL leader (as shown in the diagram — "leader and write DB") becomes a write bottleneck and eventually needs manual sharding logic bolted on.If the ID generation via Zookeeper already guarantees strict uniqueness before insert, a single relational leader database with read replicas (as literally drawn) works too, since:
In short: NoSQL is the "purist" choice for pure key-value redirect lookups at extreme scale; a well-cached SQL leader/replica setup (exactly as drawn) is a perfectly reasonable, simpler alternative if scale is more moderate or relational features like per-user URL listings matter.
ClickAnalytics| Entity Access Pattern Recommended Store | ||
URL | High-read, point lookup by key, low write volume | Key-value NoSQL (DynamoDB/Cassandra) or cached SQL with replicas |
ClickAnalytics | Very high write volume, append-only, aggregate queries later | Wide-column NoSQL / streaming + data warehouse |
User | Low volume, relational (joins possible: user → their URLs) | SQL (traditional relational fits fine, no scale pressure) |
The core justification throughout: let the query pattern drive the storage choice — the diagram already hints at this by separating cache/read-replicas (optimized for lookup speed) from the async analytics pipeline (optimized for write throughput), so the data model should mirror that same split rather than forcing everything into one database type.
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
2. Caching Redis sits in front of the read DB; CDN at the edge. A viral link serves from CDN → Redis → replica, so origin isn't hit. On miss/TTL expiry: fetch from read replica, serve the 302, write back to cache. On delete/update: purge the Redis key + CDN invalidate. Eviction: LRU with a TTL to balance freshness vs. memory.
3. Partitioning Shard by hash(short_id) % N → a redirect routes to one shard, no scatter. Consistent hashing spreads traffic evenly. Hot IDs don't overload a shard because cache/CDN absorb the read load before the shard sees it.