Learn how to design a URL shortener in this complete mock system design interview. We’ll cover Base62 encoding, Redis caching, database sharding, Snowflake IDs, consistent hashing, and high availability from requirements to production-ready architecture.
The Interview Begins
The conference room is quiet.
A whiteboard fills one wall, its surface still marked with faint traces of diagrams from previous interviews.
Hopefully they got the job.
The interviewer walks in carrying nothing but a laptop and a dry-erase marker.
He smiles.
Interviewer: Good morning.
Candidate: Good morning.
We exchange a few pleasantries before taking our seats.
The interviewer places the marker on the table between us.
Not in my hand.
Just close enough to remind me it’s waiting.
Interviewer: Let’s start with a fairly straightforward system design question.
Straightforward?
That’s interviewer for “this probably won’t be straightforward.”
He slides the marker toward me.
Interviewer: Imagine we’re building a service like TinyURL. How would you design it?
TinyURL…
I’ve definitely used it.
…which somehow doesn’t help me at all right now.
For a brief moment, my brain starts throwing out random technologies.
Redis.
Load balancer.
Database.
Wait… why am I already thinking about Redis?
My eyes drift toward the whiteboard.
I could stand up.
Draw a few boxes.
Connect them with arrows.
Congratulations.
You’ve successfully designed… something.
Whether it’s the right system is another question.
I take a slow breath.
Don’t try to sound smart.
Try to understand the problem.
Understanding the Requirements
I look back at the interviewer.
Candidate: Before discussing the architecture, I’d like to clarify a few requirements.
The interviewer nods.
A small smile appears.
Interviewer: That’s exactly where I’d start.
Okay…
That’s encouraging.
📒 Seattle Engineer Notes
Many junior engineers think they need to impress the interviewer
by drawing architecture immediately.
Experienced interviewers usually care much more about your
thinking process.
Requirements come first.
Architecture comes second.
The interviewer leans back.
Interviewer: Go ahead. What would you like to know?
I finally pick up the marker.
Alright.
Let’s earn it.
The interviewer leans back.
Interviewer: Go ahead. What would you like to know?
I finally pick up the marker.
Alright.
Requirements first.
Candidate: I’d like to understand what features we’re building.
Are users allowed to create custom aliases, or are short URLs always generated automatically?
The interviewer answers almost immediately.
Interviewer: Let’s keep the first version simple.
All short URLs are generated automatically.
I write the first note on the whiteboard.
- ✓ Auto-generated short URLs
- ✗ No custom aliases
One less decision to make.
Candidate: Do shortened URLs ever expire?
The interviewer thinks for a moment.
Interviewer: For this interview, let’s assume they don’t.
Another note.
- ✓ URLs never expire
Future me appreciates fewer edge cases.
Candidate: Will users need to log in before creating short URLs?
Interviewer: No authentication.
Anyone can create a short URL.
I add another bullet.
- ✓ Public service
- ✗ Authentication
The whiteboard is still mostly empty.
But for the first time…
It feels like I’m designing this system.
Not a system.
📒 Seattle Engineer Notes
Notice what's happening.
We still haven't discussed databases.
We haven't talked about Redis.
We haven't even drawn a single box.
Every answer from the interviewer is narrowing the solution
space.
The fewer assumptions you make, the fewer mistakes you'll have
to correct later.
The interviewer glances at my notes.
Interviewer: Anything else?
I pause.
I know the functional requirements now.
But architecture isn’t driven by features alone.
Traffic changes everything.
Understanding the Scale
The interviewer looks at the notes I’ve written in the corner of the whiteboard.
He nods.
Interviewer: Good.
Before we jump into the architecture, let’s talk about scale.
Here it comes.
Every system design interview eventually gets to the numbers.
Interviewer: Let’s assume we have around 10 million active users.
Each month, they create approximately 100 million new short URLs.
Most traffic, however, comes from people clicking existing links.
Let’s say we receive around 1 billion redirect requests every month.
I write the numbers down.
- Active users: 10 million
- New URLs: 100 million/month
- Redirects: 1 billion/month
For a second, I simply stare at the whiteboard.
One billion.
Everything sounds intimidating when someone says “one billion.”
Then I remind myself.
Don’t focus on the total.
Break it down.
Candidate: I’d like to estimate the average requests per second before making any architectural decisions.
The interviewer nods.
Interviewer: Sounds good.
I write the monthly numbers at the top of the whiteboard.
New URLs
100,000,000 / month
Okay… let’s make this easier to reason about.
100,000,000 / 30 days
≈ 3,333,333 URLs / day
Still too big.
3,333,333 / 24 hours
≈ 138,889 URLs / hour
Keep going.
138,889 / 60 minutes
≈ 2,315 URLs / minute
2,315 / 60 seconds
≈ 38 writes / second
I circle the final number.
38 writes/sec
That’s… much lower than I expected.
Then do the same for redirects.
Redirect Requests
1,000,000,000 / month
1,000,000,000 / 30 days
≈ 33,333,333 / day
33,333,333 / 24 hours
≈ 1,388,889 / hour
1,388,889 / 60 minutes
≈ 23,148 / minute
23,148 / 60 seconds
≈ 385 reads / second
I underline the two numbers.
Writes : 38/sec
Reads : 385/sec
Ten times more reads than writes.
Now the architecture is starting to tell me what it wants.
📒 Seattle Engineer Notes
You don't have to calculate every number exactly during an
interview.
Interviewers usually care more about your approach than your
mental math.
Round numbers are perfectly acceptable as long as you clearly
state your assumptions.
Designing the API
The interviewer looks at the whiteboard one more time.
Interviewer: Great.
Now that we understand the requirements and expected scale…
How would clients interact with this service?
Okay.
No infrastructure yet.
Just the interface.
I think for a moment.
Every system starts with an API.
If I can’t explain how users interact with it, the architecture doesn’t matter.
Candidate: Since users can create a short URL and later use it to access the original URL, I think we only need two core APIs.
The interviewer nods.
Interviewer: Walk me through them.
I start writing on the whiteboard.
POST /api/v1/urls
Request
{
"longUrl": "https://www.example.com/very/long/url"
}
Response
{
"shortUrl": "https://tiny.url/Ab3XkP"
}
I pause.
Simple enough.
GET /Ab3XkP
HTTP/1.1 302 Found
Location: https://www.example.com/very/long/url
The URL Shortener returns an HTTP 302 Found response with a Location header. The browser automatically follows the redirect by sending a new request to the original URL.
The interviewer nods.
Interviewer: Exactly.
He pauses for a moment before asking another question.
Interviewer: At a high level, what happens inside our service when someone clicks a short URL?
Okay… let’s think through the request lifecycle.
I walk back to the whiteboard.
Candidate: When the request reaches our service, we first extract the short code from the URL.
For example, if the user visits:
https://tiny.url/Ab3XkP
we extract:
Ab3XkP
Then we look up that short code in our database to find the corresponding long URL.
If the short code exists, we return an HTTP 302 Found response with the original URL in the Location header.
If it doesn’t exist, we return a 404 Not Found.
The interviewer smiles.
Interviewer: Nice. Can you sketch the flow?
I draw a simple diagram.
Browser URL Shortener Database
│ │ │
│── GET /Ab3XkP ───▶│ │
│ │── Lookup code ───▶│
│ │◀── Long URL ──────│
│◀── 302 Found ─────│ │
│ Location: long URL │
│
│── GET long URL ──────────────────────▶ Destination Server
I put the marker down and looked at the diagram one more time.
The overall flow feels surprisingly simple.
Receive a request. Look up the short code. Redirect the user.
The interviewer nodded.
Interviewer: I like the high-level design.
He tapped the box labeled Database.
Interviewer: Suppose our service becomes even more popular. Every redirect requires a database lookup.
Do you see any potential concerns?
Hmm…
One billion redirects every month.
That means we’re hitting the database every single time someone clicks a link.
I thought about it for a moment.
Candidate: The database could become a bottleneck.
Even though our write traffic is relatively low, every redirect requires reading the original URL. As traffic grows, the database will receive an enormous number of read requests.
The interviewer smiled.
Interviewer: Exactly.
Candidate: Since the mapping between a short code and its long URL rarely changes, it seems like a good candidate for caching.
The interviewer nodded again.
Interviewer: That’s a good observation.
We’ll come back to caching later.
For now, let’s assume the database lookup is fast enough.
He uncapped the marker and drew a small empty box next to the URL Shortener.
Interviewer: Before we worry about performance, we have another problem to solve.
Every new URL needs a unique short code.
How would you generate one?
Right.
We’ve been talking about how to retrieve URLs.
Now we need to figure out how they’re created.
Generating Short URLs
The interviewer waited.
He wasn’t looking for a specific answer.
He was waiting to hear how I would think.
Every new URL needs a unique short code.
Seems simple enough.
Until you realize the entire system depends on getting this one decision right.
I picked the marker back up.
Candidate: Before choosing an approach, I’d like to think about what makes a good short URL.
The interviewer smiled.
Interviewer: That’s a good place to start.
I wrote a new heading on the whiteboard.
Requirements for a Short Code
Then I started making a list.
- Every short code must be unique.
- It should be as short as possible.
- It should be quick to generate.
- It should scale as the number of URLs grows.
- Ideally, it should avoid collisions.
I looked back at the interviewer.
Candidate: There are probably several ways we could generate these identifiers.
For example, we could use a hash of the original URL.
We could generate a random string.
Or we could assign each URL a unique numeric ID and encode it into a shorter representation.
The interviewer nodded.
Interviewer: Good.
Let’s examine each option.
Perfect.
He’s not expecting me to know the “correct” answer immediately.
He wants to see how I evaluate trade-offs.
📒 Seattle Engineer Notes
System design interviews are rarely about finding the one
correct solution.
They're about comparing multiple reasonable solutions,
understanding their trade-offs, and explaining why you chose one
over another.
Strong candidates don't jump to an answer.
They explore the design space first.
The interviewer pointed to the first item I’d mentioned.
Interviewer: Let’s start with hashing.
Suppose we hash the original URL.
Would that work?
I stared at the whiteboard.
Hashing sounds reasonable…
But if it’s that simple, there probably wouldn’t be an entire interview question about URL shorteners.
I uncapped the marker again.
Alright.
Let’s test the idea.
Evaluating Option 1: Hashing the Original URL
The interviewer pointed to the first idea on the whiteboard.
Interviewer: You mentioned hashing the original URL.
Walk me through how that would work.
I nodded.
Candidate: Every URL would be passed through a hash function, such as SHA-256.
The hash function always produces the same output for the same input.
I wrote an example.
https://www.example.com/very/long/url
│
▼
SHA-256
│
▼
2cf24dba5fb0a30e26e83b2ac5b9e29e...
The interviewer looked at the whiteboard.
Interviewer: Why might that property be useful?
I thought for a second.
Candidate: Since the same URL always produces the same hash, we don’t have to store any additional information to regenerate it.
If someone shortens the exact same URL twice, we’d get the same short code both times.
The interviewer nodded.
Interviewer: That’s one advantage.
Then he tapped the hash with the marker.
Interviewer: But take another look at it.
Anything seem odd?
I stared at the string.
It’s supposed to be a short URL…
…but that hash doesn’t look very short.
Candidate: The hash is actually much longer than the original URL.
The interviewer smiled.
Interviewer: Exactly.
A SHA-256 hash is always 256 bits, which is typically represented as 64 hexadecimal characters.
For a URL shortening service, that’s hardly an improvement.
Right.
We solved uniqueness…
…by making the URL even longer.
Candidate: We could truncate the hash.
Maybe just keep the first six or seven characters.
The interviewer folded his arms.
Interviewer: Interesting.
Suppose these are the hashes for two completely different URLs.
URL A
2cf24dba5fb0a30e...
URL B
2cf24d91e83ac761...
He drew a line through everything after the first six characters.
URL A → 2cf24d
URL B → 2cf24d
I immediately saw the problem.
We’ve thrown away most of the information.
Candidate: Both URLs would generate the same short code.
The interviewer nodded.
Interviewer: Exactly.
That’s called a collision.
Hash functions are designed to minimize collisions when you use the entire hash.
The moment you truncate the output, you increase the chance that different inputs produce the same shortened code.
📒 Seattle Engineer Notes
Hashing isn't a bad idea.
In fact, cryptographic hash functions are designed to make
collisions extremely unlikely.
The problem is that URL shorteners need *short* codes.
Once you truncate a hash to only a few characters, you're no
longer using the full strength of the hash, and collisions become
a practical concern.
The interviewer erased the example hashes.
Interviewer: So hashing has a few drawbacks.
Can you think of another approach?
I looked back at the list I’d written earlier.
Maybe… instead of deriving the code from the URL…
…we generate one ourselves.
Evaluating Option 2: Randomly Generated Short Codes
I looked back at the list I’d written earlier.
- Hash the original URL
- Generate a random string
- Assign a unique ID
The second option caught my attention.
Candidate: Instead of deriving the short code from the URL, we could simply generate one at random.
The interviewer nodded.
Interviewer: Tell me more.
I walked to the whiteboard and wrote a few examples.
Request 1
Ab3XkP
Request 2
Qr92Lm
Request 3
xY7aBc
I stepped back.
Candidate: Every time a user creates a short URL, we randomly generate a six-character string.
The interviewer nodded.
Interviewer: That certainly gives us short URLs.
He paused.
Interviewer: But how do you know Ab3XkP hasn’t already been assigned to someone else?
Right…
Random doesn’t necessarily mean unique.
I uncapped the marker again.
Generate Random Code
│
▼
Check Database
│
Exists?
/ \
Yes No
│ │
Generate Save
Another Mapping
Code
Candidate: We’d have to check whether the generated code already exists.
If it does, we simply generate another one and try again.
The interviewer nodded.
Interviewer: Would that work?
I thought for a moment.
Most of the time… probably.
Candidate: Early on, yes.
When only a small percentage of all possible short codes have been used, the chance of generating a duplicate is very low.
The interviewer smiled.
Interviewer: And what happens as the service grows?
I imagined millions…
Then billions…
…of existing short URLs.
The empty pool of available codes was slowly shrinking.
Candidate: The probability of collisions increases.
That means we spend more time generating new codes and checking the database before finding an unused one.
The interviewer nodded.
Interviewer: Exactly.
Random generation works well when the namespace is mostly empty.
As utilization grows, retries become more frequent, increasing latency and unnecessary database lookups.
📒 Seattle Engineer Notes
Random code generation is simple and widely used.
The challenge isn't generating a random string.
It's guaranteeing uniqueness.
Every generated code must be checked before it can be used,
which means additional database reads and occasional retries
when collisions occur.
The interviewer erased the flowchart.
Interviewer: We’ve looked at hashing.
We’ve looked at random
Evaluating Option 3: Assigning a Unique ID
The interviewer erased the flowchart from the whiteboard.
Interviewer: So random generation works, but we may need multiple attempts before finding an unused code.
Can you think of another approach that guarantees uniqueness from the start?
I looked back at the three ideas I’d written earlier.
Hash the original URL
Generate a random string
Assign a unique ID
If randomness creates uncertainty…
…maybe we don’t need randomness at all.
Candidate: Instead of generating a random short code, we could assign every URL a unique numeric ID.
The interviewer nodded.
Interviewer: Tell me more.
I drew a simple table on the whiteboard.
+----+-----------------------------------------------+
| ID | Long URL |
+----+-----------------------------------------------+
| 1 | https://www.example.com/very/long/url |
| 2 | https://www.amazon.com |
| 3 | https://www.github.com |
+----+-----------------------------------------------+
Candidate: Whenever a new URL is created, the database assigns the next available ID.
Since every ID is unique, we automatically avoid collisions.
The interviewer smiled.
Interviewer: I like that.
Any drawbacks?
I looked at the table again.
Well…
The ID is unique.
But it isn’t exactly short.
I imagined someone sharing this URL.
https://tiny.url/123456789
That’s not terrible…
But it doesn’t really look like TinyURL.
Candidate: As the number of URLs grows, the IDs become longer and longer.
The interviewer nodded.
Interviewer: Exactly.
A sequential integer guarantees uniqueness.
But we’d like something more compact.
He uncapped the marker and wrote two numbers.
125
1000000
Then he looked at me.
Interviewer: What if we could represent the same number using fewer characters?
I paused.
Represent the same number…
Without changing its value?
Kind of like writing ten in binary instead of decimal?
The interviewer smiled.
Interviewer: Have you heard of Base62 encoding?
I nodded slowly.
I’ve heard the term before…
But I’ve never really thought about why it works.
📒 Seattle Engineer Notes
So far, we've evaluated three approaches.
✓ Hashing
• Deterministic
• Requires truncation to keep URLs short
• Truncation introduces collision risk
✓ Random Generation
• Produces short codes
• Requires duplicate checks and possible retries
✓ Sequential IDs
• Guaranteed unique
• Simple to generate
• Needs a way to produce shorter, user-friendly codes
Next, we'll solve that final problem using Base62 encoding.
Encoding IDs with Base62
The interviewer wrote two numbers on the whiteboard.
125
1,000,000
He looked at me.
Interviewer: We agreed that assigning each URL a unique numeric ID guarantees uniqueness.
The only remaining problem is that decimal numbers get longer over time.
How might we make them shorter?
I stared at the numbers.
We can’t change their values.
But… maybe we can change how we represent them.
Candidate: Are we talking about using a different number system?
The interviewer smiled.
Interviewer: Exactly.
He wrote another example.
Decimal
10
Then underneath it:
Binary
1010
Interviewer: These look different.
Are they different numbers?
Candidate: No.
They’re just different representations of the same value.
The interviewer nodded.
Interviewer: Right.
Changing the representation doesn’t change the value.
It only changes how we write it.
He turned back to the whiteboard.
Interviewer: Decimal uses ten symbols.
Can you name them?
I wrote:
0 1 2 3 4 5 6 7 8 9
Candidate: Ten symbols.
Base 10.
The interviewer nodded again.
Interviewer: What if we allowed ourselves to use more symbols?
Instead of only ten digits…
…suppose we also used uppercase and lowercase letters.
He wrote another list.
0 1 2 ... 9
A B C ... Z
a b c ... z
Then he counted them.
10 digits
26 uppercase letters
26 lowercase letters
--------------------
62 symbols
Interviewer: This is called Base62.
Instead of counting with ten symbols, we count with sixty-two.
I looked back at the list.
Sixty-two symbols…
That means each “digit” can represent much more information.
The interviewer nodded, almost as if he’d read my mind.
Interviewer: Exactly.
The more symbols each position can represent, the fewer characters we need to write the same number.
He pointed back at the two IDs.
125
1,000,000
Interviewer: We can encode these IDs into Base62.
The values don’t change.
Only their representation does.
📒 Seattle Engineer Notes
Base62 is an encoding, not a hash.
Hashing transforms data into a fixed-size fingerprint.
Encoding simply represents the same value using a different set
of symbols.
For URL shorteners, Base62 is attractive because it uses:
• 0–9
• A–Z
• a–z
This allows large numeric IDs to be represented with fewer
characters than decimal.
I uncapped the marker one more time.
Candidate: So if the database assigns an ID of 125, we don't expose 125 directly.
Instead, we convert it into its Base62 representation and use that as the short code.
The interviewer smiled.
Interviewer: Exactly.
And when someone visits that short code?
I nodded.
We simply reverse the process.
Decode the Base62 string back into the original ID...
Look up the record...
Return the redirect.
For the first time since the interview began...
The entire system finally fits together.
Creating a Short URL
The interviewer looked at the whiteboard, now covered with notes, diagrams, and crossed-out ideas.
Interviewer: We’ve talked about generating unique IDs and encoding them with Base62.
Let’s put everything together.
Suppose I submit a brand new URL.
What happens?
I looked back at the empty space on the whiteboard.
This is the first time we’ve followed the entire write path.
I uncapped the marker.
Candidate: Let’s say the user submits this URL.
https://www.example.com/very/long/url
The interviewer nodded.
Candidate: First, our service receives the request.
It validates the URL to make sure it’s well-formed.
Then we create a new record in the database.
I started drawing another sequence diagram.
Client URL Shortener Database
│ │ │
│── POST /urls ────▶│ │
│ │── INSERT URL ────▶│
│ │◀──── ID = 125 ────│
The interviewer watched quietly.
Choosing the Database
I had just finished explaining how we could assign each URL a unique numeric ID before encoding it with Base62.
The interviewer leaned back in his chair.
Interviewer: You mentioned assigning sequential IDs.
Where do those IDs come from?
I paused.
I jumped straight to IDs without explaining where they originate.
Candidate: We’d need to store each URL mapping in a database.
If we chose a relational database, we could let the database generate a unique ID whenever we insert a new record.
The interviewer nodded but didn’t seem completely satisfied.
Interviewer: Why a relational database?
I smiled.
Fair question.
I shouldn’t assume SQL is the only answer.
I walked back to the whiteboard and wrote two columns.
| Relational Database | NoSQL Database |
|---|---|
| Structured tables | Key-value or document storage |
| Supports transactions | Optimized for scalability |
| Auto-increment IDs | No built-in sequential IDs |
| Strong consistency | Often eventually consistent |
The interviewer looked at the table.
Interviewer: Which one would you choose for this system?
I thought for a moment.
What does our application actually do?
We’re not joining multiple tables.
We’re not running complex queries.
Almost every request is just a lookup.
I picked up the marker.
Candidate: Our data model is actually very simple.
Each short code maps to exactly one long URL.
Short Code ─────────► Long URL
We’re primarily doing two operations:
- Create a new mapping
- Retrieve a mapping by its short code
Nothing more.
The interviewer nodded.
Candidate: Because our access pattern is essentially key-value lookups, a distributed NoSQL database would be a perfectly reasonable choice.
It scales well and handles high read traffic efficiently.
He smiled.
Interviewer: Then why not choose NoSQL?
I looked back at the whiteboard.
Because we’ve already committed ourselves to sequential IDs.
Candidate: If we use sequential numeric IDs and encode them with Base62, a relational database gives us a very convenient way to generate those IDs using an auto-incrementing primary key.
That keeps the design simple.
The interviewer nodded.
Interviewer: That’s a reasonable tradeoff.
Simple designs are often the best place to start.
📒 Seattle Engineer Notes
In a system design interview, it's often better to start with the
simplest solution that satisfies the requirements.
A relational database provides:
• Guaranteed unique IDs
• Simple inserts
• Easy primary key lookups
As the system grows, we can revisit this decision and discuss how
to scale or replace components.
Interviewers usually prefer a correct, simple design before a
highly distributed one.
The interviewer erased a small corner of the whiteboard.
Interviewer: Great.
So let’s assume we’re using a relational database that assigns each new URL a unique numeric ID.
He wrote:
125
Then he circled it.
Interviewer: We still have one problem.
We’re not going to give users this.
https://tiny.url/125
He looked at me.
Interviewer: How do we turn that number into something shorter?
I smiled.
Now it’s finally time for Base62.
Encoding the ID with Base62
The interviewer circled the number on the whiteboard.
125
Interviewer: We have our unique ID.
Now let’s turn it into a short code.
How would you do that?
I looked at the number for a moment.
We already know Base62 is the answer.
The question is… how do we actually perform the conversion?
I uncapped the marker.
Candidate: It reminds me of how we convert decimal numbers into binary.
The interviewer smiled.
Interviewer: Good connection.
Can you explain?
I nodded.
Candidate: When converting from decimal to binary, we repeatedly divide the number by 2 and record the remainder.
The interviewer picked up another marker.
Interviewer: Exactly.
Base62 works the same way.
The only difference is that instead of dividing by 2…
He wrote:
62
…we divide by 62.
I leaned closer.
So the algorithm isn’t new at all.
It’s the same base conversion I learned years ago.
The interviewer wrote our symbol table.
Value Symbol
0-9 0-9
10-35 A-Z
36-61 a-z
Interviewer: Every remainder maps to one of these symbols.
Let’s try it with 125.
He wrote the first step.
125 ÷ 62 = 2 remainder 1
Interviewer: What does a remainder of 1 correspond to?
I looked at the table.
Candidate: The character "1".
He wrote it off to the side.
1
Then he continued.
2 ÷ 62 = 0 remainder 2
Interviewer: And the remainder?
Candidate: "2".
Now the whiteboard showed:
Remainders
1
2
I frowned.
That would give us “12”…
The interviewer noticed my expression.
Interviewer: Something bothering you?
I nodded.
Candidate: When we convert decimal to binary…
…don’t we read the remainders in reverse order?
The interviewer grinned.
Interviewer: Exactly.
He flipped them around.
2
1
Then boxed the answer.
21
Interviewer: The Base62 representation of decimal 125 is:
21
I stared at it for a second.
That’s surprisingly simple.
No hashing.
No randomness.
Just repeated division and remainders.
📒 Seattle Engineer Notes
Converting a number to Base62 follows the same process used for
converting decimal numbers to binary or hexadecimal.
1. Divide the number by 62.
2. Record the remainder.
3. Continue dividing the quotient by 62.
4. Stop when the quotient becomes 0.
5. Read the remainders in reverse order.
The only new concept is the symbol table that maps values
0–61 to characters.
The interviewer capped his marker.
Interviewer: Now imagine someone visits:
https://tiny.url/21
He tapped the short code.
Interviewer: How do we find the original URL?
I smiled.
We’ve already built every piece we need.
I picked up the marker.
Candidate: When a user clicks the shortened URL, the browser sends a request to our service.
GET /21
The service extracts the short code from the request and decodes it back into its original numeric ID.
Using that ID, we retrieve the corresponding URL from the database.
I sketched the request flow.
Browser URL Shortener Database
│ │ │
│── GET /21 ───────▶│ │
│ │ Decode → ID │
│ │── Lookup ────────▶│
│ │◀── Long URL ──────│
Candidate: Once we have the original URL, we don’t return the webpage itself.
Instead, we send an HTTP redirect back to the browser.
HTTP/1.1 302 Found
Location: https://www.example.com/very/long/url
Candidate: The browser automatically follows the Location header and requests the destination website.
I finished the diagram.
Browser URL Shortener Database Destination Website
│ │ │ │
│── GET /21 ───────▶│ │ │
│ │── Lookup ────────▶│ │
│ │◀── Long URL ──────│ │
│◀── 302 Redirect ──│ │ │
│ │
│──────── GET https://www.example.com ──────────────────▶│
I stepped back and looked at the whiteboard.
The redirect path is surprisingly simple.
A single lookup.
A single redirect.
The interviewer nodded.
Interviewer: Nice.
We’ve built a system that works.
Then he walked over to the sequence diagram and circled one line.
We’ve built a system that works.
Then he walked over to the sequence diagram and circled one line.
Database Lookup
Interviewer: But every redirect performs a database lookup.
Earlier, we estimated roughly 385 redirect requests per second.
What happens if our service becomes ten times more popular?
I looked back at the database.
Every click…
Every redirect…
Every request depends on this one component.
That’s going to become our bottleneck.
The interviewer smiled.
Interviewer: Exactly.
How can we reduce the load on the database?
Scaling the Read Path
I paused for a moment.
Most shortened URLs never change once they’re created.
If thousands of people click the same link…
Why are we performing the exact same database lookup every single time?
I looked back at the architecture on the whiteboard.
Browser
│
▼
URL Shortener Service
│
▼
Database
Every request followed the same path.
No matter how many times someone requested the same short URL…
…we always ended up querying the database.
I picked up the marker.
Candidate: We don’t necessarily need to hit the database every time.
The interviewer raised an eyebrow.
Interviewer: Go on.
Candidate: URL mappings are essentially read-only after they’re created. If we’ve already looked up a short URL once, we can keep that mapping somewhere much faster to access.
The interviewer nodded.
Interviewer: Somewhere faster than the database?
I smiled.
Candidate: In memory.
I drew another box between the service and the database.
Browser
│
▼
URL Shortener Service
│
┌──────┴──────┐
▼ ▼
In-Memory Cache Database
The interviewer studied the diagram.
Interviewer: Walk me through a redirect request now.
I added another sequence diagram.
Browser URL Shortener In-Memory Cache
│ │ │
│── GET /21 ────────▶│ │
│ │── Lookup ─────────▶│
│ │◀── Long URL(hit)── │
│◀── 302 Redirect ───│ │
Candidate: If the mapping already exists in the cache, we can immediately return the redirect.
The database isn’t involved at all.
The interviewer nodded.
Interviewer: That’s the best-case scenario.
Then he erased the cached value.
Interviewer: What if the cache doesn’t contain it?
I thought for a second.
The cache won’t always have every URL.
I extended the diagram.
Browser URL Shortener Cache Database
│ │ │ │
│── GET /21 ───▶│ │ │
│ │── Lookup ──▶│ │
│ │◀── Miss ────│ │
│ │ │ │
│ │────────────── Lookup ───▶ │
│ │◀───────────── Long URL ── │
│ │── Store ───▶│ │
│◀── 302 Redirect│ │ │
Candidate: On a cache miss, we retrieve the mapping from the database…
…store it in the cache…
…and then return the redirect.
The next request for the same short URL can be served directly from memory.
The interviewer smiled.
Interviewer: Exactly.
That’s a very common caching strategy.
Do you know what it’s called?
I thought for a moment.
Then it clicked.
Candidate: Cache-aside.
The interviewer nodded approvingly.
📒 Seattle Engineer Notes
This approach is known as the **Cache-Aside** pattern.
For each request:
1. Check the cache.
2. If the data exists, return it immediately.
3. Otherwise, retrieve it from the database.
4. Store the result in the cache.
5. Return the response.
Because URL mappings rarely change and popular links are requested
repeatedly, this pattern can significantly reduce database traffic
while keeping the application logic straightforward.
The interviewer tapped the box labeled In-Memory Cache.
Interviewer: I like the idea.
One last question.
What technology would you use to implement this cache?
Candidate: I’d use Redis.
The interviewer nodded.
Interviewer: Why Redis?
Candidate: Redis is an in-memory key-value store.
Our service performs a simple lookup: given a short code, return the corresponding long URL. Since Redis keeps data in memory, it can retrieve these mappings much faster than querying the database. It’s a natural fit for this access pattern.
The interviewer smiled.
Interviewer: Exactly. We’re not replacing the database.
We’re simply avoiding unnecessary trips to it.
The interviewer capped his marker.
Interviewer: Our redirects are much faster now.
But we’ve only solved one problem.
He circled the database.
Database
Interviewer: What happens when a single database can no longer store all of our URL mappings?
I looked back at the architecture.
Caching reduces read traffic…
But eventually, one database won’t be enough.
Now we have to think about scaling the data itself.
Scaling the Write Path
I looked back at the diagram.
Browser
│
▼
URL Shortener Service
│
┌──────┴──────┐
▼ ▼
In-Memory Cache Database
Redis has significantly reduced our read traffic.
But every newly created short URL still has to be written to the same database.
And every cache miss still depends on it.
I turned back to the interviewer.
Candidate: Eventually, a single database becomes a bottleneck.
The interviewer nodded.
Interviewer: In what ways?
I thought for a moment before writing three bullet points.
• Storage
• Write Throughput
• Availability
Candidate: First, storage.
As more users create shortened URLs, the database continues to grow. Eventually, one machine simply won’t have enough capacity.
Second, write throughput.
Even though Redis serves many read requests, every new URL still needs to be inserted into this database. At some point, a single server can’t keep up with the write volume.
I paused.
There’s one more…
Candidate: It’s also a single point of failure.
If this database goes down, we can’t create new short URLs. And when a requested mapping isn’t in the cache, we won’t be able to retrieve it either.
The interviewer smiled.
Interviewer: Exactly.
Caching reduced our read bottleneck.
It didn’t eliminate our database bottleneck.
He erased the single database box.
Then he drew four smaller ones.
┌─────┐ ┌─────┐
│ DB1 │ │ DB2 │
└─────┘ └─────┘
┌─────┐ ┌─────┐
│ DB3 │ │ DB4 │
└─────┘ └─────┘
Interviewer: Suppose we replace one database with four.
How should we decide which database stores a newly created URL?
I stared at the whiteboard.
This isn’t just about adding more databases.
We need a rule that every application server can follow consistently.
Otherwise, we’d never know where to look.
Choosing a Sharding Strategy
The interviewer pointed at the four database shards on the whiteboard.
Interviewer: Splitting the data across multiple databases solves our storage problem.
But it creates another.
How does the application know which database should store a newly created URL?
I stared at the diagram.
Every application server has to make the same decision.
Otherwise, one server might write to DB1 while another searches DB3.
We need a deterministic rule.
I picked up the marker.
Candidate: One option is range-based sharding.
I wrote beneath the diagram.
DB1 : IDs 1 - 250M
DB2 : IDs 250M - 500M
DB3 : IDs 500M - 750M
DB4 : IDs 750M - 1B
Candidate: Whenever we generate a new ID, we simply check which range it belongs to and store it in the corresponding database.
Interviewer: That’s a reasonable approach.
He looked back at the whiteboard for a moment.
Interviewer: But I have another question.
Earlier in the interview, how were we generating these IDs?
I answered immediately.
Candidate: We relied on the database’s auto-incrementing primary key.
The interviewer nodded.
Then he pointed at the four database shards.
Interviewer: Which database generates the next ID now?
I froze.
Wait…
Earlier there was only one database.
There was only one sequence.
Now there are four independent databases.
I quickly wrote an example.
DB1 → 1, 2, 3, 4 ...
DB2 → 1, 2, 3, 4 ...
DB3 → 1, 2, 3, 4 ...
DB4 → 1, 2, 3, 4 ...
I looked back at the interviewer.
Candidate: If every database generates IDs independently…
they’ll all produce the same values.
That means different URLs could end up with identical IDs.
Since our short URL is derived from the ID, we’d generate duplicate short URLs.
The interviewer smiled.
Interviewer: Exactly.
Our original design assumed there was a single database responsible for generating IDs.
That assumption no longer holds once we introduce multiple shards.
He capped the marker.
Interviewer: So before we decide how to distribute writes…
we first need to answer another question.
How do we generate globally unique IDs across the entire system?
Interviewer: How would you solve that?
I thought for a moment.
We need IDs that are globally unique.
The databases can’t generate them independently anymore.
So perhaps we should generate them before writing to the database.
I picked up the marker.
Candidate: One option is to introduce a dedicated ID generation service.
I drew a new component.
Browser
│
▼
URL Shortener Service
│
▼
ID Generation Service
│
▼
Database Shards
Candidate: Before storing a new URL, the application requests a unique ID from this service.
Once it receives the ID, it determines the destination shard and writes the record.
The interviewer nodded.
Interviewer: That certainly guarantees uniqueness.
Then he asked another question.
Interviewer: What happens if this service becomes unavailable?
I looked back at the diagram.
Every URL creation now depends on this one service.
If it goes down…
I answered.
Candidate: Then we wouldn’t be able to generate new short URLs.
It becomes another single point of failure.
The interviewer smiled.
Interviewer: Exactly.
Just like our original database, we’ve moved the bottleneck instead of eliminating it.
I nodded.
So the challenge isn’t just generating unique IDs.
We also need the ID generator itself to scale and remain highly available.
📒 Seattle Engineer Notes
A centralized ID generation service guarantees globally unique IDs.
However, every write request depends on it.
When introducing a new component into a distributed system, always ask:
• Can it scale?
• Is it highly available?
• Does it become a single point of failure?
Solving one bottleneck by introducing another is a common design pitfall.
The interviewer uncapped the marker again.
Interviewer: There are several ways to generate globally unique IDs in distributed systems.
Can you name a few?
I started writing a list.
• UUID
• Snowflake IDs
• Centralized ID Service
The interviewer pointed at the first item.
Interviewer: Let’s start with UUIDs.
What do you know about them?
I thought for a moment.
Candidate: UUID stands for Universally Unique Identifier.
Instead of relying on a central database or service, each application server can generate IDs independently.
The interviewer nodded.
Interviewer: Why is that useful?
I answered.
Candidate: Since every server generates its own IDs, there’s no coordination required.
That means we don’t have a single service responsible for issuing IDs, so the system scales naturally as we add more application servers.
The interviewer smiled.
Interviewer: Exactly.
The interviewer walked back to the whiteboard.
Interviewer: That sounds promising.
No central bottleneck.
No extra network request just to generate an ID.
He paused.
Interviewer: But remember what we’re building.
I looked back at the diagram.
Long URL
│
▼
Generate Numeric ID
│
▼
Base62 Encode
│
▼
Short URL
Interviewer: Does UUID fit naturally into this design?
I stared at the flow for a moment.
Our short URL comes from encoding a numeric ID…
But a UUID isn’t a simple number.
I shook my head.
Candidate: Not really.
UUIDs are 128-bit identifiers.
They’re much larger than the sequential IDs we’ve been using.
The interviewer nodded.
He wrote an example.
550e8400-e29b-41d4-a716-446655440000
Interviewer: This is a UUID.
What happens if we Base62-encode it?
I thought for a moment.
Base62 makes the representation shorter…
But it doesn’t reduce the amount of information.
I answered.
Candidate: The encoded value would still be relatively long because we’re still representing a 128-bit identifier.
The interviewer smiled.
Interviewer: Exactly.
Encoding changes how the data is represented.
It doesn’t change how much data there is.
He quickly sketched a comparison.
Sequential ID
125
↓
Base62
21
Then beside it:
UUID (128 bits)
↓
Base62
Still much longer
I nodded.
UUID solves the uniqueness problem…
But it works against one of the primary goals of a URL shortener.
Candidate: We’d end up with longer short URLs than necessary.
The interviewer crossed UUID off the list.
• ~~UUID~~
• Snowflake IDs
• Centralized ID Service
Interviewer: What about the centralized ID service you mentioned earlier?
I thought for a moment.
Instead of every database generating IDs…
What if we had a single service responsible for it?
I sketched a small diagram.
Application Server
│
▼
ID Generation Service
│
▼
Database Shards
Candidate: Before storing a new URL, the application requests a unique ID from the service.
Once it receives the ID, it can determine which database shard should store the record.
The interviewer nodded.
Interviewer: What are the advantages?
Candidate: Since there’s only one service generating IDs, uniqueness is easy to guarantee.
It can also continue issuing compact numeric IDs, so our Base62 encoding remains short.
The interviewer smiled.
Interviewer: That’s true.
Then he paused.
Interviewer: But every new URL now depends on this service.
What happens if it’s unavailable?
I looked back at the diagram.
Every write request has to pass through it…
If it fails…
I answered.
Candidate: Then we can’t generate any new short URLs.
The ID generation service becomes a single point of failure.
The interviewer nodded.
Interviewer: Exactly.
We’ve solved one problem…
but introduced another.
📒 Seattle Engineer Notes
A centralized ID generation service guarantees globally unique,
compact numeric IDs.
However, every write request depends on it.
Without careful replication and failover, it can become both a
performance bottleneck and a single point of failure.
The interviewer uncapped the marker again.
Interviewer: Is there a way to generate compact numeric IDs…
without relying on a single centralized service?
I looked at the last remaining option.
• Snowflake IDs
This must be why Snowflake IDs were invented.
The interviewer noticed the look on my face.
Interviewer: Let’s build it together.
Suppose you have three application servers.
He drew a simple diagram.
+-----------+
| Server A |
+-----------+
+-----------+
| Server B |
+-----------+
+-----------+
| Server C |
+-----------+
Interviewer: We don’t want these servers talking to each other every time they generate an ID.
How could they independently create IDs without producing duplicates?
I thought for a moment.
Random numbers wouldn’t work.
Sooner or later, two servers could generate the same value.
The interviewer nodded.
Interviewer: Right.
Randomness alone can’t guarantee uniqueness.
I looked back at the three servers.
They don’t communicate…
So they need to rely on something they already have in common.
Then it clicked.
Candidate: Time?
The interviewer smiled.
Interviewer: Exactly.
Every server has access to the current timestamp.
If IDs increase over time, we’re already much closer to generating unique, ordered IDs.
He wrote:
Current Time (ms)
↓
1752998523123
I nodded.
Using time also means newer IDs are naturally larger than older ones.
But another question immediately came to mind.
Candidate: Wait…
What if two servers generate an ID during the exact same millisecond?
Wouldn’t they still produce duplicates?
The interviewer smiled again.
Interviewer: Great question.
That’s exactly the next problem Snowflake solves.
📒 Seattle Engineer Notes
Using the current timestamp provides a natural ordering for IDs and
greatly reduces the chance of collisions.
However, time alone is not enough.
Multiple servers can generate IDs during the same millisecond, so
additional information is needed to guarantee uniqueness.
This is where Snowflake's machine ID and sequence number come into play.
The interviewer picked up the marker.
Interviewer: Let’s say two application servers generate an ID at exactly the same millisecond.
If both IDs contain only the timestamp…
they’d be identical.
He wrote:
Timestamp
Then he added another field.
Timestamp
+
Machine ID
Interviewer: Every application server is assigned a unique machine ID.
For example…
Server A
Timestamp = 1752998523123
Machine ID = 1
Server B
Timestamp = 1752998523123
Machine ID = 2
Interviewer: Same timestamp.
Different machine IDs.
Different IDs.
I nodded.
So that’s how Snowflake prevents collisions across multiple servers.
Then another thought occurred to me.
What if the collision happens on the same server instead?
Candidate: What if Server A receives two requests during the same millisecond?
The timestamp would be the same.
The machine ID would also be the same.
Wouldn’t those IDs still collide?
The interviewer smiled.
Interviewer: Exactly.
That’s why Snowflake has one final component.
He wrote:
Timestamp
+
Machine ID
+
Sequence Number
Interviewer: The sequence number starts at zero for each millisecond.
Every new request increments it.
He expanded the example.
Timestamp = 1752998523123
Machine ID = 1
Request 1 → Sequence = 0
Request 2 → Sequence = 1
Request 3 → Sequence = 2
I nodded.
So even if multiple requests arrive during the same millisecond, every ID is still unique.
Candidate: What happens when the clock moves to the next millisecond?
Interviewer: The sequence number resets to zero.
He added another example.
Timestamp = 1752998523124
Machine ID = 1
Request 1 → Sequence = 0
The completed design was surprisingly simple.
Timestamp
+
Machine ID
+
Sequence Number
I looked at the whiteboard for a moment.
Each field solves a different problem.
The timestamp keeps IDs ordered.
The machine ID distinguishes different servers.
The sequence number distinguishes requests generated by the same server within a single millisecond.
The interviewer nodded.
Interviewer: Exactly.
That’s the core idea behind Snowflake IDs.
📒 Seattle Engineer Notes
Snowflake combines three pieces of information:
• Timestamp
- Keeps IDs roughly ordered by creation time.
• Machine ID
- Allows multiple servers to generate IDs independently without collisions.
• Sequence Number
- Allows a single server to generate multiple IDs within the same millisecond.
Rather than relying on a centralized ID service, every application server can generate globally unique numeric IDs locally.
The interviewer stepped back from the whiteboard.
Interviewer: So…
why is Snowflake a better fit for our URL shortener than the previous approaches?
I looked back at the three solutions we’d discussed.
UUID
Centralized ID Service
Snowflake IDs
UUIDs eliminated coordination…
But produced unnecessarily long short URLs.
The centralized ID service generated compact numeric IDs…
But every write request depended on a single service.
I looked back at the Snowflake diagram.
Timestamp
+
Machine ID
+
Sequence Number
Everything finally clicked.
Candidate: Snowflake gives us the best of both worlds.
Every application server can generate IDs independently, so there’s no centralized bottleneck.
At the same time, the IDs are still numeric, allowing us to continue using Base62 to generate short URLs.
The interviewer nodded.
Interviewer: Exactly.
We’ve eliminated the single point of failure while keeping our short URLs compact.
He paused before asking another question.
Interviewer: Earlier, we decided to shard our database because a single database wouldn’t scale.
How does generating Snowflake IDs help us determine which shard should store a newly created URL?
I looked back at the whiteboard.
Candidate: Could we use the generated ID to determine which database shard stores the record?
The interviewer smiled.
Interviewer: Exactly.
The ID doesn’t just uniquely identify the record.
It can also help us determine where to store it.
He wrote a sample Snowflake ID on the whiteboard.
812938471928374
Then he drew four database shards.
+--------+ +--------+ +--------+ +--------+
| DB1 | | DB2 | | DB3 | | DB4 |
+--------+ +--------+ +--------+ +--------+
Interviewer: Now the question becomes…
How do we consistently map this ID to one of these four databases?
I stared at the whiteboard.
Whatever rule we choose…
Every application server has to make the exact same decision.
Otherwise, the same ID could be written to different databases.
I looked back at the interviewer.
Candidate: We need a deterministic mapping.
Given the same ID, every server should always choose the same database shard.
The interviewer nodded.
Interviewer: Exactly.
Can you think of a simple way to achieve that?
I thought for a moment.
We’re not really interested in the value of the ID itself.
We just need a function that distributes IDs across four shards.
Then it clicked.
Candidate: We could hash the ID and use the result to determine the shard.
The interviewer smiled.
Interviewer: That’s the idea.
One simple approach is:
Hash(ID) % Number of Shards
For four database shards:
Hash(ID) % 4
The remainder determines the destination.
0 → DB1
1 → DB2
2 → DB3
3 → DB4
Interviewer: Every application server can perform this calculation independently.
No coordination is required.
I nodded.
It’s simple.
Fast.
And deterministic.
Then the interviewer uncapped the marker again.
Interviewer: Let’s say our service becomes twice as popular.
We decide to add a fifth database shard.
He extended the diagram.
+--------+ +--------+ +--------+ +--------+ +--------+
| DB1 | | DB2 | | DB3 | | DB4 | | DB5 |
+--------+ +--------+ +--------+ +--------+ +--------+
Then he looked at me.
Interviewer: What happens now?
I thought about the routing formula.
It was…
Hash(ID) % 4
But now…
Hash(ID) % 5
My eyes widened.
Every remainder could change.
A record that used to belong to DB2 might now belong to DB4.
A record in DB1 could suddenly map to DB5.
I looked back at the interviewer.
Candidate: We’d have to move a huge amount of existing data.
The interviewer nodded.
Interviewer: Exactly.
Changing the number of shards changes the routing calculation.
Without another strategy, nearly every record may map to a different database.
📒 Seattle Engineer Notes
Using `Hash(ID) % N` is simple and evenly distributes data across shards.
However, it has one major drawback.
When the number of shards changes, the modulo result changes for many
keys, requiring a large-scale data migration.
This is one of the motivations for techniques such as consistent hashing,
which minimize data movement when shards are added or removed.
The interviewer capped the marker for a moment.
Interviewer: Now let me ask you another question.
Should we optimize for a problem we don’t have yet?
I thought about it.
At the moment…
We only have four database shards.
And we don’t expect the number of shards to change frequently.
Candidate: Probably not.
The modulo approach is simple and works well as long as the number of shards remains stable.
The interviewer nodded.
Interviewer: I agree.
In many real-world systems, that’s a perfectly reasonable trade-off.
He paused.
Interviewer: However, suppose our service keeps growing.
Adding or removing shards becomes a common operation.
Would Hash(ID) % N still be a good choice?
I shook my head.
Candidate: No.
Every time N changes, we’d have to redistribute a large portion of the existing data.
The interviewer smiled.
Interviewer: Exactly.
That’s why many large-scale distributed systems use a different approach called consistent hashing.
Instead of mapping an ID directly to one of the database shards using modulo…
it maps both the IDs and the database shards onto a logical hash ring.
He began drawing a circle on the whiteboard.
Interesting…
I’ve heard of consistent hashing before, but I’ve never understood why it’s needed.
📒 Seattle Engineer Notes
`Hash(ID) % N` is often a good solution when the number of database shards
is relatively stable.
However, if shards are frequently added or removed, changing `N` causes
many IDs to be reassigned, resulting in expensive data migration.
Consistent hashing was designed to minimize this data movement while
still distributing data across multiple shards.
The interviewer completed the circle.
0
/ \
/ \
/ \
| |
\ /
\ /
\ /
Max Hash
Interviewer: First, we hash each database shard and place it on the ring.
He marked four points.
DB1
●
/ \
DB4 DB2
● ●
\ /
●
DB3
Interviewer: Next, we hash the ID.
Suppose it lands here.
He placed another point on the ring.
DB1
●
/ \
DB4 × DB2
● ●
\ /
●
DB3
Candidate: Which database stores the record?
The interviewer smiled.
Interviewer: Starting from the ID, move clockwise around the ring.
The first database you encounter owns the record.
He drew an arrow.
DB1
●
/ \
DB4 × ─► DB2
● ●
\ /
●
DB3
I nodded.
Candidate: So this record would be stored in DB2.
Interviewer: Exactly.
Every application server performs the same calculation.
As long as they use the same hash function, they’ll always reach the same database.
I looked at the diagram.
That’s surprisingly simple.
But how does this reduce data migration?
The interviewer smiled.
Interviewer: Good question.
Let’s add a new database.
📒 Seattle Engineer Notes
Consistent hashing places both the data and the database shards on the
same logical hash ring.
To determine where a record belongs:
1. Hash the record's ID.
2. Locate that position on the ring.
3. Move clockwise until reaching the first database shard.
Because every application server performs the same steps, no central
routing table is required.
The interviewer added a fifth database to the ring.
DB1
●
/ \
DB5 DB2
● ●
\ /
DB4 ● ● DB3
He looked back at me.
Interviewer: Before we added DB5…
which database owned this section of the ring?
He shaded the arc between DB4 and DB1.
DB1
●
///////
/ \
DB4 ●////////
I traced the ring clockwise with my finger.
The first database encountered is DB1.
Candidate: DB1.
The interviewer nodded.
Interviewer: Correct.
Every ID that landed in this range was stored in DB1.
Then he pointed to the updated ring.
DB1
●
/////|
/ |
DB5 ●-----|
● |
\ /
DB4 ● ● DB3
Interviewer: Now we’ve inserted DB5 between DB4 and DB1.
What happens to the records in this range?
I looked at the diagram.
The clockwise rule hasn’t changed.
Only the first database encountered has changed.
Then it clicked.
Candidate: Only the records that now encounter DB5 first need to move.
Everything else stays where it is.
The interviewer smiled.
Interviewer: Exactly.
Adding a new database doesn’t change the destination for every record.
It only affects the portion of the ring that the new database takes over.
I nodded.
That’s much better than modulo hashing.
Instead of redistributing nearly every record…
We’re only moving a small subset of the data.
📒 Seattle Engineer Notes
When a new shard is inserted into a consistent hash ring, it only takes
ownership of part of its successor's range.
As a result, only the records within that portion of the ring need to be
migrated.
This is why consistent hashing dramatically reduces data movement
compared to `Hash(ID) % N`, where changing `N` can remap almost every
record.
High Availability
The interviewer stepped back from the whiteboard.
For the first time since the interview began, the architecture felt complete.
Browser
│
▼
URL Shortener Service
│
▼
Redis
│
▼
Database Shards
Reads were served from Redis.
Writes were distributed across multiple database shards.
Every application server could independently generate globally unique Snowflake IDs.
And consistent hashing allowed us to add new shards without migrating nearly every record.
I put the marker down.
Candidate: I think this design should scale well.
The interviewer smiled.
Interviewer: I agree.
He looked at the diagram for a few seconds before uncapping the marker again.
Then, without saying a word…
He drew a large X over one of the database shards.
DB1 DB2
+---+ +---+
| | | X |
+---+ +---+
DB3 DB4
I stared at the whiteboard.
Interviewer: What happened?
Candidate: DB2 failed.
Interviewer: Exactly.
He looked back at me.
Interviewer: We spent quite a bit of time making this system scalable.
But scalability isn’t enough.
Real systems fail.
Servers crash.
Disks die.
Entire availability zones become unavailable.
He tapped the crossed-out database again.
Interviewer: What happens to our URL shortener now?
I looked back at the architecture.
For the first time…
I wasn’t thinking about throughput anymore.
I was thinking about failure.
📒 Seattle Engineer Notes
System design interviews usually evolve through several phases.
- Build a working system.
- Scale the system.
- Make the system resilient.
A system that handles one million requests per second is still a poor design if it stops working whenever a single server fails.
Scalability answers:
Can the system handle more traffic?
High availability answers:
Can the system continue operating when components fail?
The room fell silent.
I looked back at the failed database.
DB1 DB2 +---+ +---+ | | | X | +---+ +---+ DB3 DB4Candidate: Every piece of data stored on DB2 would become unavailable.
Users trying to access those shortened URLs would receive errors instead of being redirected.
And we wouldn’t be able to create new URLs that belong on this shard.
The interviewer nodded.
Interviewer: Exactly.
Sharding helped us scale.
It didn’t protect us from failures.
He uncapped the marker and drew another database beside DB2.
DB2 +-------+ |Primary| +-------+ │ │ ▼ +-------+ |Replica| +-------+I tilted my head.
Candidate: A backup?
The interviewer smiled.
Interviewer: Close.
A backup is something you restore after a failure.
This is a replica.
Instead of storing the data in only one place, we continuously maintain another copy on a different machine.
He expanded the diagram.
DB1 Primary ─────► DB1 Replica DB2 Primary ─────► DB2 Replica DB3 Primary ─────► DB3 Replica DB4 Primary ─────► DB4 ReplicaI looked at the whiteboard.
Earlier, every record existed exactly once.
Now every shard had a second copy.
Candidate: So if the primary database crashes…
the replica still has the data?
Interviewer: Exactly.
The goal isn’t to prevent failures.
It’s to make sure failures don’t stop the system.
He pointed back at the failed database.
+---------+ |Primary | +---------+ X +---------+ |Replica | +---------+Interviewer: If the primary becomes unavailable, we can promote the replica to become the new primary.
I nodded.
So the system wasn’t waiting for the failed server to recover.
It simply switched to another copy.
The interviewer quickly updated the diagram.
Before Primary ─────► Replica After Failover Failed Primary X New PrimaryInterviewer: This process is called failover.
Instead of waiting for the failed database to come back online, we promote a healthy replica and continue serving requests.
For the first time…
A database failure no longer meant the service had to stop.
📒 Seattle Engineer Notes Sharding and replication solve different problems. Sharding improves scalability by distributing data across multiple databases. Replication improves availability by maintaining additional copies of the same data. If a primary database fails, a replica can be promoted to take its place. This process is known as failover. Together, sharding and replication allow a system to continue serving traffic while also scaling to large amounts of data.The interviewer stepped back from the whiteboard.
The architecture had changed significantly since the interview began.
Browser │ ▼ URL Shortener Service │ Redis │ ┌─────────────┴─────────────┐ ▼ ▼ DB1 Primary ───► Replica DB2 Primary ───► Replica DB3 Primary ───► Replica DB4 Primary ───► ReplicaHe smiled.
Interviewer: Now we’ve built something that’s not only scalable…
but resilient.
I looked at the whiteboard one last time.
We had started with a single database.
One failure would have brought down an entire portion of the service.
Now every shard had redundancy.
The system could continue operating even when machines failed.
For the first time during the interview…
the design felt production-ready.
I stepped away from the whiteboard.
For the first time since the interview began…
I wasn’t looking at isolated components anymore.
I was looking at a complete system.
It had started with a simple question.
“How would you design a URL shortener?”
Somehow, that single question had taken us through requirements gathering, APIs, ID generation, caching, sharding, consistent hashing, and high availability.
The interviewer studied the whiteboard one last time.
He nodded.
Interviewer: Nice job.
He placed the marker back on the table.
Interviewer: There are still plenty of directions we could take this.
Monitoring.
Rate limiting.
Analytics.
Abuse prevention.
Global deployments.
Security.
He smiled.
Interviewer: But for the requirements we started with…
this is a solid design.
I let out a breath I hadn’t realized I was holding.
Candidate: Thank you.
The interviewer closed his laptop.
Interviewer: One last piece of advice.
System design interviews aren’t about producing the perfect architecture.
They’re about showing how you think.
Every assumption you question…
Every trade-off you explain…
Every bottleneck you recognize…
That’s what interviewers are evaluating.
Not whether your diagram matches theirs.
He stood up and offered his hand.
Interviewer: Great discussion.
Candidate: Thank you.
I shook his hand and walked out of the room.
The whiteboard was still covered with arrows, boxes, and crossed-out ideas.
At the beginning of the interview…
it had looked intimidating.
Now…
it looked like a story.
📒 Seattle Engineer Notes If you've read this far, congratulations—you've completed an entire mock system design interview. More importantly, you've seen how experienced engineers approach these problems. They don't begin with Redis. They don't begin with microservices. They begin by understanding the problem, making reasonable assumptions, identifying bottlenecks, and improving the design one step at a time. That's exactly what interviewers want to see. No one designs a production system perfectly on the first try. Good system design is an iterative conversation.