System Design
Learn System Design
Introduction to System Design
How to Learn System Design?
Key Characteristics of Distributed Systems
Scalability
Availability
Latency and Performance
Concurrency and Coordination
Monitoring and Observability
Resilience and Error Handling
Fault Tolerance vs. High Availability
Flashcards Review
Chapter Assessment
Network Essentials
HTTP vs. HTTPS
TCP vs. UDP
HTTP: 1.0 vs. 1.1 vs 2.0 vs. 3.0
URL vs. URI vs. URN
What Happens When You Type a URL into the Browser
Flashcards Review
Chapter Assessment
Long-Polling vs. WebSockets vs. Server-Sent Events
Introduction to Real-Time Communication
What is Long-Polling?
What is WebSocket?
What are Server-Sent Events?
Difference Between Long-Polling, WebSockets, and Server-Sent Events
Flashcards Review
Chapter Assessment
Domain Name System (DNS)
Introduction to DNS
DNS Resolution Process
DNS Load Balancing and High Availability
Flashcards Review
Chapter Assessment
Proxies
What is a Proxy Server?
Uses of Proxies
VPN vs. Proxy Server
Flashcards Review
Chapter Assessment
Load Balancing
Introduction to Load Balancing
Load Balancing Algorithms
Uses of Load Balancing
Load Balancer Types
Stateless vs. Stateful Load Balancing
High Availability and Fault Tolerance
Scalability and Performance
Challenges of Load Balancers
Flashcards Review
Chapter Assessment
API Gateway
Introduction to API Gateway
Usage of API gateway
Advantages and disadvantages of using API gateway
Flashcards Review
Chapter Assessment
API Design
What Is an API?
What Are REST APIs?
Resources, Not Actions
HTTP Methods and Their Semantics
URL Design
Request and Response Shapes
Status Codes and Error Design
Pagination from the Consumer's View
Idempotency Keys
Versioning and Backward Compatibility
Concurrency and Conditional Requests
REST vs gRPC vs GraphQL
Flashcards Review
Chapter Assessment
Rate Limiting and Throttling
What Is Rate Limiting
Rate Limiting Algorithms
Distributed Rate Limiting
Rate Limiting in Practice
Flashcards Review
Chapter Assessment
Caching
Introduction to Caching
Why is Caching Important?
Types of Caching
Cache Replacement Policies
Cache Invalidation
Cache Read Strategies
Cache Coherence and Consistency Models
Caching Challenges
Cache Performance Metrics
Flashcards Review
Chapter Assessment
CDN
What is CDN?
Origin Server vs. Edge Server
CDN Architecture
Push CDN vs. Pull CDN
Flashcards Review
Chapter Assessment
Data Partitioning
Introduction to Data Partitioning
Partitioning Methods
Data Sharding Techniques
Benefits of Data Partitioning
Common Problems Associated with Data Partitioning
Flashcards Review
Chapter Assessment
Redundancy and Replication
What is Redundancy?
What is Replication?
Replication Methods
Data Backup vs. Disaster Recovery
Flashcards Review
Chapter Assessment
CAP & PACELC Theorems
Introduction to CAP Theorem
Components of CAP Theorem
Trade-offs in CAP Theorem
Examples of CAP Theorem in Practice
Beyond CAP Theorem
System Design Trade-offs in Interviews
Flashcards Review
Chapter Assessment
Databases (SQL vs. NoSQL)
Introduction to Databases
SQL Databases
NoSQL Databases
SQL vs. NoSQL
ACID vs BASE Properties
Real-World Examples and Case Studies
SQL Normalization and Denormalization
In-Memory Database vs. On-Disk Database
Data Replication vs. Data Mirroring
Database Federation
Flashcards Review
Chapter Assessment
Indexes
What are Indexes?
How a B-Tree Index Works
Types of Indexes
B-Tree vs. LSM Tree
Indexes in Distributed Systems
Flashcards Review
Chapter Assessment
Bloom Filters
Introduction to Bloom Filters
Benefits & Limitations of Bloom Filters
Variants and Extensions of Bloom Filters
Applications of Bloom Filters
Flashcards Review
Chapter Assessment
Quorum
Why Quorum?
What is Quorum?
Flashcards Review
Chapter Assessment
Leader and Follower
What is Leader and Follower Pattern?
Flashcards Review
Chapter Assessment
Heartbeat
What is Heartbeat?
Flashcards Review
Chapter Assessment
Checksum
What is Checksum?
Uses of Checksum
Flashcards Review
Chapter Assessment
Distributed Messaging System
Introduction to Messaging System
Introduction to Kafka
Messaging patterns
Popular Messaging Queue Systems
RabbitMQ vs. Kafka vs. ActiveMQ
Scalability and Performance
Flashcards Review
Chapter Assessment
Distributed File Systems
What is a Distributed File System?
Architecture of a Distributed File System
Key Components of a DFS
Flashcards Review
Chapter Assessment
Security
What is Security and Privacy?
What is Authentication?
What is Authorization?
Authentication vs. Authorization
OAuth vs. JWT for Authentication
What is Encryption?
What are DDoS Attacks?
Flashcards Review
Chapter Assessment
Misc Concepts
Batch Processing vs. Stream Processing
XML vs. JSON
Synchronous vs. Asynchronous Communication
Push vs. Pull Notification Systems
Microservices vs. Serverless Architecture
Message Queues vs. Service Bus
Stateful vs. Stateless Architecture
Event-Driven vs. Polling Architecture
Flashcards Review
Chapter Assessment
Quiz - System Design Fundamentals
Quiz
System Design Trade-offs
Importance of Discussing Trade-offs
Strong vs Eventual Consistency
Latency vs Throughput
ACID vs BASE Properties in Databases
Read-Through vs Write-Through Cache
Batch Processing vs Stream Processing
Load Balancer vs. API Gateway
API Gateway vs Direct Service Exposure
Proxy vs. Reverse Proxy
API Gateway vs. Reverse Proxy
SQL vs. NoSQL
Primary-Replica vs Peer-to-Peer Replication
Data Compression vs Data Deduplication
Server-Side Caching vs Client-Side Caching
REST vs RPC
Polling vs. Long-Polling vs. WebSockets vs. Webhooks
CDN Usage vs Direct Server Serving
Serverless Architecture vs Traditional Server-based
Stateful vs Stateless Architecture
Hybrid Cloud Storage vs All-Cloud Storage
Token Bucket vs Leaky Bucket
Read Heavy vs Write Heavy System
Quiz
How to Approach a System Design Interview
System Design Interviews - A step by step guide
Functional vs. Non-functional Requirements
What are Back-of-the-Envelope Estimations?
Things to Avoid During System Design Interview
System Design Master Template
System Design Master Template
Quiz
Designing Pastebin
Designing Pastebin
Quiz - Designing Pastebin
Designing Instagram
Designing Instagram
Quiz - Designing Instagram
Designing Dropbox
Designing Dropbox
Quiz - Designing Dropbox
Designing Facebook Messenger
Designing Facebook Messenger
Quiz - Designing Facebook Messenger
Designing Twitter
Designing Twitter
Quiz - Designing Twitter
Designing Youtube or Netflix
Designing Youtube or Netflix
Quiz - Designing Youtube
Designing Typeahead Suggestion
Designing Typeahead Suggestion
Quiz - Designing Typeahead Suggestion
Designing an API Rate Limiter
Designing an API Rate Limiter
Quiz - Designing an API Rate Limiter
Designing Twitter Search
Designing Twitter Search
Quiz - Designing Twitter Search
Designing a Web Crawler
Designing a Web Crawler
Quiz - Designing a Web Crawler
Designing Facebook’s Newsfeed
Designing Facebook’s Newsfeed
Quiz - Designing Facebook’s Newsfeed
Designing Yelp or Nearby Friends
Designing Yelp or Nearby Friends
Quiz - Designing Yelp or Nearby Friends
Designing Uber backend
Designing Uber backend
Quiz - Designing Uber backend
Designing Ticketmaster
Designing Ticketmaster
Quiz - Designing Ticketmaster
Dynamo: How to design a key value store?
Dynamo: Introduction
High-Level Architecture
Data Partitioning
Replication
Vector Clocks and Conflicting Data
The Life of Dynamo’s put() & get() Operations
Anti-entropy Through Merkle Trees
Gossip Protocol
Dynamo Characteristics and Criticism
Summary: Dynamo
Quiz: Dynamo
Mock Interview: Dynamo
Designing YouTube Likes Counter (medium)
YouTube Likes Counter
Quiz
Cassandra: How to Design a Wide-column NoSQL Database?
Cassandra: Introduction
High-level Architecture
Replication
Cassandra Consistency Levels
Gossiper
Anatomy of Cassandra's Write Operation
Anatomy of Cassandra's Read Operation
Compaction
Tombstones
Summary: Cassandra
Quiz: Cassandra
Mock Interview: Cassandra
Kafka: How to Design a Distributed Messaging System?
Messaging Systems: Introduction
Kafka: Introduction
High-level Architecture
Kafka: Deep Dive
Consumer Groups
Kafka Workflow
Role of ZooKeeper
Controller Broker
Kafka Delivery Semantics
Kafka Characteristics
Summary: Kafka
Quiz: Kafka
Mock Interview: Kafka
Chubby: How to Design a Distributed Locking Service?
Chubby: Introduction
High-level Architecture
Design Rationale
How Chubby Works
File, Directories, and Handles
Locks, Sequencers, and Lock-delays
Sessions and Events
Master Election and Chubby Events
Caching
Database
Scaling Chubby
Summary: Chubby
Quiz: Chubby
Mock Interview: Chubby
HDFS: How to Design File Storage System?
Hadoop Distributed File System: Introduction
High-level Architecture
Deep Dive
Anatomy of a Read Operation
Anatomy of a Write Operation
Data Integrity & Caching
Fault Tolerance
HDFS High Availability (HA)
HDFS Characteristics
Summary: HDFS
Quiz: HDFS
Mock Interview: HDFS
GFS: How to Design a Distributed File System Storage?
Google File System: Introduction
High-level Architecture
Single Master and Large Chunk Size
Metadata
Master Operations
Anatomy of a Read Operation
Anatomy of a Write Operation
Anatomy of an Append Operation
GFS Consistency Model and Snapshotting
Fault Tolerance, High Availability, and Data Integrity
Garbage Collection
Criticism on GFS
Summary: GFS
Quiz: GFS
Mock Interview: GFS
BigTable: How to Design a Wide Column Storage System?
BigTable: Introduction
BigTable Data Model
System APIs
Partitioning and High-level Architecture
SSTable
GFS and Chubby
Bigtable Components
Working with Tablets
The Life of BigTable's Read & Write Operations
Fault Tolerance and Compaction
BigTable Refinements
BigTable Characteristics
Summary: BigTable
Quiz: BigTable
Mock Interview: BigTable
Designing Reddit (medium)
Design Reddit
Quiz
Designing Notification Service (medium)
Designing a Notification System
Quiz
Design Google Calendar (medium)
Design Google calendar (Medium)
Quiz
Design a Recommendation System (medium)
Design a Recommendation System for Netflix
Quiz
Designing Gmail (medium)
Design Gmail
Quiz
Designing Google News (medium)
Design Google News, a Global News Aggregator System (Medium)
Quiz
Designing Unique ID Generator (medium)
Design Unique ID Generator (Easy)
Quiz
Designing Code Judging System (medium)
Design Code Judging System like LeetCode (Medium)
Quiz
Designing Payment System (hard)
Design Payment System
Quiz
Designing Flash Sale System (hard)
Design a Flash Sale for an E-commerce Site (Hard)
Quiz
Designing Reminder Alert System (hard)
Design a Reminder Alert System
Quiz
System Design Patterns
Introduction: System Design Patterns
1. Bloom Filters
2. Consistent Hashing
3. Quorum
4. Leader and Follower
5. Write-ahead Log
6. Segmented Log
7. High-Water Mark
8. Lease
9. Heartbeat
10. Gossip Protocol
11. Phi Accrual Failure Detection
12. Split Brain
13. Fencing
14. Checksum
15. Vector Clocks
16. CAP Theorem
17. PACELC Theorem
18. Hinted Handoff
19. Read Repair
20. Merkle Trees
Quiz
Designing a URL Shortening Service like TinyURL
url shortening
rate limiting
caching
availability
+3
Every case study in this chapter follows the same seven steps from System Design Interviews: A step by step guide, and builds its design from the components in the System Design Master Template. Keep both open beside you, and try each problem yourself before reading the solution.
Why do we need URL shortening?
URL shortening is used to create shorter aliases for long URLs. We call these shortened aliases "short links." Users are redirected to the original URL when they open these short links. Short links save a lot of space when displayed, printed, messaged, or tweeted. Additionally, users are less likely to mistype shorter URLs.
For example, if we shorten the following URL through TinyURL:
https://www.designgurus.io/course/grokking-the-system-design-interview
We would get:
https://tinyurl.com/vzet59pa
The shortened URL is less than half the size of the actual URL, and the longer the original link, the bigger the saving.
URL shortening has other uses as well. Companies track individual links to analyze the audience and measure the performance of an ad campaign. Short links can also be optimized for different devices, or used to hide the original affiliate URL.
If you haven't used tinyurl.com before, please try creating a new shortened URL. Spend some time going through the various options their service offers. This will help you a lot in understanding this chapter.
Try it yourself
Before reading the solution, try designing it. Aim to produce four things: the requirements, a rough load estimate, the API, and a first drawing of the components.
Sketch it here
Designing a URL Shortener (video)
Here is a video discussing how to design a URL Shortener:
Step 1: Clarify the Requirements
💡 Always clarify requirements at the beginning of the interview. Ask questions until you know the exact scope of the system the interviewer has in mind.
Functional requirements
- Given a long URL, the service returns a short link.
- Following a short link redirects the user to the original URL.
- Users can optionally pick their own custom alias.
- Links expire after a default period, and users can set their own expiry.
Two more belong on the list as extended requirements, agreed as lighter-weight features. Owners can see click statistics for their links. A link can be marked private, so only permitted users may open it. Neither feature drives the core design, but several later decisions depend on them.
Non-functional requirements
- High availability. If the service is down, every link anyone has ever shared stops working. That is worse than a normal outage. The damage is spread across other people's websites, emails, and posts.
- Low latency on redirects. A redirect is an extra step the user never asked for. The user should not notice it.
- Short links must not be guessable. Somebody should not be able to type random characters and reach other people's links.
Some features are out of scope: billing and paid plans, link previews, and malware scanning of destinations. Say this list out loud, so the interviewer can add anything back.
Keep the third requirement in mind. It is easy to agree to, and easy to break without noticing. In step 6 we will see the usual answer break it.
Step 2: Estimate the Scale
Ask the interviewer for two numbers, because every other estimate follows from them. The first is how many new links are created per month. The second is the ratio of reads to writes. Assume 500 million new links a month and 100 reads for every write.
Load. A day is roughly 100,000 seconds. That round number keeps the division simple.
500M per month / 30 days = ~17M new links per day
17M / 100,000 seconds = ~170, call it 200 writes per second
200 x 100 = 20,000 reads per second
20,000 x 3 (peak factor) = ~60,000 reads per second at peak
Storage. Keep every link for five years.
500M x 12 months x 5 years = 30 billion links
30 billion x 500 bytes = ~15 TB
Bandwidth. Both directions are small, because we are moving text.
200 writes x 500 bytes = ~100 KB per second in
20,000 reads x 500 bytes = ~10 MB per second out
Cache. If 20 percent of links produce 80 percent of the traffic, we want that 20 percent in memory.
20,000 x 100,000 seconds = ~2 billion reads per day
0.2 x 2 billion x 500 bytes = ~200 GB
That last figure is an upper bound. Many of those 2 billion reads hit the same links. So the number of distinct hot links is smaller, and the real memory need is lower.
Only three of these numbers change the design. The rest are just reassurance:
- 30 billion rows and 15 TB. Too much for one machine, so the data has to be partitioned.
- 100 reads per write. Read-heavy, so a cache in front of the database is the highest-value thing we can add.
- 10 MB per second out. Small. No CDN, no special network design, nothing to discuss.
Step 3: Define the API
💡 Defining the API early forces you to be concrete about what the system actually does, and it usually exposes a requirement nobody has stated yet.
Three endpoints cover the whole product. Say that, then define them.
POST /urls
body: { original_url, custom_alias?, expires_at? }
201: { short_url, short_key, expires_at }
GET /{short_key}
302: Location: <original_url>
DELETE /urls/{short_key}
204: no content
Two more endpoints are worth mentioning, and then skipping. One lists the links a user owns. The other reads a link's click statistics. Both are ordinary paginated reads, so spending interview time on them adds nothing.
Every write endpoint is rate limited per account. Anonymous requests are limited per IP address. Without these limits, one script can consume keys and quota all day. We come back to abuse in step 6.
Step 4: Define the Data Model
The data here has an unusually simple shape. The shape is what picks the database:
- Billions of rows.
- Every row is small, well under one kilobyte.
- One relationship, from a link to the user who created it.
- One read pattern that matters: look up a row by its short key.
Which database? A key-value or wide-column store like DynamoDB or Cassandra fits well. We have billions of rows spread across machines. Every hot query is a lookup by primary key. That is exactly the workload those stores are built for.
A user_id column is not a reason to choose a relational database. What decides that question is how much your queries depend on relationships. Here, redirects never join anything. "List my links" is a simple lookup by user_id. Relational databases are the right choice when you need joins across several tables, or transactions that span entities. This design needs neither.
Notice that clicks are not a column on the URL row. A counter there would make every redirect wait on a database write. A viral link would turn that one row into the hottest write in the system. Clicks belong in a separate store, which we get to in step 6.
Step 5: Draw the High-Level Design
There are only two paths through this system. They could hardly be more different.
The write path runs at about 200 requests per second. A request arrives, the shortening service takes a key, and one row is inserted.
The read path runs at 20,000 requests per second, a hundred times heavier. A request arrives, the redirect service looks the key up, and returns a redirect.
That asymmetry is the most useful thing on the page. Everything expensive we add later belongs on the read path: the cache and the read replicas. The write path needs none of that. Its problem is not capacity but correctness. Every write must get a key that no other write has ever used. That is where step 6 goes next.
Step 6: Go Deep
Generating the key
This is the central question of the design: how do we produce a short, unique key for each URL?
How long should the key be? The usual answer is six characters. For this design, six is wrong.
We are storing 30 billion links. Six characters from a 62-character alphabet give about 56.8 billion possible keys. So more than half of all possible keys would point to a real link. Someone typing random characters would find a working link about every other try. That breaks the guessability requirement we agreed on in step 1.
Seven characters is the shortest length that fixes it. The keyspace grows to 3.5 trillion keys, and only 0.9 percent of them are ever used. A random guess now finds a working link about once in 117 tries. Rate limiting covers the rest of the risk.
This is a trade worth naming in the interview: one extra character makes guessing about sixty times harder.
Which alphabet? Base62, meaning A-Z, a-z, and 0-9. Base64 is often suggested, but base64 adds two more characters: + and /. Both cause trouble inside a URL. A slash splits the key into two path segments. A plus is often read as an encoded space by form handlers and proxies. So in practice, both end up percent-encoded. That makes links longer and harder to read, which works against the reason for shortening them. If you want base64, use the URL-safe variant with - and _ instead.
Now, where do keys come from? There are two answers. The difference between them is the real content of this problem.
Hash the URL. Run MD5 or SHA-256 over the long URL and keep the first seven characters of the encoded output. It is simple, and it has two problems.
The first problem: the same URL always produces the same key. So two users shortening the same link get the same short link. That sounds efficient, but it is not what you want. The two users may need different expiry times or separate click statistics. Worse, one of them may delete the link and break it for the other. Appending a counter or the user id to the hash input fixes this.
The second problem is the one that matters. We keep only seven characters, so we throw away most of the hash. Seven base62 characters hold about 42 bits. That means different URLs will collide, and at 30 billion stored links they will collide often. Every write then becomes a loop: generate a key, ask the database whether it is taken, and on a conflict start again. That loop runs on the request path while the user waits. It also gets slower as the keyspace fills.
Pre-generate the keys instead. A standalone Key Generation Service (KGS) produces random keys ahead of time and keeps them in a pool. Shortening a URL is then just: take a key from the pool, insert the row, done. No hashing, no collision check, no retry.
The uniqueness check has not disappeared. It has moved off the request path, into a background job where nobody is waiting. That is the whole trade. Stating it that way is a much stronger answer than either design on its own.
How big is the pool? It only has to stay ahead of demand, not cover the keyspace. At 17 million new links a day, a pool of one billion keys is about two months of buffer. One billion seven-character keys is only about 7 GB. The generator refills the pool continuously. Refilling is easy. With 0.9 percent of the keyspace used, a random candidate is already free about 99 times out of 100.
How does KGS avoid handing the same key to two servers? By handing out keys in whole blocks. A block is moved out of the unused pool and marked used before it leaves. A server asks for a block of, say, a thousand keys. KGS marks them used in one operation and hands them over. The server keeps the block in memory and answers writes locally. Two servers can never be given the same block.
If a server dies holding 900 unspent keys, those keys are lost. That is fine. We have trillions of them, and the alternative is coordinating on every single write.
Is KGS a single point of failure? Yes, and there are two protections against it. First, a standby takes over if the primary dies. Second, every shortening server still holds a block of keys in memory. So even during a failover, writes keep working.
What about custom aliases? Here, a uniqueness check on the request path is unavoidable. The user chose the value, so we cannot pre-approve it. Try to insert it. If the alias is taken, return an error and ask for another. That is acceptable because custom aliases are rare. Impose a sensible length limit, say 16 characters, so storage stays predictable. This limit is also why the data model stores the short key as varchar(16) rather than a fixed seven characters. The same column has to hold custom aliases too.
What happens on a redirect
This is 99 percent of the traffic, so it is worth examining carefully.
The lookup checks the cache first, and goes to the database on a miss. Then come three checks, each with its own answer:
- The key does not exist: 404 Not Found.
- The link has expired: 410 Gone. This is more accurate than 404. It tells the caller the link existed but is now removed, and the caller treats it differently.
- The link is private and the caller may not see it: 403 Forbidden if they are signed in, 401 Unauthorized if they are not. The distinction matters. 401 means "sign in and try again." 403 means "we know who you are, and the answer is no."
Otherwise, return 302 Found with the original URL in the Location header, and send a click event without waiting for it.
Why 302 and not 301? Both redirect the user. The difference is caching. A 301 says the move is permanent. Browsers cache it, so later clicks never reach our servers at all. That saves us traffic, but it also destroys our click statistics, and analytics is a stated feature. A 302 means every click still reaches our servers. If you did not need per-click analytics, 301 would be the better answer. Saying exactly that is what the interviewer is listening for.
Caching
The read path is a hundred times heavier than the write path, so this is where a change makes the biggest difference.
Put an in-memory cache like Redis in front of the database. Each cache entry holds everything a redirect needs: the original URL, the expiry time, and the visibility flag. On a cache hit, all the checks from the walkthrough above run without touching the database at all.
On a miss, the service reads the row from the database, returns the redirect, and writes the row back into the cache for next time.
Caching is usually hard because of updates. A write can race a cached copy and leave the cache stale. That can't happen here, because a link's destination never changes after it's created. An entry leaves the cache in only two ways, and both are cheap: expiry is checked against the cached row itself on every hit, and a DELETE call evicts the key as part of handling the request.
Size the cache at the 200 GB we estimated. Then tune it from the observed hit rate rather than from theory. Once warm, a cache this size should answer roughly ninety percent of reads. That is the hit rate the redirect walkthrough assumed. LRU (evict the least recently used entry) is the right eviction policy. A link is read most when it is new, so the links being read now are the ones worth keeping.
Cache expired and missing keys too, with a short time to live. Otherwise every request for a dead link becomes a database read.
Partitioning and replication
30 billion rows do not fit on one machine, so we partition the data by the short key. Take a hash of the key and map it to one of N partitions. The keys came from a random generator, so they spread evenly on their own. No partition becomes hotter than another.
Use consistent hashing so that adding or removing a machine moves a small part of the data rather than remapping everything.
You may see range-based partitioning proposed here, splitting on the first character of the key. It is worth knowing why we are not doing that. Range partitioning is the right tool when you need ordered scans, and we have no query that reads a range of keys. It also becomes unbalanced the moment keys stop being uniformly random, for example once custom aliases are included.
Replicate every partition to at least two other machines. Replicas serve reads, which helps with the read-heavy load. They also mean losing a machine does not lose data. Managed stores like DynamoDB and Cassandra do all of this for you. But interviewers ask how it works underneath, so be ready to explain the mechanism rather than the product name.
Expiry and cleanup
Links have an expiry date, so somebody has to remove them. Scanning the database constantly for expired rows would add permanent load for no benefit. So clean up lazily instead:
- When a request arrives for an expired link, return 410 and delete the row then.
- Run a low-priority cleanup job during off-peak hours to remove links nobody has asked for.
Some expired links will therefore stay in the database past their expiry date. That is fine, because they are never served to anyone.
Do not put expired keys back in the pool. It is tempting, because keys look like a resource worth recovering. But think about what a recycled key means. A short link printed on a poster, saved in a bookmark, or embedded in an old email suddenly points at somebody else's destination. That is a security problem, not a tidiness problem. The keyspace is 3.5 trillion, so there is nothing worth recovering anyway.
Analytics
Analytics is a stated requirement. The only rule that matters is this: analytics must never run on the redirect path.
When a redirect happens, the service returns the 302 immediately. It then publishes an event to a queue like Kafka. The event holds the short key, a timestamp, the country from the IP address, the referrer, and the user agent. Consumers read those events in batches. They aggregate the events into an analytics store built for counts and group-bys rather than point reads.
The traffic is manageable: 20,000 events per second at roughly 200 bytes each is about 4 MB per second. A small cluster handles that easily.
One trade-off must be stated plainly: click counts become eventually consistent. They lag behind reality by seconds or minutes. For click statistics that is clearly acceptable. In return, we get a redirect path that never waits on an analytics write.
Abuse and access control
Two different problems get confused here, so separate them.
Abuse is somebody overusing the service. A script creating links as fast as it can will use up the key pool and fill the database. So rate limit link creation per account and per IP address at the API gateway, with different limits for different account tiers. Rate limit redirects too. This protects the service, and it also makes brute-force guessing of keys impractical. That is the other half of the guessability answer from earlier.
Access control is somebody reading a link they should not. Store a visibility flag on each link. For private links, also store the set of user ids permitted to open it. This check runs on the redirect path. That is why the permission data belongs in the same store as the link, not in a separate service you have to call.
Step 7: Bottlenecks and Failure Points
The interviewer does not expect this design to be limitless. They expect you to know where it ends.
One more failure mode is worth naming, because it is the one people miss. The redirect path depends on the cache, the database, and nothing else. Key generation, analytics, and cleanup can all be down, and redirects keep working. That is not an accident. It is the reason the design puts each of them behind a pool or a queue. Say it out loud, because it is the strongest thing about the architecture.
Putting It Together
Read the diagram as two paths. Writes go left, through the shortening service and the key generation service, into the URL database. Reads go right, through the redirect service and the cache. A read touches the database only on a miss. As it returns, it publishes a click event to a queue.
Where AI Fits in This Design
After the main design, interviewers often ask one more question: where would AI fit in this system?
For a URL shortener, start with what does not need AI. Key generation, the redirect path, and caching follow fixed rules and finish in a few milliseconds. AI adds nothing there, and saying so is a strong answer.
The one real fit is screening. A short link hides its destination. That makes shorteners a favorite tool for phishing: tricking a person into opening a fake page built to steal information. So screen what people shorten. After a link is stored, a background worker sends its destination URL to a classifier, a model that labels the page as safe or suspicious. The worker writes that label to the link's row as one more flag.
Notice where this runs: after the write, never during the redirect. The redirect path already checks flags on the row, such as visibility and expiry. A suspicious flag joins the checks that already exist, so the redirect stays as fast as before.
Then say what happens when the model is wrong. A false positive, a safe page marked suspicious, disables a customer's working link. So a flagged link should show a warning page, or go to human review, not be deleted silently.
💡 In the interview: spend your time on key generation and the read path, and move quickly through everything else. The requirements, the estimates, and the API together should take about ten minutes. Two moments give the interviewer most of the information. The first is when you notice that a six-character key breaks the guessability requirement. It shows you check your own design against what you agreed, rather than reciting a known answer. The second is when you explain that KGS exists to move the uniqueness check off the request path. That is a general principle, not just a fact about URL shorteners. If you have time left, 301 versus 302 is the follow-up you are most likely to be asked. Have the analytics reason ready.
Key takeaway: a URL shortener is a small amount of data under a very uneven load, roughly a hundred reads for every write. The whole design follows from that. Pick a key length that actually satisfies the guessability requirement, which means seven base62 characters rather than the usual six. Pre-generate keys in a background service, so no write ever waits on a collision check. Hand keys out in blocks, so no two servers can collide. Serve reads from a cache in front of a partitioned database. Remember that a link's destination never changes, so an update can never make a cached entry wrong. The expiry is stored in the cached row, and a delete removes the key. Return 302 rather than 301 so clicks keep reaching you. Return 410 rather than 404 for expired links. Keep analytics on a queue, so no redirect ever waits on it.
Discussion
On This Page