Lessons
1 · System Design Foundations
2 · APIs and Communication
3 · Capacity Estimation
4 · Core Building Blocks
5 · Databases
- 12Relational Databases and Data Modeling for System Design
- 13Database Indexes and Query Performance: How to Make Reads Fast
- 14Transactions, Isolation Levels and Concurrency Control: Preventing Race Conditions
- 15Database Replication: Primary-Replica, Read Replicas, Lag and Failover
- 16Database Sharding and Partitioning: How It Works and When You Need It
- 17SQL vs NoSQL: Data Models and How to Choose the Right Database
6 · Distributed Systems
- 18Why Distributed Systems Are Hard: Partial Failure, Timeouts and Clocks
- 19CAP Theorem and Consistency Models Explained Without the Myths
- 20Idempotency, Retries and Message Delivery Guarantees
- 21Distributed Transactions: Two-Phase Commit, the Outbox Pattern and Sagas
- 22Distributed Locks, Leases, Leader Election and Consensus
7 · Reliability and Operations
8 · Architecture and Security
9 · Interview Method and Foundation Case Studies
10 · Real-World Case Studies
- 33Design a Notification System: Email, SMS and Push at Scale
- 34Design a Chat System Like a Messaging App
- 35Design a News Feed: Fan-Out, Ranking and the Celebrity Problem
- 36Design Search Autocomplete (Typeahead)
- 37Design a Distributed Job Scheduler
- 38Design a Ticket Booking System Without Overselling
- 39Design a Payment System: Idempotency, Ledgers and Reconciliation
11 · Large-Scale Case Studies
- 40Design a Video Streaming Platform: Upload, Transcoding and Adaptive Streaming
- 41Design a Ride-Hailing System: Location, Matching and Real-Time Updates
- 42Design a Food Delivery Platform: Orders, Restaurants and Delivery Partners
- 43Design an E-Commerce System: Catalog, Cart, Checkout, Inventory and Flash Sales
- 44Design a Web Search Engine: Crawling, Inverted Indexes and Ranking
12 · Interview Readiness
Lesson 30 · Interview Method and Foundation Case Studies
Design a URL Shortener (and Pastebin): A Complete Walkthrough
The classic first system design problem, worked end to end with the course's method: requirements, estimates, APIs, data model, a simple baseline, short-code generation and collisions, 301 versus 302 redirects, caching a read-heavy path, expiry, analytics off the critical path, sharding, failures, abuse prevention, and the Pastebin variant with object storage.
For
SDE-1 candidates working through their first full design, and anyone who wants to see the whole interview method applied to a read-heavy system.
Level
Before you start
Read The System Design Interview Framework (article 29), plus Back-of-the-Envelope Estimation (06), Caching (09), Relational Databases and Data Modeling (12) and Database Indexes (13). Object storage (article 10) is used in the Pastebin section.
On this page
Key takeaways
- A URL shortener is a small, extremely read-heavy key-value lookup; the design effort goes into the redirect path, not the write path.
- Random 7-character base-62 codes give trillions of possibilities, unguessable links and rare collisions handled by a unique constraint; counter-based codes avoid collisions but are enumerable.
- A cache in front of the database, with write-through on creation, serves most redirects in milliseconds and keeps new links working immediately.
- Analytics belongs on an asynchronous click stream, never in the redirect's critical path; the redirect status code (302 versus 301) decides whether you can count and change links.
- Abuse (phishing and malware links) is the real operational risk, so creation needs rate limits, URL checks and a takedown path.
"Design a URL shortener" is often the first system design question people meet, and it is easy to underestimate. The functional core fits in one sentence: turn a long URL into a short code, and redirect anyone who visits the code. Yet a good answer touches estimation, key generation, caching, consistency, failure handling and abuse, and the same reasoning applies to any read-heavy lookup service.
This is the first full case study of the course. Every case study from here on uses the same fourteen sections, which follow the seven phases from article 29. Read it once straight through, then try the problem yourself with a 45-minute timer before rereading.
What you will learn
#- How to scope a deceptively simple problem.
- How to estimate a read-heavy system and what the numbers imply.
- Three ways to generate short codes, and how to handle collisions.
- Why the choice between 301 and 302 redirects matters.
- How to scale the redirect path with caching, replicas and, eventually, sharding.
- How to add analytics and expiry without slowing redirects.
- How the design fails and how it is secured.
- How the same design becomes Pastebin.
1. The problem
#Users submit a long URL such as https://www.example.com/articles/2026/10/how-to-plan-a-budget-trip-to-kerala?ref=newsletter and receive a short link such as https://pix.ly/a8Kq2Zx. Anyone who opens the short link is redirected to the original URL. The service is public: anonymous users and signed-in users can create links, and links are shared widely on social media and in messages.
2. Clarify the requirements
#Questions worth asking (and assumed answers):
Question | Assumed answer | Design impact |
|---|---|---|
How many new links, and how many redirects? | 100 million new links a month; about 100 redirects per link on average | Read-heavy; sizes cache and storage |
Custom aliases ( | Yes, optional | Uniqueness check on user-chosen keys |
Expiry? | Optional expiry date; default: never | Expiry checks and cleanup |
Can links be edited or deleted? | Owners can delete; no editing of the target | Affects cache invalidation and redirect codes |
Analytics? | Click counts per link, available to owners within minutes | Asynchronous click pipeline |
Accounts? | Optional; anonymous creation allowed with stricter limits | Abuse controls |
Global users? | Yes | CDN or regional serving for redirects |
Functional requirements
- Create a short link for a long URL, optionally with a custom alias and an expiry time.
- Redirect a short link to its long URL.
- Let owners delete their links.
- Show owners click counts per link (minutes of delay acceptable).
Out of scope: link editing, detailed analytics dashboards, QR codes, team workspaces.
Non-functional requirements
Quality | Target |
|---|---|
Redirect latency | p99 under 50 ms at the server (excluding the network to the user) |
Availability | Redirects 99.99%: a broken short link in a printed poster cannot be fixed; creation 99.9% |
Durability | A created link is never lost |
Correctness | A code never points to the wrong URL; codes are never reused |
Consistency | A new link works immediately for its creator; click counts may lag by minutes |
Security | Resist abuse (phishing, malware, spam); codes not guessable in sequence |
The requirement that drives the design: redirects are an extremely read-heavy, latency-sensitive key lookup that must be very highly available.
3. Assumptions and scale estimation
#Writes (new links)
100 million per month / (30 x 86,400 s) = about 40 per second on averagePeak factor 3: about 120 per secondReads (redirects)
100 redirects per link -> 10 billion redirects per month10 billion / 2.6 million s = about 3,900 per second on averagePeak factor 3: about 12,000 per secondRead:write ratio: 100 to 1Storage
Per link: code (7 bytes), long URL (average about 200 bytes, maximum 2 KB),owner, timestamps, expiry, flags: about 500 bytes with overheadsPer year: 1.2 billion links x 500 bytes = 600 GBFive years: 6 billion links, about 3 TB (before replicas)Cache
Access is skewed: recent and viral links get most clicks. Assume the hottest 20 million links (about the last few weeks of popular links) serve most redirects: 20 million x 500 bytes = 10 GB. That fits comfortably in memory on a small replicated cache cluster.
Bandwidth
A redirect response is tiny (headers and a short body, under 1 KB): 12,000 per second x 1 KB = 12 MB per second, about 0.1 Gbps. Bandwidth is not a concern.
Conclusions: writes are modest (one database primary is plenty); storage grows to a few terabytes over years (one database can hold it, partitioning is a later option); reads are the hard part, at 12,000 per second with a tight latency target, which calls for caching.
4. API design
#POST /v1/linksIdempotency-Key: 3c1f... (so a retried create does not make two links){ "long_url": "https://www.example.com/...", "custom_alias": "kerala-trip", "expires_at": "2027-01-01T00:00:00Z" }201 CreatedLocation: /v1/links/kerala-trip{ "code": "kerala-trip", "short_url": "https://pix.ly/kerala-trip", "expires_at": "2027-01-01T00:00:00Z" }Errors: 400 malformed URL; 409 alias already taken; 422 URL not allowed (blocked domain); 429 rate limitedGET /{code}302 FoundLocation: https://www.example.com/...Errors: 404 unknown code; 410 Gone for an expired or deleted linkDELETE /v1/links/{code} -> 204 (owner only)GET /v1/links/{code}/stats -> 200 {"clicks": 18234, "as_of": "..."} (owner only)301 or 302?
#HTTP defines 301 Moved Permanently and 308 Permanent Redirect, which clients and caches may remember, and 302 Found and 307 Temporary Redirect, which they should not treat as permanent (RFC 9110).
301 (or 308) permanent | 302 (or 307) temporary | |
|---|---|---|
Browser behaviour | May cache the redirect and skip our server next time | Asks our server each time |
Load on our servers | Lower | Higher |
Click counting | Repeat visits from the same browser are invisible | Every visit is seen |
Deleting or expiring a link | Cached redirects may keep working | Takes effect immediately |
Because we need click counts and the ability to delete and expire links, choose 302. (A shortener without analytics or expiry could prefer 301 to reduce load.) This is a good example of a small decision with consequences worth stating in an interview.
5. Data model
#The only hot access pattern is "find the long URL by code": a point lookup by key.
CREATE TABLE links ( code VARCHAR(32) PRIMARY KEY, -- random code or custom alias long_url VARCHAR(2048) NOT NULL, owner_id BIGINT, -- null for anonymous links created_at TIMESTAMPTZ NOT NULL DEFAULT now(), expires_at TIMESTAMPTZ, deleted_at TIMESTAMPTZ, is_custom BOOLEAN NOT NULL DEFAULT false);CREATE INDEX links_by_owner ON links (owner_id, created_at DESC) WHERE owner_id IS NOT NULL;CREATE INDEX links_expiring ON links (expires_at) WHERE expires_at IS NOT NULL AND deleted_at IS NULL;CREATE TABLE link_click_counts ( code VARCHAR(32) PRIMARY KEY REFERENCES links(code), clicks BIGINT NOT NULL DEFAULT 0, updated_at TIMESTAMPTZ NOT NULL);- The primary key on
codemakes redirects a single index lookup and enforces uniqueness for both random codes and custom aliases. links_by_ownerserves "my links" pages; partial indexes keep the expiry job cheap.- Click counts are separate, updated in batches by the analytics pipeline, so redirects never write to
links. - Partition key if we ever shard:
code. Every redirect includes it, and random codes spread evenly.
Relational or key-value? The access pattern is pure key-value, so a key-value store (for example DynamoDB or Cassandra) would fit well. A relational database also fits: a few terabytes over five years, about 120 writes per second at peak, and point reads that are mostly served by the cache. Either is defensible. Choose the one the team operates confidently, and say that the access pattern makes a later migration to a key-value store straightforward.
6. Baseline architecture
# create (POST) redirect (GET /code)Client ---------------------------> Load balancer <------------------------- Browser | App servers (stateless) | PostgreSQL (primary)Create flow: validate the URL; generate a code (section 7); insert the row (the primary key rejects duplicates); return 201.
Redirect flow: look up code; if it is missing, deleted or expired, return 404 or 410; otherwise return 302 with the Location.
This works for a small number of users. At 12,000 redirects per second, something breaks first.
7. What breaks first?
#- Database reads. 12,000 point lookups per second at peak, with a 50 ms p99 target, all hitting one primary. Even if the database could keep up, it leaves no headroom, and any slowdown shows up directly in redirect latency.
- Code generation. How do we create unique, short, unguessable codes, possibly on many app servers at once?
- Hot links. A viral link can receive a large share of all traffic.
- Analytics. Counting clicks naively (an
UPDATEper redirect) would add 12,000 writes per second to the database.
Generating short codes
#How long must codes be? Using the 62 characters 0-9, a-z and A-Z, 6 characters give 62^6 = about 57 billion codes and 7 characters give 62^7 = about 3.5 trillion. Five years of links (6 billion) fits in either, but random codes collide more often as the space fills, so 7 characters gives comfortable room.
Three approaches:
Approach | How | Pros | Cons |
|---|---|---|---|
Hash the URL | Hash the long URL (for example SHA-256), base-62 encode, take the first 7 characters | Same URL can map to the same code (deduplication) | Collisions between different URLs must still be handled; two users shortening the same URL share a code and its analytics, which is often unwanted |
Counter + base 62 | A unique number per link (from a sequence, or from ranges of numbers allocated to each server) encoded in base 62 | No collisions; short codes early on | Sequential codes are enumerable: anyone can walk |
Random code + uniqueness check | Generate 7 random base-62 characters; insert; on a primary-key conflict, generate another | Unguessable; no coordination; simple | Small chance of collision, handled by a retry |
This Java program shows base-62 encoding and decoding of counter values, the size of the 7-character space, the collision probability for a random code once 6 billion links exist, and some random codes (with a fixed seed so the output is repeatable; production code would use a secure random source).
// File: ShortCodes.javaimport java.util.Locale;import java.util.Random;public class ShortCodes { static final String ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; static final int BASE = ALPHABET.length(); // 62 // Counter-based codes: encode a unique number in base 62. static String encode(long n) { if (n == 0) { return "0"; } StringBuilder sb = new StringBuilder(); while (n > 0) { sb.append(ALPHABET.charAt((int) (n % BASE))); n /= BASE; } return sb.reverse().toString(); } static long decode(String code) { long n = 0; for (char c : code.toCharArray()) { n = n * BASE + ALPHABET.indexOf(c); } return n; } // Random codes: pick each character at random (in production, from a secure random source). static String randomCode(Random random, int length) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { sb.append(ALPHABET.charAt(random.nextInt(BASE))); } return sb.toString(); } public static void main(String[] args) { for (long n : new long[] {1, 61, 62, 125_000_000_000L}) { String code = encode(n); System.out.println(n + " -> " + code + " -> " + decode(code)); } long space7 = (long) Math.pow(BASE, 7); System.out.printf(Locale.US, "7-character space: %,d codes%n", space7); System.out.println("largest 7-character code: " + encode(space7 - 1)); long used = 6_000_000_000L; // 5 years of links in the worked example System.out.printf(Locale.US, "chance a new random code collides after %,d links: %.2f%%%n", used, 100.0 * used / space7); Random random = new Random(7); // fixed seed only so this output is repeatable System.out.println("random codes: " + randomCode(random, 7) + " " + randomCode(random, 7) + " " + randomCode(random, 7)); }}1 -> 1 -> 161 -> Z -> 6162 -> 10 -> 62125000000000 -> 2crtgcg -> 1250000000007-character space: 3,521,614,606,208 codeslargest 7-character code: ZZZZZZZchance a new random code collides after 6,000,000,000 links: 0.17%random codes: A0FQQ6c HISUmE3 jC7k3hAEven after five years, a new random code collides with an existing one less than 0.2% of the time, so on average about one create in 600 needs a single retry, and two consecutive collisions are very rare. That retry is cheap and correct because the database's primary key is the referee: two servers that happen to generate the same code at the same instant cannot both insert it.
Decision: random 7-character codes, inserted with the primary key enforcing uniqueness, retrying up to a few times on conflict. Custom aliases use the same table and the same constraint; a conflict there returns 409 to the user instead of retrying. (If short, sequential-looking codes were a requirement, a counter with ranges allocated to each server, scrambled by a reversible permutation to avoid enumeration, would be the alternative.)
8. Scaling the design
#Cache the redirect path
#Add a distributed cache (for example Redis) in front of the database: cache-aside for reads, as described in Microsoft's Cache-Aside pattern (Cache-Aside pattern):
- Redirect: look up
link:{code}in the cache; on a hit, redirect; on a miss, read the database, store the result with a TTL of about a day (with jitter), redirect. - Create: after inserting the row, write it into the cache too, so the very first redirect (often the creator testing the link) is a hit and never depends on replica lag.
- Delete: mark the row deleted, then delete the cache entry.
- Unknown codes: cache "not found" for a short time (negative caching), so bots probing random codes do not reach the database.
With the 10 GB hot set cached and a hit ratio of, say, 95%, the database sees about 600 reads per second at peak instead of 12,000.
Read replicas and a small local cache
#- Cache misses go to read replicas, not the primary, which handles only about 120 writes per second.
- Each app server keeps a tiny in-process cache (a few seconds' TTL) for the hottest codes, so a viral link does not overload one cache node (the hot-key problem from article 09).
Serving from the edge (optional)
#Redirect responses for popular links can be cached at a CDN for a short time, which cuts latency for distant users and load on the origin. A CDN such as CloudFront routes each request to a nearby edge location (What is Amazon CloudFront?). The cost: clicks served at the edge are not seen by our servers, so counting must come from CDN logs, and deleting a link takes effect only after the edge TTL. Keep the edge TTL short (seconds to a minute) if used at all.
Analytics off the critical path
#Never update a counter in the database on each redirect. Instead, after sending the redirect, the app server emits a click event (code, timestamp, coarse country and referrer) to a queue or stream, without waiting for an acknowledgement on the request path. Aggregation workers consume events, sum clicks per code per minute, and update link_click_counts in batches. If the analytics pipeline is down, redirects continue; events wait in the stream (article 11).
Expiry
#- On read: a redirect for an expired link returns
410 Gone, even if the row still exists. (The cached entry storesexpires_at, so the cache checks it too.) - Cleanup: a background job uses the
links_expiringindex to find expired links in batches, marks them, and removes their cache entries. - Never reuse codes, even after expiry: an old printed link must not start pointing somewhere new.
Sharding, when needed
#At 3 TB over five years and 120 writes per second, one primary with replicas is enough for a long time. If growth is ten times faster, partition links by hash of code across shards (article 16). Every redirect includes the code, so each lookup touches one shard, and random codes spread load evenly. "My links" queries by owner would then need a secondary index partitioned by owner, or the owner's links could be kept in a separate table keyed by owner.
The evolved architecture
#REQUEST PATH (redirect)Browser -> (optional CDN, short TTL) -> Load balancer -> App servers App server: in-process hot cache -> Redis cache -> read replicas (on miss) App server returns 302 immediatelyREQUEST PATH (create)Client -> Load balancer -> App servers -> URL safety checks -> Primary DB (unique code) -> write-through to RedisASYNC PATHApp servers --click events--> Stream --> Aggregation workers --> link_click_counts (batched)Scheduler --> Expiry job --> marks expired links, deletes cache entries9. Failure scenarios and reliability
#Failure | What happens | Response |
|---|---|---|
One app server crashes | Load balancer removes it | Stateless servers; N+1 per zone |
Cache node fails | Its keys miss; replicas take a burst of reads | Cache replicas with failover; in-process cache absorbs hot keys; request coalescing on misses |
Whole cache cluster down | Up to 12,000 reads per second on replicas | Size replicas for a degraded mode, shed low-priority traffic (stats pages, creation) before redirects |
Primary database down | Creates fail; redirects continue from cache and replicas | Synchronous standby and automatic failover; clients retry creation with the same idempotency key |
Replica lag | A brand-new link could miss on a replica | Write-through to cache on creation; on a miss for a code younger than a minute, read the primary |
Region down | Redirects fail in that region | DR replica in another region (warm standby, article 24); redirects are the priority to restore |
Analytics stream down | Click events cannot be published | Buffer briefly in memory, then drop with a metric; redirects must never wait on analytics |
Duplicate create (retry) | Could create two links | Idempotency key stored with the link |
Concurrent identical random codes | Could collide | Primary-key constraint rejects one; it retries |
The key reliability idea: the redirect path depends on as little as possible (app server, cache, replicas). Creation, analytics and expiry can degrade without breaking redirects.
10. Consistency
#- New links: strongly visible to the creator immediately, thanks to write-through to the cache (and primary reads for very young codes on a miss).
- Deleted links: stop working after the cache entry is deleted; if edge caching is used, after its TTL as well. State this window.
- Click counts: eventually consistent, minutes behind; acceptable per requirements.
- Code uniqueness: strongly enforced by the database primary key, never by application checks.
11. Security
#The real-world risk of a public shortener is abuse: attackers hide phishing and malware links behind a trusted-looking short domain.
- Check URLs at creation against blocklists and reputation services, and re-scan popular links periodically; block known-bad domains (
422). - Rate-limit creation per account, API key and IP; stricter for anonymous users; CAPTCHA or account requirement above a threshold (article 31).
- Takedown path: abuse reports, a staff tool to disable links (with audit logs), and fast cache invalidation.
- Unguessable codes (random, not sequential) so private links cannot be enumerated.
- Validate input: only
httpandhttpsURLs, maximum length, nojavascript:ordata:schemes. - Custom aliases: reserve system paths (
/v1,/admin,/login), filter offensive or impersonating aliases. - Authorisation: only owners can delete links or see their stats (object-level checks, article 28).
- Interstitial warning pages for links flagged as suspicious, rather than redirecting blindly.
12. Observability
#Signal | Why |
|---|---|
Redirect p50 and p99 latency, error rate | The main SLO |
Cache hit ratio (and per-layer: in-process, Redis) | A drop predicts database overload |
404 and 410 rates | Spikes may mean broken links being shared, or scanning by bots |
Creation rate, rejected-URL rate, collision retries | Abuse detection and code-space health |
Replica lag | Freshness of new links on misses |
Click-event lag | Analytics delay against its minutes target |
Synthetic checks | Create a link and follow it every minute from several regions |
13. Trade-offs
#Decision | Chosen | Alternative | Why |
|---|---|---|---|
Redirect code | 302 | 301 | Need click counts, deletion and expiry |
Code generation | Random 7 characters, unique constraint, retry | Counter + base 62 | Unguessable, no coordination; collisions rare and cheap |
Store | Relational with cache and replicas | Key-value store | Fits the scale and team; key-value is an easy later move |
Cache writes | Write-through on create, cache-aside on read | Cache-aside only | New links must work immediately |
Analytics | Asynchronous click stream, batched counts | Update per redirect | Keeps redirects fast and isolated |
Edge caching | Optional, short TTL | None, or long TTL | Latency versus counting and deletion freshness |
14. The final interview answer
#"We're building a public URL shortener with about 100 million new links a month and a hundred times as many redirects, roughly 12,000 redirects a second at peak, with a p99 target of 50 ms and very high availability for redirects.
Links live in a table keyed by a 7-character random base-62 code; the primary key guarantees uniqueness, and the rare collision is retried. Custom aliases share the table. Creation takes an idempotency key, checks the URL against abuse lists, inserts, and writes the new link through to Redis so it works immediately.
Redirects go through stateless app servers that check a tiny in-process cache, then Redis, then read replicas, and return a 302, which we chose over 301 so we can count clicks and honour deletion and expiry. With a 95% hit rate the database sees about 600 reads a second. Clicks are published to a stream and aggregated asynchronously, so analytics can never slow or break redirects. Expiry is enforced on read and cleaned up by a background job.
The redirect path depends only on app servers, cache and replicas, so primary failover, analytics outages or creation problems don't stop redirects. If growth outpaces one primary, we shard by hash of the code. The biggest operational risk is abuse, handled by URL checks, rate limits and a takedown path."
The Pastebin variant
#Pastebin lets users store text (code snippets, logs, notes) and share it by a short link. It is the URL shortener plus a blob:
Aspect | URL shortener | Pastebin |
|---|---|---|
Stored per item | A URL (about 200 bytes) | Text from a few bytes to several megabytes |
Where the content lives | In the database row | In object storage, with the database holding metadata and the object key (article 10) |
Read response | A 302 redirect | The paste content (or a page that renders it) |
Bandwidth | Tiny | Significant: average paste 10 KB x millions of reads |
Extra features | Custom aliases | Syntax highlighting, burn-after-reading, private pastes, size limits |
Changes to the design:
- Create: the client sends the text (for large pastes, uploads it directly to object storage with a presigned URL, as AWS documents for S3 (presigned URLs)); the API stores metadata (
code,object_key,size,content_type,expires_at,visibility) in the database. - Read: look up metadata (cached), then serve the content from object storage through a CDN, which absorbs the bandwidth. Public pastes can be cached at the edge with long TTLs because content is immutable (a paste is never edited; a new version is a new paste).
- Expiry: object-storage lifecycle rules delete expired content; the metadata row returns
410. - Private pastes: short-lived signed URLs for content, plus authorisation on metadata.
- Burn after reading: a conditional update marks the paste consumed on first read (
UPDATE ... WHERE consumed = false), and only the request that changed the row gets the content: a nice small exercise in concurrency (article 14). - Abuse: pastes can contain leaked credentials or malicious content; add size limits, scanning, rate limits and a takedown process.
Exercises
#Exercise 1
Redo the estimate
#Redo the estimate for a shortener with 500 million new links a month, a read:write ratio of 20 to 1, a 10-year retention and 400 bytes per link. Compute peak writes and reads (peak factor 3), and total storage. Does one database still suffice?
Exercise 2: Counter-based codes
#Your interviewer insists codes must be as short as possible early on, so they want counter-based codes. Design how 50 app servers get unique numbers without a single bottleneck, and how you stop people enumerating links.
Exercise 3: The viral link
#A link is shared by a celebrity and receives 50,000 redirects per second for an hour, while the rest of the system receives its normal 12,000. Walk through what happens in this design and what protects it.
Exercise 4: Custom alias race
#Two users submit the custom alias diwali-sale at the same moment. Walk through what happens and what each user sees. Then: what changes if links are stored in a key-value store without transactions?
Show solutionHide solution
Writes: 500M / 2.6M s = about 190 per second; peak about 580 per secondReads: 20 x 500M = 10 billion per month = about 3,850 per second; peak about 11,500 per secondStorage: 500M x 12 months x 10 years = 60 billion links x 400 bytes = 24 TB (before replicas)Reads are similar to the original example, so the caching design is unchanged. Writes (580 per second) are still fine for one well-provisioned primary, but storage (24 TB, about 72 TB with three copies) is beyond what you would want on one database node. Plan to shard by hash of code from the start (or use a key-value store that partitions automatically). Also check the code space: 60 billion links in 3.5 trillion 7-character codes is 1.7% used, so collisions rise to about 1 in 60 creates near the end; still acceptable with retries, or move to 8 characters.
Exercise 2
Show solutionHide solution
Range allocation: a small, highly available range service (backed by a database sequence or a consensus store) hands out blocks of numbers, for example 1,000,000 at a time. Each app server keeps its current block in memory and assigns numbers locally; when it runs low, it asks for the next block. The range service is called rarely (once per million links per server), so it is not a bottleneck, and a server crash only wastes the unused part of its block.
Preventing enumeration: encoding sequential numbers directly gives sequential-looking codes. Apply a reversible scrambling (a bijective permutation of the number space, such as a keyed format-preserving cipher or a multiply-and-modulo step with a secret constant) before base-62 encoding. Codes stay unique (the permutation is one-to-one) and short, but consecutive links no longer have adjacent codes. Also keep rate limits on lookups of unknown codes, so scanning is slow and visible.
Exercise 3
Show solutionHide solution
The hot code is one key. Without protection, all 50,000 requests per second would hit the single Redis node that owns it.
- In-process caches on each app server serve it after the first lookup; with, say, 60 app servers and a 5-second TTL, Redis sees about 12 lookups per second for that key.
- The load balancer spreads 50,000 + 12,000 = 62,000 requests per second across app servers; if that exceeds capacity, autoscaling adds servers (it takes minutes), and until then load shedding protects them, prioritising redirects over stats pages and creation.
- Optional edge caching would serve most of the 50,000 at the CDN.
- Click events for the viral link (50,000 per second) flow into the stream; aggregation workers batch them, so
link_click_countssees one update per minute for that code. If workers fall behind, analytics lags, but redirects are unaffected. - Abuse checks continue to apply: if the "celebrity" link turns out to be malicious, a takedown deletes the cache entries, and redirects return a warning page.
Exercise 4
Show solutionHide solution
In the relational design, both requests try INSERT ... (code = 'diwali-sale'). The primary key guarantees only one succeeds. The winner gets 201; the other gets a unique-violation error, which the API turns into 409 Conflict ("alias already taken, try another"). No double ownership is possible, whatever the timing or the number of app servers.
In a key-value store, use its conditional write: "put this item only if the key does not exist" (most key-value stores offer an equivalent of insert-if-absent, such as a condition on the key's existence). The semantics are the same: one write succeeds, the other is rejected and returns 409. What you must not do is "read to check, then write", which reintroduces the check-then-act race from article 14.
Where to go next
#- Next: Design a Distributed Rate Limiter, the component that protects this shortener's creation endpoint.
- Then: Design a Distributed Cache, the component that makes its redirect path fast.
Sources & further reading
- Technology
- Software Engineering
- #caching
- #case study
- #distributed systems
- #roadmap
- #scalability
- #system design
- #system design interview
Connected lessons
Linked from 2 guides
Other pages in the library that point here.
Keep reading
More in Technology →Guide · Technology
Start Here: Your Roadmap from Zero to Job-Ready DSA
Guide · Technology
Heaps and Priority Queues
Guide · Career
A LinkedIn Profile That Gets Recruiter Messages

