1. Functional & Non-Functional Requirements
Clarifying scope before drawing a single box
In an interview, clarify requirements before drawing an architecture. Treat the values below as scenario targets to discuss with the interviewer, not measured service performance.
Functional Requirements:
- URL Shortening: Given a long URL, generate a unique, short alias (e.g.
https://prepvisor.in/u/a8F9k2Z). - Redirection: When accessing a short URL, immediately redirect the user to the original long URL with high availability.
- Custom Aliases (Optional): Users should optionally pick a custom alias if available.
- Link Expiration (Optional): Short URLs expire after a default TTL (e.g., 2 or 5 years) or custom user timestamp.
For redirect status-code semantics, see HTTP Semantics (RFC 9110), Sections 15.4.2–15.4.3.
Non-Functional Requirements:
- Latency target (example): Set a target such as under 20 milliseconds and clarify how it is measured.
- Availability target (example): A scenario may specify 99.99% availability; this is a requirement, not a guarantee.
- Read/write workload (assumption): Explore a 100:1 to 500:1 read-to-write ratio as an input to the design.
- Non-Predictable URLs: Short links must not be easily guessable to prevent enumeration attacks.
2. Capacity Estimation & Back-of-the-Envelope Calculations
Quantifying traffic, storage, and memory bottlenecks
EXAMPLE / ASSUMPTION — The following arithmetic illustrates one hypothetical five-year workload. Replace every input with requirements agreed during the interview; these are not measured TinyURL or PrepVisor traffic figures.
Traffic Calculations:
- Write QPS: Assume 100 Million new short URLs generated per month:
$\text{Writes per second} = \frac{100,000,000}{30 \times 24 \times 3600} \approx 40 \text{ URLs/sec}$
$\text{Reads per second} = 40 \times 100 = 4,000 \text{ redirections/sec}$
- Peak Traffic: Assume a $2\times$ peak multiplier: Peak Read QPS = 8,000 QPS.
Storage Calculations (5 Years):
- Total URLs in 5 years = $100\text{M}/\text{month} \times 12 \times 5 = 6 \text{ Billion URLs}$.
- Assume average original URL size = 500 bytes; metadata + short key + timestamp = 100 bytes. Total per record = 600 bytes.
- Total Storage in 5 years:
$6\text{ Billion} \times 600 \text{ Bytes} = 3.6 \text{ Terabytes}$ (This estimate excludes replication, indexes, storage-engine overhead, backups, and growth headroom.)
Cache Estimation (illustrative 80/20 assumption):
- 20% of the URLs generate 80% of read traffic.
- Daily read requests = $4,000 \times 86,400 \approx 345 \text{ Million requests/day}$.
- Daily cache requirement (20% of daily reads):
$0.20 \times 345\text{M} \times 600 \text{ Bytes} \approx 41.4 \text{ GB RAM}$ (Payload estimate only: Redis object/allocator overhead, replication, fragmentation, and operational headroom are excluded. Validate deployment size.)
3. Key Generation Strategy: Base62 vs Hashing
Hash-derived tokens and pre-generated keys: collision trade-offs
A critical technical differentiator in this interview is how you generate the 7-character short token.
Option A: Hashing (MD5 / SHA-256) + Base62
- Hash long URL using MD5 $\rightarrow$ 128-bit hash $\rightarrow$ Base62 encode $\rightarrow$ take first 7 chars.
- Trade-off: Truncating a hash to a short token can produce collisions. The design needs collision detection and retry or another allocation strategy; the latency impact depends on implementation and workload.
Option B: Dedicated Key Generation Service (KGS) with Pre-Generated Keys (Recommended)
Using Base62 characters ([a-z, A-Z, 0-9]): $62^7 = 3,521,614,606,208 \approx 3.52 \text{ Trillion unique URLs}$ This is the theoretical token space before reserved values or operational constraints; it is not a forecast of capacity or lifetime.
- A standalone Key Generation Service generates random Base62 tokens offline and loads them in batches into memory.
- When an API server needs keys, it may request a block (for example, 10,000) from the KGS. A crash can orphan unused keys. Preventing duplicate allocation requires a uniqueness-enforced key space and exclusive, durable allocation coordination.
Base62 Encoding Utility in Javapublic class Base62Encoder {
private static final String ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final int BASE = ALPHABET.length(); // 62
public static String encode(long id) {
StringBuilder sb = new StringBuilder();
while (id > 0) {
sb.append(ALPHABET.charAt((int) (id % BASE)));
id /= BASE;
}
while (sb.length() < 7) {
sb.append('0'); // Pad to 7 characters
}
return sb.reverse().toString();
}
}
4. High-Level Architecture & End-to-End Flow
Connecting Load Balancer, Application Nodes, Redis Cache & Database
EXAMPLE / ASSUMPTION — This flow uses illustrative traffic proportions and response times, not measured production behavior.
Read Flow (illustrative read-heavy workload):
- User clicks
https://prepvisor.in/u/x8Kp2L9. - Request hits Route 53 (DNS) and resolves to an Application Load Balancer (ALB).
- ALB forwards to stateless Redirection API Workers.
- Worker checks Redis Cache by key
x8Kp2L9:
- Cache hit (assumed 90%+): Returns a 302 response; measure latency in the target deployment.
- Cache miss (assumed remainder): Queries a database replica, populates Redis, and returns a 302 response.
Write Flow (Creating Short URL - 1% of Traffic):
- Client POSTs long URL to
/api/v1/shorten. - Auth & Rate Limiter verifies client quota.
- Service grabs pre-allocated Base62 key from local worker memory (fetched from KGS).
- Inserts mapping into Primary Database and async-populates Redis.
- Returns short URL to candidate immediately.
5. Database Schema & Storage Engine Choice
SQL vs NoSQL trade-offs for 6 Billion rows
Because TinyURL does not require complex relational joins or multi-table ACID transactions, both NoSQL (DynamoDB/Cassandra) and Partitioned SQL (PostgreSQL) are valid choices.
Recommended Schema:
CREATE TABLE url_mappings (
short_key VARCHAR(7) PRIMARY KEY,
original_url VARCHAR(2048) NOT NULL,
user_id UUID NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP WITH TIME ZONE NULL
);
-- Index for expiration cleanup worker
CREATE INDEX idx_url_mappings_expires_at ON url_mappings(expires_at)
WHERE expires_at IS NOT NULL;
Sharding Strategy (one option for this example):
- Partition by a hash of the short key to distribute records across shards:
$\text{Shard ID} = \text{Murmur3}(\text{short\_key}) \pmod N$
- Hash partitioning tends to spread keys, but does not guarantee uniform load or prevent hot spots for every workload.
6. Single Point of Failure (SPOF) & Fault Tolerance
What interviewers look for when testing your senior architectural maturity
A common interview pitfall is presenting a happy-path architecture without disaster recovery.
- Redis Cache Crash:
- Under the illustrative 4,000-read-QPS workload, a cache failure can shift substantial read traffic to the database and risk overload.
- Mitigation: Deploy Redis in Master-Replica Sentinel with auto-failover, and implement circuit breakers in API workers to gracefully throttle if DB connection pools saturate.
- KGS Crash:
- A standby KGS with an explicit leader/lease or allocation-coordination protocol can support failover; recovery time depends on the design.
- Database Write Master Failure:
- A database failover process may promote a replica. Recovery time and read availability depend on the database, replication mode, and application behavior; set and test a recovery objective.
References & Further Reading
Frequently Asked Questions
Why use Base62 instead of Base64 for short URLs?
Base64 includes characters like '+' and '/', which carry special meanings in URL query strings and path segments and require percent-encoding (%2B, %2F). Base62 consists strictly of alphanumeric characters [a-z, A-Z, 0-9], making it safe for all browsers, SMS, and messaging apps.
Should TinyURL return HTTP 301 or HTTP 302 status code?
HTTP 301 means the resource has moved permanently and is heuristically cacheable unless cache controls say otherwise. HTTP 302 means the move is temporary. Neither status alone guarantees analytics behavior: caching directives, clients, and the service's request flow affect whether later requests reach the origin. See RFC 9110, Sections 15.4.2–15.4.3.
How do you handle expired short links efficiently?
For an illustrative large dataset, lazy expiry checks plus periodic cleanup are one option. On access, verify the timestamp, return the chosen expired-link response, and enqueue cleanup if appropriate.
How do you prevent two users from receiving the same short URL?
A KGS can allocate pre-generated unique Base62 tokens to workers. Uniqueness depends on how the service creates, reserves, and allocates keys; no collision guarantee follows from the pattern alone.
How do you protect the service from malicious spam and DDoS?
Implement Token Bucket rate limiting per IP address / API key, integrate Google Safe Browsing API on the write path to block phishing URLs, and enforce CAPTCHA on unauthenticated link creation.
How can you estimate storage for a URL shortener?
Multiply the assumed record count by an estimated record size, then account separately for indexes, replication, backups, and storage-engine overhead. For example, 6 billion records at an assumed 600 bytes each yield 3.6 TB before those additional costs; this is scenario arithmetic, not a measured deployment size.