Free preview

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

BlockRole
DatabaseStore the mapping between short and long URLs
SequencerGenerate unique IDs for new short URLs
Load balancersDistribute requests across available servers
CacheStore frequently accessed URL mappings
Rate limitersPrevent 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)
ParameterDescription
api_dev_keyA registered account's unique identifier — used to track activity and control services
original_urlThe long URL to be shortened
custom_aliasOptional user-defined short URL
expiry_dateOptional expiration date
url_keyThe 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.

Enjoying the preview?

Create a free account to unlock the rest of this course, the in-browser judge, and live AI mock interviews.

Sign up free to continue