Design a URL shortener: build it locallyLESSON 9.14b · 14 OF 22 IN CHAPTER
PART C / System design under constraints
Step 146 of 255
LESSON 9.14b · 14 OF 22 IN CHAPTERGUIDED READING

Design a URL shortener: build it locally

Build it locally

Deliver: Build endpoints to reserve a short name and redirect its visitors. Prevent two creators from owning the same name, and make expiry and blocking take effect.

Required behavior: POST /links accepts target, optional alias and expiry. Return 201 or 409. GET /{code} returns 302 with Cache-Control: no-store, or 410 after expiry. The management API requires ownership.

The required first milestone is a working local implementation of the behavior above. The numbered implementation steps define the scope. The cloud architecture is a later extension, not something the starter has already provisioned.

Get the code and run the supplied example

The code is in the public junior-to-staff repository. Install Git and Python 3.12+. No AWS account or Python packages are required for this first run. If you already have a checkout, use it and skip cloning.

git clone https://github.com/Soulful-Iris/junior-to-staff.git
cd junior-to-staff
python3 examples/architecture-starts/url_shortener.py

Supplied file: examples/architecture-starts/url_shortener.py. You can also read or download the source here (download file, source below).

Read the supplied code · url_shortener.py
read or download the source here · url_shortener.py
"""Local mechanism demonstration for url-shortener. No AWS resources are created."""
import sqlite3
c=sqlite3.connect(':memory:')
c.execute('CREATE TABLE links(code TEXT PRIMARY KEY,target TEXT,expires INTEGER)')
for target in ('https://example.com/one','https://example.com/two'):
    try:
        c.execute('INSERT INTO links VALUES (?,?,?)',('launch',target,100))
        print('201 reserved launch for',target)
    except sqlite3.IntegrityError:
        print('409 alias already owned')
cached=c.execute('SELECT target,expires FROM links WHERE code=?',('launch',)).fetchone()
for now in (99,100,101):
    print('now',now,('302 '+cached[0]) if now<cached[1] else '410 expired')

This program is a mechanism demonstration: it runs the small scenario in one process and prints the result. It is not an HTTP service, a complete application, or an AWS deployment. A successful run demonstrates this mechanism only. It does not establish the workload or failure guarantees of the application you will build.

Example output from the supplied run:

Generated IDs and timestamps may differ. Compare the state transitions and outcomes.

201 reserved launch for https://example.com/one
409 alias already owned
now 99 302 https://example.com/one
now 100 410 expired
… (more output follows)

Set up your implementation workspace

Create work/url-shortener/ in your checkout (or use a separate repository). Copy the supplied mechanism into that directory as mechanism.py, then extract its state transitions into functions you can call from your implementation. The record and module names below describe what you must implement. They are not a promise that files with those names already exist. Keep a README.md beside your implementation with its exact run commands and observed results.

Local components and state to implement

This table names the records, interfaces or decision inputs for your deliverable. Unless a name is explicitly linked to supplied source above, it is something you create. Implement the local state transitions first, then connect the HTTP, storage or worker boundaries required by the steps.

Record / module Key or interface Responsibility
links code → owner,target,expires_at,version Durable uniqueness authority.
request_results (owner,request_id) → payload hash and code Keeps random-code allocation stable after a lost response.
redirect.py / analytics.py resolve(code, now). Record_click(event_id) Separate resolving the mapping from counting an event.

Implement the assignment

1. Implement alias ownership

Use a unique database key or DynamoDB conditional put. Do not check then insert as two separate unprotected operations. For generated codes, retry a random-code collision with a fresh candidate. Persist the allocated code with the create operation’s identity.

2. Write the redirect path

Read target, expiry and version. Check the timestamp before constructing the response. Return 302 and Cache-Control: no-store. Ordinary CDN/browser redirect caching can outlive your expiry decision. Reject unsupported target schemes and restrict management changes to the owner.

3. Handle a hot campaign

Cache mapping data inside the resolver and coalesce concurrent misses per code. Maintain a blocklist whose maximum accepted age is five seconds for this exercise. If that authority is unavailable or too old, fail closed for redirects. Demonstrate expiry with a deliberately stale cached mapping.

4. Count clicks asynchronously

Emit events with a stable event ID and timestamp. Define whether failed event publication can undercount analytics. Redirect availability and complete click accounting are different promises. Aggregate duplicates by event ID inside a bounded replay window and report delayed counts explicitly.

Demonstrate the completed local result

01 · Try this input

Input / starting state
Run the starting program
Expected result
The first launch reservation wins. The second conflicts. A cached mapping returns 410 at time 100.

02 · Try this input

Input / starting state
Pause blocklist refresh
Expected result
Redirects stop once policy age exceeds five seconds.

03 · Try this input

Input / starting state
Slow the click consumer
Expected result
Redirect latency remains bounded while the visible analytics lag grows.

Handoff: In your implementation README, include the start command, one successful operation, the failure case above and the resulting stored state or decision. State which dependencies are simulated. Someone with a fresh checkout should be able to reproduce this without your chat history.