Building Blocks and API
In one line: the rate limiter here is not the usual afterthought. In a system this cheap to abuse, it is the component that decides whether the service survives.
Building blocks
| Block | Role |
|---|---|
| Database | Store the mapping between short and long URLs |
| Sequencer | Generate unique IDs for new short URLs |
| Load balancers | Distribute requests across available servers |
| Cache | Store frequently accessed URL mappings |
| Rate limiters | Prevent system abuse by limiting requests from a single source |
Plus servers for application logic and a base-58 encoder for readable strings.
The rate limiter is load-bearing here, not defensive
In most designs a rate limiter protects against abuse at the margins. In this one it protects the ID space, which is the system's scarcest resource.
Consider what an unlimited client can do. Each shortening request consumes one identifier permanently — the design does not reuse expired IDs (Lesson 2). So an attacker with a script can:
Exhaust short identifiers. Lesson 9 shows that IDs producing 6-character URLs number about 38 billion. A client at even modest rates burns through the readable end of the space, forcing everyone else onto longer URLs.
Claim the valuable custom aliases. Every dictionary word, brand name, and common phrase — squatted in bulk. The disadvantages section in Lesson 1 already names this: "popular custom URLs may also be unavailable if they are already taken."
Fill the database. At 500 bytes an entry, a sustained attack adds storage nobody asked for, and the entries live for five years.
Notice the asymmetry: a shortening request costs the attacker one HTTP call and costs the system a permanent identifier. That is exactly the shape that makes a resource cheap to attack.
The chapter's answer is right: rate-limit on the api_dev_key, using a fixed-window counter, which is sufficient because precision does not matter here — you are stopping bulk abuse, not shaving milliseconds.
When a request permanently consumes a finite shared resource, the rate limiter is a capacity control, not a security afterthought.
The sequencer as a building block is the design's real load-bearer
Lesson 1 noted the separation of uniqueness from readability. The block list makes it structural: a sequencer is a named, reusable component, covered in its own chapter of this course.
Worth recalling what it provides: globally unique IDs without per-request coordination. Servers are allocated ranges and mint IDs locally, so generating an identifier does not require a round trip to a central authority — which is what keeps write latency low and removes a single point of failure.
That property is why the design can claim, in Lesson 11, that "the regular short URL generation process ensures no duplication in records" without any collision check. The uniqueness comes from the allocation scheme, not from checking.
A sequencer converts a distributed-agreement problem into a local one, and that is the single most valuable thing it does.
The API
shortURL(api_dev_key, original_url, custom_alias=None, expiry_date=None) redirectURL(api_dev_key, url_key) deleteURL(api_dev_key, url_key)
| Parameter | Description |
|---|---|
api_dev_key | A registered account's unique identifier — used to track activity and control services |
original_url | The long URL to be shortened |
custom_alias | Optional user-defined short URL |
expiry_date | Optional expiration date |
url_key | The short URL to resolve or delete |
redirectURL takes an api_dev_key, and it cannot
This is the API's clearest problem.
redirectURL(api_dev_key, url_key) requires a developer key on redirection. But redirection is what happens when anyone clicks a short link — someone who received it in a message, saw it on a poster, or found it in an email. They have no key, no account, and no relationship with the service.
Redirection is the public operation. It is a browser following a URL, and the request is simply:
GET /27qMi57J -> 302 Found, Location: https://the-long-url...
No authentication is possible, and none should be required.
That has a consequence for Lesson 4's rate limiter, though: if reads carry no key, they cannot be limited per key. They must be limited by IP or by short-code, which is a different and harder problem — and it matters, because reads outnumber writes 100:1.
Authenticate the operation that consumes resources; leave the public operation public. Writes take a key; reads take nothing.
What the API does not have
Three gaps worth naming.
No update endpoint, despite update being a stated functional requirement in Lesson 2. Only create, redirect, and delete are specified.
No idempotency key on shortURL. A client that retries after a timeout gets a second short URL for the same long URL — unless the server deduplicates, which Lesson 11 says it does by looking up the long URL first. So deduplication substitutes for idempotency here, which works but is a weaker guarantee: it makes retries safe only because the operation happens to be naturally deduplicable.
No analytics endpoint. Click tracking is the primary commercial reason URL shorteners exist, and it is also why Lesson 2's redirect must be a 302 rather than a 301. The design supports it structurally and never mentions it.
Splitting read and write services is worth doing explicitly, because the two scale on completely different signals — roughly a thousand to one. The read path needs replicas and cache; the write path needs a counter and one row insert.
Key takeaway
The rate limiter is load-bearing rather than defensive — a shortening request costs an attacker one HTTP call and costs the system a permanent identifier, so it protects the ID space, the valuable custom aliases, and the database. Limiting per api_dev_key with a fixed window is right, because precision does not matter for bulk abuse. The sequencer converts a distributed-agreement problem into a local one, which is why the design needs no collision check at all. And redirectURL cannot take an API key — redirection is the public operation, performed by anyone who clicks a link — which means reads must be limited by IP or short-code instead, a harder problem given they outnumber writes 100:1.
Next: choosing the database.