System Design

Learn System Design

How to Learn System Design?

Scalability

Availability

Latency and Performance

Concurrency and Coordination

Monitoring and Observability

Resilience and Error Handling

Fault Tolerance vs. High Availability

Flashcards Review

Chapter Assessment

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

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

Introduction to DNS

DNS Resolution Process

DNS Load Balancing and High Availability

Flashcards Review

Chapter Assessment

What is a Proxy Server?

Uses of Proxies

VPN vs. Proxy Server

Flashcards Review

Chapter Assessment

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

Introduction to API Gateway

Usage of API gateway

Advantages and disadvantages of using API gateway

Flashcards Review

Chapter Assessment

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

What Is Rate Limiting

Rate Limiting Algorithms

Distributed Rate Limiting

Rate Limiting in Practice

Flashcards Review

Chapter Assessment

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

What is CDN?

Origin Server vs. Edge Server

CDN Architecture

Push CDN vs. Pull CDN

Flashcards Review

Chapter Assessment

Introduction to Data Partitioning

Partitioning Methods

Data Sharding Techniques

Benefits of Data Partitioning

Common Problems Associated with Data Partitioning

Flashcards Review

Chapter Assessment

What is Redundancy?

What is Replication?

Replication Methods

Data Backup vs. Disaster Recovery

Flashcards Review

Chapter Assessment

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

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

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

Introduction to Bloom Filters

Benefits & Limitations of Bloom Filters

Variants and Extensions of Bloom Filters

Applications of Bloom Filters

Flashcards Review

Chapter Assessment

Why Quorum?

What is Quorum?

Flashcards Review

Chapter Assessment

What is Leader and Follower Pattern?

Flashcards Review

Chapter Assessment

What is Heartbeat?

Flashcards Review

Chapter Assessment

What is Checksum?

Uses of Checksum

Flashcards Review

Chapter Assessment

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

What is a Distributed File System?

Architecture of a Distributed File System

Key Components of a DFS

Flashcards Review

Chapter Assessment

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

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

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

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

Quiz

Designing a URL Shortening Service like TinyURL

Designing a URL Shortening Service like TinyURL

Quiz - Designing URL Shortner

Designing Pastebin

Quiz - Designing Pastebin

Designing Instagram

Quiz - Designing Instagram

Designing Dropbox

Quiz - Designing Dropbox

Designing Facebook Messenger

Quiz - Designing Facebook Messenger

Designing Twitter

Quiz - Designing Twitter

Designing Youtube or Netflix

Quiz - Designing Youtube

Designing Typeahead Suggestion

Quiz - Designing Typeahead Suggestion

Designing an API Rate Limiter

Quiz - Designing an API Rate Limiter

Designing Twitter Search

Quiz - Designing Twitter Search

Designing a Web Crawler

Quiz - Designing a Web Crawler

Designing Facebook’s Newsfeed

Quiz - Designing Facebook’s Newsfeed

Designing Yelp or Nearby Friends

Quiz - Designing Yelp or Nearby Friends

Designing Uber backend

Quiz - Designing Uber backend

Designing Ticketmaster

Quiz - Designing Ticketmaster

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

YouTube Likes Counter

Quiz

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

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: 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

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

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: 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

Design Reddit

Quiz

Designing a Notification System

Quiz

Design Google calendar (Medium)

Quiz

Design a Recommendation System for Netflix

Quiz

Design Gmail

Quiz

Design Google News, a Global News Aggregator System (Medium)

Quiz

Design Unique ID Generator (Easy)

Quiz

Design Code Judging System like LeetCode (Medium)

Quiz

Design Payment System

Quiz

Design a Flash Sale for an E-commerce Site (Hard)

Quiz

Design a Reminder Alert System

Quiz

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

Designing a URL Shortening Service like TinyURL

url shortening

rate limiting

caching

availability

+3

hard
·
27 min
·Updated Aug 2026·Credit: Grokking the System Design Interview

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.

Image
A URL shortener does two things: turn a long URL into a short link, and redirect anyone who follows that link back to the original.

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:

Designing a URL Shortener (video)

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

  1. Given a long URL, the service returns a short link.
  2. Following a short link redirects the user to the original URL.
  3. Users can optionally pick their own custom alias.
  4. 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

  1. 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.
  2. Low latency on redirects. A redirect is an extra step the user never asked for. The user should not notice it.
  3. 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.

Image
Every estimate for this system derives from two numbers: 500 million new links a month, and a hundred 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:

  1. Billions of rows.
  2. Every row is small, well under one kilobyte.
  3. One relationship, from a link to the user who created it.
  4. One read pattern that matters: look up a row by its short key.
Image
The data model. One row per short link, keyed by the short key, with click events kept in a separate store.

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.

Image
The write path and the read path. Reads outnumber writes a hundred to one, which is why the cache is on the read side.

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.

Image
Key length against guessability. At 30 billion stored links, six characters leaves more than half the keyspace occupied. Seven does not.

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.

Image
Hashing puts a collision check and a retry loop on the request path. Pre-generating keys moves that same check offline.

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.

Image
The Key Generation Service hands out whole blocks of keys, marking them used before they leave, so two servers can never get the same key.

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.

Image
What happens on a redirect: one lookup, three checks, and four possible answers.

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.

Image
Click tracking belongs on a queue. A counter column on the URL row would put a database write on every single redirect.

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.

Image
Where this design saturates first, and what you say about each limit.

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

Image
The full architecture, with the write path on the left, the read path on the right, and the analytics pipeline attached to the redirect service.

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
Have a question or insight about this topic? Share it with the community.
Reading Progress
0%

On This Page