An original guide on engineering a link shortening service like TinyURL from scratch. Discover the architecture, core logic, and scaling strategies for a production-ready system.
A solid design starts with clear goals and an understanding of the expected load.
At its core, a URL shortener transforms a lengthy web address into a much shorter, more manageable link. When a user accesses this condensed link, the service seamlessly redirects them to the original destination.
100M / (30d × 24h × 3600s) ≈ 40 writes/sec
40 writes/sec × 100 = 4,000 reads/sec
100M × 12m × 5y × 500 bytes ≈ 3 Terabytes
Primary Takeaway: The system is overwhelmingly read-dominant. Optimizing for fast reads through caching is the most critical performance consideration.
Let's define our service's contract (API) and outline the flow of data.
POST /api/v1/shorten
Body: { "long_url": "...", "custom_alias": "..." }
Response: { "short_url": "..." }
GET /{short_url}
HTTP Status: 302 Found
Header: Location: {long_url}
The system's operation can be separated into two primary sequences: the "write path" for creating links and the "read path" for handling redirects.
1. A user sends the long URL to our service.
2. A dedicated ID Generator produces a globally unique number.
3. This number is encoded into a short string (the path for our URL).
4. This new mapping (short string → long URL) is written to the Database.
1. A user clicks a short link, hitting our service.
2. The service immediately checks a high-speed Cache.
3. If it's a popular link (a cache hit), we redirect instantly. If not (a cache miss), we query the main Database.
4. The service sends an HTTP redirect back to the user's browser.
A robust method for creating non-colliding, non-sequential, short keys is to pair a distributed unique ID generator with Base62 encoding. Think of it as converting a very large number into a compact, URL-friendly string.
1. Request a unique 64-bit integer from a service like Snowflake (e.g., `1000123456789`).
2. Treat this integer as a number in base-10 and convert it to base-62.
Our "digits" are now the 62 characters: `0-9`, `a-z`, and `A-Z`.
3. The result of this conversion is a short, URL-safe string like `p4J3j2a`.
✓ Strengths:
✗ Weakness:
Instructs the browser to remember this redirect forever. On future visits, the browser won't even contact our server.
✓ Benefit: Fastest possible experience for repeat visitors.
✗ Drawback: We lose all visibility. No analytics, no ability to edit the link.
Tells the browser that the link is only valid for now. The browser must check with our server every time.
✓ Benefit: Enables click tracking, A/B testing, and editing the destination.
✗ Drawback: Incurs a small latency penalty for the extra network hop.
Our Choice: A 302 redirect is standard for this use case because the ability to gather analytics is a core business requirement.
While a simple key-value store is a natural fit, a standard relational database also works perfectly. The key is efficient retrieval. We must ensure the `short_url` column is the primary key and indexed for lightning-fast lookups.
Table: url_mappings
├── short_url VARCHAR(7) PRIMARY KEY
├── long_url TEXT
├── created_at TIMESTAMP
└── expires_at TIMESTAMP NULLABLEWith reads dominating traffic, a cache is non-negotiable. Storing `short_url` -> `long_url` pairs in an in-memory cache like Redis will serve the majority of requests without touching the database. A "Least Recently Used" (LRU) policy will ensure the most popular links stay in the cache.
To protect our service from malicious actors or runaway scripts, we must limit how many URLs a single user or IP address can create in a given time frame. This prevents system overload and ensures fair usage.
Recording every click can slow down redirects. To solve this, the redirect handler should immediately send the user on their way and then, in a "fire-and-forget" manner, publish the click event to a message queue (e.g., Kafka). Downstream services can then process these events for analytics without impacting user-facing latency.
When our data (terabytes) outgrows a single machine, we must partition it (shard). By splitting the data based on a hash of the short URL, we can distribute storage and query load across an entire fleet of database servers, allowing for near-infinite horizontal scaling.
Practice designing a URL Shortener with an AI interviewer and get instant feedback.