Master the System Design Interview
Build a repeatable framework for FAANG system design interviews.
Introduction
30 Second Summary
System design interviews are the most dreaded part of the FAANG interview loop. You know the concepts, but when someone asks you to "Design a URL Shortener" you freeze because you don't know what steps to follow or what to say first.
In this project, you will learn a repeatable 5-step framework and apply it to 6 real interview questions that come up at Google, Amazon, and Meta. Then you will build a working Java implementation that connects your design decisions to running code.
What You'll Build
A complete system design interview toolkit with written design documents, architecture diagrams in Excalidraw, and a working URL Shortener server built in Java.
By the end of this project, you'll have:
- A repeatable 5-step framework you can apply to any system design question in a 45-minute interview.
- Written design documents covering 6 real FAANG interview questions (URL Shortener, News Feed, Chat App, Rate Limiter, Search Autocomplete, Distributed Cache) with deep dives and trade-off analysis.
- A working Java URL Shortener that proves your design decisions in code with Base62 encoding and an in-memory cache.
- Secret Mission: Apply the full framework cold to a Video Streaming system in a timed exercise.
Are there any prerequisites?
This project assumes you have 2-3 years of Java experience and basic familiarity with distributed systems concepts like caching, databases, and load balancers. You do not need prior system design interview experience.
Before We Start
Before you can practice system design interviews, you need a workspace that separates your written designs from your code.
In this step, you will verify your Java environment, scaffold a project directory for all your design documents and code, and open your diagramming tool.
In this step, get ready to:
- Verify that your Java Development Kit is version 21 or later.
- Create a project directory with separate folders for designs and code.
- Open Excalidraw in your browser for architecture diagrams.
Verify your Java environment
Your URL Shortener implementation later in this project requires JDK 21 or later. The java --version command confirms which version is active on your machine.
- Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows), then typing Terminal and pressing Enter.
- Check your Java version by running this command:
java --version
You should see output that starts with a version number. The first number is what matters.
✔️ I see version 21 or higher
Your Java environment is ready. Move on to the next substep.
ⓧ I see an older version
Your system has an older JDK installed. You need JDK 21 or later for this project.
- Download JDK 21 or later from the official Oracle downloads page:
- https://www.oracle.com/java/technologies/downloads/
- Run the installer for your operating system.
- After installation, close and reopen your terminal, then run java --version again to confirm the new version is active.
Why do I need to reopen the terminal?
Your terminal caches the path to executables when it starts. Reopening it forces a fresh lookup so it finds the newly installed JDK.
ⓧ Command not found
Java is not installed on your machine yet. You need JDK 21 or later for this project.
- Download JDK 21 or later from the official Oracle downloads page:
- https://www.oracle.com/java/technologies/downloads/
- Run the installer and follow the prompts for your operating system.
- After installation, close and reopen your terminal, then run java --version again to confirm it outputs version 21 or higher.
Create your project directory
Your project needs two separate areas: a designs/ folder for written design documents, and a url-shortener/ folder for the Java implementation you will build later.
- Move to your Desktop by running this command:
cd ~/Desktop
macOS
- Create the project directory and subdirectories by running these commands:
mkdir -p system-design-prep/designs system-design-prep/url-shortener
cd system-design-prep
- Create the empty markdown files inside designs/ by running:
touch designs/url-shortener.md designs/news-feed.md designs/chat-app.md designs/rate-limiter.md designs/search-autocomplete.md designs/distributed-cache.md designs/framework-cheatsheet.md
Windows
- Create the project directory and subdirectories by running these commands:
mkdir system-design-prep\designs
mkdir system-design-prep\url-shortener
cd system-design-prep
- Create the empty markdown files inside designs\ by running:
New-Item -ItemType File -Path designs\url-shortener.md, designs\news-feed.md, designs\chat-app.md, designs\rate-limiter.md, designs\search-autocomplete.md, designs\distributed-cache.md, designs\framework-cheatsheet.md
What does this structure do?
The designs/ folder will hold one markdown file per system you design. Each file walks through all 5 steps of the framework for that system.
The url-shortener/ folder will hold the Java implementation that proves your URL Shortener design decisions work in practice.
- Confirm the files exist by running:
ls designs/
You should see all seven markdown files listed in the output.
Not seeing seven files?
Make sure you ran the file creation command from inside the system-design-prep directory. Run pwd (macOS) or cd (Windows) to check your current location. The path should end with system-design-prep.
If you are in the wrong directory, navigate to it and re-run the file creation command. help me find my system-design-prep folder
Open Excalidraw for architecture diagrams
System design interviews are visual. You will sketch high-level architecture diagrams for each system you design. Excalidraw is a free browser-based whiteboard that requires no account.
- Open your browser and navigate to:
- https://excalidraw.com
- Bookmark the page for quick access throughout this project. Press Cmd+D (macOS) or Ctrl+D (Windows) to add a bookmark.
You should see a blank canvas with a toolbar along the top for shapes, arrows, and text.
No account needed
Excalidraw works entirely in your browser. Your drawings save automatically to your browser's local storage. You can also export them as images later.
Your workspace is ready. Next up, you will learn the 5-step system design interview framework and apply it to your first design question.
Learn the Framework and Design a URL Shortener
Your environment is set up and your design files are ready. Now it's time to learn the framework that separates a "strong hire" from a "reject" in a system design interview.
Roughly half of failed system design interviews happen because candidates jump straight to drawing a diagram without first clarifying what they're building. In this step, you'll internalize a repeatable 5-step process and immediately apply it to the most commonly asked question: the URL Shortener.
In this step, get ready to:
- Write the 5-step interview framework into your cheatsheet.
- Design the URL Shortener's requirements and capacity estimation.
- Sketch the high-level architecture diagram in Excalidraw.
Write the 5-step framework into your cheatsheet
Every system design interview follows the same 45-minute structure, regardless of the question. Interviewers evaluate whether you can lead a structured conversation, not whether you've memorized a specific architecture.
The framework you're about to write down is the skeleton you'll use for every design in this project. It gives you a time budget for each phase so you never run out of time on the parts that matter most.
- Open framework-cheatsheet.md in your editor.
- Paste the following framework content into the file:
# System Design Interview Framework
## The 5 Steps
1. Requirements (5 min) - scope the problem
2. Estimation (5 min) - back-of-envelope math
3. High-Level Design (15 min) - end-to-end architecture
4. Deep Dive (15 min) - hardest parts in detail
5. Trade-offs (5 min) - what you chose and why
## Strong Hire Signals
- Clarifies requirements first
- Discusses trade-offs proactively
- Justifies decisions with numbers
- Treats interviewer as a partner
## Reject Signals
- Jumps to diagram immediately
- Presents one solution without alternatives
- Gives abstract answers without concrete technology choices
- Long silences
What does each step test?
Step 1 (Requirements) tests whether you can scope a problem without hand-holding. Step 2 (Estimation) tests whether you can do quick math to justify architecture choices.
Step 3 (High-Level Design) tests whether you can sketch a coherent end-to-end system. Step 4 (Deep Dive) is where "hire" becomes "strong hire." Step 5 (Trade-offs) tests whether you can articulate what you chose, what you gave up, and why.
- Save framework-cheatsheet.md.
- Read through the "Strong Hire Signals" and "Reject Signals" sections. These are the behaviors interviewers are actively looking for and penalizing.
Use this as a pre-interview checklist
Before any system design interview, skim this cheatsheet. It takes 30 seconds and resets your brain into the right structure. You'll add more patterns to it as you progress through this project.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file.
ⓧ I'd like to double check the full code
# System Design Interview Framework
## The 5 Steps
1. Requirements (5 min) - scope the problem
2. Estimation (5 min) - back-of-envelope math
3. High-Level Design (15 min) - end-to-end architecture
4. Deep Dive (15 min) - hardest parts in detail
5. Trade-offs (5 min) - what you chose and why
## Strong Hire Signals
- Clarifies requirements first
- Discusses trade-offs proactively
- Justifies decisions with numbers
- Treats interviewer as a partner
## Reject Signals
- Jumps to diagram immediately
- Presents one solution without alternatives
- Gives abstract answers without concrete technology choices
- Long silences
Define requirements and estimate capacity
The URL Shortener is the "Hello World" of system design interviews. It appears at Google, Amazon, and Meta regularly. Its scope is small, but it touches hashing, caching, database choice, and scaling.
You'll now apply the first two framework steps: Requirements and Estimation. Requirements force you to define exactly what the system does before drawing anything. Estimation gives you concrete numbers that justify your architecture choices later.
- Open url-shortener.md in your editor.
- Paste the following requirements and estimation content:
# URL Shortener Design
## Step 1: Requirements
### Functional
- Create short URL from long URL
- Redirect short URL to original
- Optionally track click analytics
### Non-Functional
- Low latency redirects (<100ms)
- High availability (99.99%)
- Handle 100M DAU
- URLs should not be guessable
## Step 2: Estimation
- 100M DAU x 1 write/day = ~1,200 writes/sec
- Read-heavy 10:1 ratio = ~12,000 reads/sec
- Each URL mapping ~500 bytes
- Storage: 100M/day x 500B x 365 x 5 years = ~91 TB
- Cache top 20% = ~18 TB cache needed
How does the estimation math work?
Start from Daily Active Users (100M). Assume 1 URL shortened per user per day. That's 100M writes spread across 86,400 seconds, which gives ~1,200 writes/sec.
URL shorteners are read-heavy (people click shortened links far more than they create them). A 10:1 read-to-write ratio means ~12,000 reads/sec. Storage comes from multiplying daily writes by record size over 5 years of retention.
The 20% cache rule follows the Pareto principle. A small fraction of URLs get most of the traffic, so caching 20% of total storage covers the hot set.
- Save url-shortener.md.
- Review the estimation numbers. In a real interview, you would talk through this math out loud. The interviewer cares about your reasoning process, not whether you get exactly 1,157 vs 1,200 writes/sec.
Estimation numbers feel like guesswork?
That's normal. The point of estimation is not precision. It's to justify design decisions. If you calculate 1,200 writes/sec, a single database server can handle that easily. But 12,000 reads/sec justifies adding a cache layer.
In an interview, state your assumptions clearly ("I'll assume 1 write per user per day") and the interviewer will correct you if they want different numbers.
Help me practice estimation math for system design interviews.
Sketch the high-level architecture
Step 3 of the framework is where you draw the actual system. This is what most candidates jump to immediately, but because you've already defined requirements and estimated capacity, your architecture choices are now grounded in numbers.
The URL Shortener's architecture follows a classic read-heavy web service pattern: a load balancer distributes requests across application servers, with a Redis cache in front of the database to handle the 10:1 read ratio.
- Add the following High-Level Design section to the bottom of url-shortener.md:
## Step 3: High-Level Design
Client -> Load Balancer -> Application Servers -> Cache (Redis) -> Database (PostgreSQL or DynamoDB)
### API Endpoints
- POST /shorten: {longUrl: '...'} -> {shortUrl: '...'}
- GET /{shortCode}: 301/302 redirect
### Read Path
Client -> LB -> App Server -> Cache (hit: return redirect) -> DB (miss: populate cache, return redirect)
### Write Path
Client -> LB -> App Server -> generate short code -> write to DB -> populate cache -> return short URL
Why document both read and write paths?
Interviewers specifically ask "walk me through what happens when a user clicks a short link" or "what happens when someone creates a short URL." Having both paths written forces you to think about where the cache sits in each flow.
The read path shows cache-aside pattern: check cache first, fall back to the database on a miss, then populate the cache for next time. The write path shows that you populate the cache immediately so the first read is always fast.
- Save url-shortener.md.
Now draw this architecture as a diagram. Diagrams are what interviewers actually see during the interview, so practice creating them quickly.
- Switch to Excalidraw in your browser.
- Draw boxes for each component: Client, Load Balancer, Application Servers, Cache (Redis), and Database (PostgreSQL or DynamoDB).
- Draw arrows between components showing the direction of requests: Client to Load Balancer, Load Balancer to App Servers, App Servers to Cache, Cache to Database.
- Label the two API endpoints on the arrows going into the Load Balancer: POST /shorten and GET /{shortCode}.
- Take a screenshot of your completed diagram and save it to your designs/ folder.
Speed over beauty
In a real interview, you have about 3 minutes to draw this. Practice drawing fast, legible boxes with short labels. Nobody expects pixel-perfect diagrams on a whiteboard.
Not sure how to arrange the components?
Lay them out left to right in the order a request flows: Client on the far left, Database on the far right. This mirrors how you'd explain the system verbally ("a request comes in from the client, hits the load balancer, then...").
Help me draw a URL Shortener architecture diagram.
✔️ Awesome, I've got everything!
Great. Double check you've saved url-shortener.md with all three sections.
ⓧ I'd like to double check the full code
# URL Shortener Design
## Step 1: Requirements
### Functional
- Create short URL from long URL
- Redirect short URL to original
- Optionally track click analytics
### Non-Functional
- Low latency redirects (<100ms)
- High availability (99.99%)
- Handle 100M DAU
- URLs should not be guessable
## Step 2: Estimation
- 100M DAU x 1 write/day = ~1,200 writes/sec
- Read-heavy 10:1 ratio = ~12,000 reads/sec
- Each URL mapping ~500 bytes
- Storage: 100M/day x 500B x 365 x 5 years = ~91 TB
- Cache top 20% = ~18 TB cache needed
## Step 3: High-Level Design
Client -> Load Balancer -> Application Servers -> Cache (Redis) -> Database (PostgreSQL or DynamoDB)
### API Endpoints
- POST /shorten: {longUrl: '...'} -> {shortUrl: '...'}
- GET /{shortCode}: 301/302 redirect
### Read Path
Client -> LB -> App Server -> Cache (hit: return redirect) -> DB (miss: populate cache, return redirect)
### Write Path
Client -> LB -> App Server -> generate short code -> write to DB -> populate cache -> return short URL
You now have a repeatable framework and your first full design covering requirements, estimation, and architecture. Next up, you'll apply this same framework to five more FAANG interview questions and start spotting the patterns that repeat across all of them.
Apply the Framework to Five More Interview Questions
You now have a complete URL Shortener design that walks through all 5 framework steps. That single design proves you can apply the framework, but interviewers pull from a wide range of systems.
In this step, you will apply the same framework to five additional systems that cover distinct architectural patterns. Each system introduces a new challenge: fan-out strategies, real-time delivery, distributed shared state, prefix-based search, and cache coordination. Recognizing these patterns across systems is what separates a "strong hire" from someone who memorized one diagram.
In this step, get ready to:
- Design a News Feed and a Chat/Messaging system covering real-time delivery patterns.
- Design a Rate Limiter and a Search Autocomplete system covering distributed state and low-latency lookup patterns.
- Design a Distributed Cache covering consistent hashing and failure resilience patterns.
Design the News Feed and Chat systems
These two systems share a common theme: delivering content to users in near-real-time. The News Feed introduces fan-out strategies for distributing posts at scale. The Chat system introduces persistent connections and guaranteed message delivery.
- Open designs/news-feed.md in your editor and paste the following design document:
# News Feed Design (Instagram/Twitter)
## Step 1: Requirements
- Users post content
- Followers see posts in their feed
- Feed is personalized and near-real-time
## Step 2: Fan-out Strategy
- Fan-out on write: precompute feeds into Redis for each follower on post creation
- Fan-out on read: compute feed at request time
- Hybrid: fan-out on write for regular users (<10K followers), fan-out on read for celebrities (>10K followers)
## Step 3: Components
- Post Service
- Fan-out Service
- Timeline Cache (Redis Sorted Set)
- CDN for media
What does this design capture?
The key architectural decision here is the fan-out strategy. Fan-out on write precomputes every follower's feed at post time, making reads instant but writes expensive. Fan-out on read computes the feed on demand, making writes cheap but reads slow.
The hybrid approach is what real systems like Instagram use. Regular users (under 10K followers) get fan-out on write for speed. Celebrities (over 10K followers) use fan-out on read to avoid writing to millions of feeds on every post.
- Save news-feed.md.
- Confirm you see the three sections: Requirements, Fan-out Strategy, and Components.
- Now open designs/chat-app.md and paste the following design document:
# Chat/Messaging System Design (WhatsApp/Slack)
## Step 1: Requirements
- Real-time 1:1 and group messaging
- Message ordering
- Offline delivery
- Read receipts
## Step 2: Key Patterns
- WebSocket connections for real-time delivery (vs polling)
- Kafka for guaranteed delivery and ordering
- Per-user inbox model for offline storage
## Step 3: Architecture
Client <-> WebSocket Gateway <-> Chat Service <-> Message Queue (Kafka) <-> Storage
- Online: deliver via WebSocket
- Offline: persist to queue, push on reconnect
What does this design capture?
The Chat system's core challenge is real-time bidirectional communication. WebSocket connections keep a persistent channel open between client and server, eliminating the latency of repeated HTTP requests.
For guaranteed delivery, Kafka acts as a durable message queue. If a recipient is offline, their messages persist in the queue and get pushed when they reconnect. This per-user inbox pattern ensures no messages are lost even during network failures.
- Save chat-app.md.
- Confirm you see Requirements, Key Patterns, and Architecture sections with the online/offline delivery paths listed.
Not sure when to use WebSockets vs polling?
Use WebSockets when you need bidirectional, low-latency communication (chat, gaming, live collaboration). Use polling or long-polling when updates are infrequent and the client only needs to receive data (email notifications, dashboard refreshes).
help me understand when to choose WebSockets over HTTP polling in a system design interview
✔️ Awesome, I've got everything!
Great. Double check you have saved both files.
ⓧ I'd like to double check the full code
Here is news-feed.md:
# News Feed Design (Instagram/Twitter)
## Step 1: Requirements
- Users post content
- Followers see posts in their feed
- Feed is personalized and near-real-time
## Step 2: Fan-out Strategy
- Fan-out on write: precompute feeds into Redis for each follower on post creation
- Fan-out on read: compute feed at request time
- Hybrid: fan-out on write for regular users (<10K followers), fan-out on read for celebrities (>10K followers)
## Step 3: Components
- Post Service
- Fan-out Service
- Timeline Cache (Redis Sorted Set)
- CDN for media
Here is chat-app.md:
# Chat/Messaging System Design (WhatsApp/Slack)
## Step 1: Requirements
- Real-time 1:1 and group messaging
- Message ordering
- Offline delivery
- Read receipts
## Step 2: Key Patterns
- WebSocket connections for real-time delivery (vs polling)
- Kafka for guaranteed delivery and ordering
- Per-user inbox model for offline storage
## Step 3: Architecture
Client <-> WebSocket Gateway <-> Chat Service <-> Message Queue (Kafka) <-> Storage
- Online: deliver via WebSocket
- Offline: persist to queue, push on reconnect
Design the Rate Limiter and Search Autocomplete systems
These two systems deal with protecting infrastructure and delivering results under strict latency budgets. The Rate Limiter introduces distributed shared state with atomic operations. The Search Autocomplete introduces in-memory data structures optimized for prefix lookups.
- Open designs/rate-limiter.md and paste the following design document:
# Rate Limiter Design
## Step 1: Requirements
- Limit API requests per user/IP
- Configurable limits per endpoint
- Distributed across multiple servers
## Step 2: Algorithms
- Token Bucket: allows bursts, simple
- Sliding Window Log: precise, memory-heavy
- Sliding Window Counter: balance of precision and memory
## Step 3: Architecture
API Gateway -> Rate Limiter Middleware -> Redis (centralized counter) -> Application
- Distributed challenge: use Redis INCR + EXPIRE for atomic operations
What does this design capture?
The Rate Limiter's core challenge is distributed shared state. Multiple application servers must agree on how many requests a user has made, so you need a centralized counter store.
Redis solves this with atomic INCR and EXPIRE operations. The three algorithms (Token Bucket, Sliding Window Log, Sliding Window Counter) each trade off burst tolerance, memory usage, and precision differently. Knowing when to pick each one is a common deep-dive question.
- Save rate-limiter.md.
- Confirm you see Requirements, Algorithms, and Architecture sections with the Redis atomic operations noted.
- Now open designs/search-autocomplete.md and paste the following design document:
# Search Autocomplete/Typeahead Design
## Step 1: Requirements
- Return top 5 suggestions as user types
- Sub-100ms latency
- Update suggestions based on trending queries
## Step 2: Key Pattern
- Trie (prefix tree) in memory, each node stores pre-computed top-K suggestions
## Step 3: Architecture
Client -> API Gateway -> Autocomplete Service (in-memory Trie)
- Offline pipeline aggregates search logs and rebuilds Trie periodically
- Two-layer: static Trie for historical + real-time trending layer for recent popular queries
What does this design capture?
The Autocomplete system's constraint is sub-100ms latency while the user is actively typing. A Trie (prefix tree) stored in memory gives O(length) prefix lookups, and pre-computing the top-K suggestions at each node eliminates runtime sorting.
The two-layer approach solves the freshness problem. A static Trie handles historical patterns, while a real-time trending layer injects recently popular queries without a full rebuild. This separation keeps reads fast while allowing suggestions to stay current.
- Save search-autocomplete.md.
- Confirm you see Requirements, Key Pattern, and Architecture sections with the two-layer approach documented.
Feeling overwhelmed by so many systems?
Focus on the patterns, not memorizing every detail. The Rate Limiter and Distributed Cache both use Redis for distributed shared state. The News Feed and Chat App both solve real-time delivery. Recognizing these recurring patterns is exactly what interviewers reward.
help me see the recurring patterns across these system designs
✔️ Awesome, I've got everything!
Great. Double check you have saved both files.
ⓧ I'd like to double check the full code
Here is rate-limiter.md:
# Rate Limiter Design
## Step 1: Requirements
- Limit API requests per user/IP
- Configurable limits per endpoint
- Distributed across multiple servers
## Step 2: Algorithms
- Token Bucket: allows bursts, simple
- Sliding Window Log: precise, memory-heavy
- Sliding Window Counter: balance of precision and memory
## Step 3: Architecture
API Gateway -> Rate Limiter Middleware -> Redis (centralized counter) -> Application
- Distributed challenge: use Redis INCR + EXPIRE for atomic operations
Here is search-autocomplete.md:
# Search Autocomplete/Typeahead Design
## Step 1: Requirements
- Return top 5 suggestions as user types
- Sub-100ms latency
- Update suggestions based on trending queries
## Step 2: Key Pattern
- Trie (prefix tree) in memory, each node stores pre-computed top-K suggestions
## Step 3: Architecture
Client -> API Gateway -> Autocomplete Service (in-memory Trie)
- Offline pipeline aggregates search logs and rebuilds Trie periodically
- Two-layer: static Trie for historical + real-time trending layer for recent popular queries
Design the Distributed Cache system
The final system pulls together patterns you have already seen. A Distributed Cache must coordinate multiple nodes, handle failures gracefully, and route keys efficiently using consistent hashing.
- Open designs/distributed-cache.md and paste the following design document:
# Distributed Cache Design (Redis/Memcached)
## Step 1: Requirements
- Sub-millisecond reads
- Horizontal scalability
- High availability
- Consistent hashing for key distribution
## Step 2: Key Patterns
- Consistent hashing with virtual nodes
- LRU eviction under memory pressure
- Thundering herd problem on node failure
## Step 3: Architecture
Client -> Cache Client Library (consistent hash ring) -> Cache Cluster (multiple nodes)
- Stabilization window during cluster changes
- Circuit breaker pattern to protect backend
What does this design capture?
Consistent hashing with virtual nodes is the key pattern here. Naive modulo hashing (key % number_of_nodes) breaks catastrophically when you add or remove a node because almost every key remaps. Consistent hashing ensures only ~1/N keys need to move.
The thundering herd problem occurs when a cache node dies. Suddenly all requests for those keys hit the database simultaneously. The stabilization window pauses redistribution briefly to prevent cascading failures, while a circuit breaker protects the backend from being overwhelmed.
- Save distributed-cache.md.
- Confirm you see Requirements, Key Patterns, and Architecture sections with the stabilization window and circuit breaker noted.
Confused about consistent hashing vs regular hashing?
Regular modulo hashing assigns keys with key % N where N is the node count. Adding one node changes N and remaps nearly every key. Consistent hashing arranges nodes on a ring. Adding a node only steals keys from its immediate neighbors, leaving everything else untouched.
help me understand consistent hashing with virtual nodes
✔️ Awesome, I've got everything!
Great. Double check you have saved the file.
ⓧ I'd like to double check the full code
Here is distributed-cache.md:
# Distributed Cache Design (Redis/Memcached)
## Step 1: Requirements
- Sub-millisecond reads
- Horizontal scalability
- High availability
- Consistent hashing for key distribution
## Step 2: Key Patterns
- Consistent hashing with virtual nodes
- LRU eviction under memory pressure
- Thundering herd problem on node failure
## Step 3: Architecture
Client -> Cache Client Library (consistent hash ring) -> Cache Cluster (multiple nodes)
- Stabilization window during cluster changes
- Circuit breaker pattern to protect backend
You now have design documents covering six distinct systems. Next up, you will go deeper into the trade-offs and deep-dive topics that turn a "hire" signal into a "strong hire" signal.
Master Deep Dives and Trade-off Articulation
You now have six design documents with requirements, estimation, and architecture. The interviewer can see you structure a problem. But structure alone earns a "hire" signal, not a "strong hire."
The difference between those two signals lives in Steps 4 and 5 of the framework: deep dives and trade-off articulation. This is where you prove you understand why you chose something, not just what you chose.
In this step, get ready to:
- Write deep-dive sections for four systems covering the hardest architectural decisions.
- Practice the articulation pattern that interviewers score for all six designs.
- Add 2026-specific expectations (cost reasoning, failure modes, scaling paths) to your toolkit.
Write deep dives for your strongest systems
A deep dive proves you can reason about the hardest part of a system in detail. The interviewer picks the component most likely to break at scale and watches how you navigate alternatives.
What makes a good deep dive?
Present 2-3 concrete alternatives with their trade-offs, then pick one and justify it with numbers. Never say "it depends" without immediately following up with what it depends on.
- Open url-shortener.md in your editor.
- Add this deep dive section below your existing Step 3 content:
## Step 4: Deep Dive - Key Generation
- Base62 of auto-increment ID: simple, sequential, reveals volume
- MD5/SHA256 truncated to 7 chars: collision risk, not sequential
- Pre-generated KGS: no collisions, decoupled, adds complexity
- CHOSEN: Base62 of distributed counter. 62^7 = 3.5 trillion possible URLs, fast generation, low collision risk
What does this deep dive demonstrate?
You present three real alternatives, name their strengths and weaknesses, then pick one with a concrete justification (3.5 trillion possible URLs from 62^7).
The interviewer sees that you know the options exist and can reason about which fits the requirements you scoped in Step 1.
- Save url-shortener.md.
Now apply the same pattern to your other three systems that have natural deep-dive topics.
- Open news-feed.md and add this section below your Step 3 content:
## Step 4: Deep Dive - Database Choice
- Redis Sorted Set: fast reads, memory-limited
- Cassandra: durable, scalable, slightly higher latency
- Save news-feed.md.
- Open chat-app.md and add this section below your Step 3 content:
## Step 4: Deep Dive - Message Ordering
- Lamport clocks for logical timestamps
- Per-conversation sequence numbers with single writer per conversation
- Save chat-app.md.
- Open distributed-cache.md and add this section below your Step 3 content:
## Step 4: Deep Dive - Consistent Hashing
- Naive modulo hashing fails when nodes are added/removed (massive key redistribution)
- Virtual nodes improve distribution uniformity and minimize redistribution
Why these deep-dive topics?
Each topic represents the single hardest decision in that system. Database choice for the News Feed determines your read/write trade-off. Message ordering for the Chat App determines your consistency guarantees. Consistent hashing for the Distributed Cache determines your scaling behavior.
- Save distributed-cache.md.
- Review all four files and confirm each has a new ## Step 4: Deep Dive section with concrete alternatives listed.
Practice the articulation pattern across all designs
Interviewers don't just want to hear your decision. They want to hear the structure of your reasoning. A specific sentence pattern lets you deliver this clearly under pressure every time.
- Open framework-cheatsheet.md and add this section below your existing "Reject Signals" content:
## Articulation Pattern
'I chose [X] because [Y]. The trade-off is [Z]. If the requirements changed to [W], I would switch to [V].'
Why this pattern works
This single sentence covers every dimension interviewers score: the decision, the reasoning, the cost, and the flexibility to adapt. Memorize this structure so it becomes automatic when you speak out loud.
- Save framework-cheatsheet.md.
Now apply this pattern to each of your six designs. Start with the URL Shortener.
- Open url-shortener.md and add this section below your new Step 4 content:
## Step 5: Trade-offs
'I chose 302 redirects over 301 because 302 lets us track analytics on every click. The trade-off is slightly higher latency since the browser cannot cache the redirect. If analytics were not required, I would switch to 301 for better client-side caching.'
What makes this trade-off strong?
It names a specific HTTP status code choice (302 vs 301), ties it to a requirement (analytics), names the cost (no browser caching), and shows flexibility (switch to 301 if requirements change). This is the level of specificity that earns a "strong hire."
- Save url-shortener.md.
Now write trade-off articulations for the remaining five systems. Follow the same pattern in each file.
- Open news-feed.md and add this below your Step 4 content:
## Step 5: Trade-offs
'I chose hybrid fan-out because pure push breaks at celebrity scale. The trade-off is increased read latency for celebrity posts since we merge at read time. If all users had similar follower counts, I would use pure fan-out on write for simplicity.'
- Save news-feed.md.
- Open chat-app.md and add this below your Step 4 content:
## Step 5: Trade-offs
'I chose WebSockets over long-polling because we need bidirectional real-time communication. The trade-off is connection state management at scale. If we only needed server-to-client notifications, I would use Server-Sent Events.'
- Save chat-app.md.
- Open rate-limiter.md and add this below your Step 3 content (this system skips the deep dive and goes straight to trade-offs):
## Step 5: Trade-offs
'I chose Token Bucket over Sliding Window because it naturally handles bursty traffic. The trade-off is slightly less precise rate enforcement. If we needed exact per-second limits for billing, I would switch to Sliding Window Log.'
- Save rate-limiter.md.
- Open search-autocomplete.md and add this below your Step 3 content:
## Step 5: Trade-offs
'I chose a pre-computed Trie over real-time query against a database because autocomplete must be sub-100ms. The trade-off is stale suggestions until the next rebuild cycle. If we needed real-time trending, I would add a hot-path that injects the last hour popular queries.'
- Save search-autocomplete.md.
- Open distributed-cache.md and add this below your Step 4 content:
## Step 5: Trade-offs
'I chose consistent hashing with virtual nodes over simple modulo because it minimizes key redistribution during scaling events. The trade-off is slightly more complex client-side routing logic.'
- Save distributed-cache.md.
- Confirm all six design files now have a ## Step 5: Trade-offs section that follows the articulation pattern.
Trade-offs feel too similar?
Each trade-off should name a different type of cost. Latency, memory, complexity, staleness, and precision are all distinct trade-off dimensions. If your articulations feel repetitive, check that you are naming the specific cost for each system rather than a generic "it's harder."
Help me make my trade-off articulations more specific to each system.
Add 2026-specific interview expectations
FAANG interviewers in 2026 now expect three additional dimensions beyond the classic trade-off: cost reasoning, failure modes, and scaling paths. These used to be bonus points. They are now baseline requirements.
- Open framework-cheatsheet.md and add these reusable templates below your Articulation Pattern section:
## 2026-Specific Expectations
### Cost Reasoning
'At [scale], the dominant cost is [component]. If we needed to cut cost by half, the lever I would pull is [action].'
### Failure Modes
'If [component] goes down, [recovery mechanism]. If [second component] goes down, [mitigation].'
### Scaling Path
'At 10x traffic with [condition], I would [action 1], [action 2], and [action 3].'
Why are these templates powerful?
Each template forces a specific structure that demonstrates operational maturity. Cost reasoning shows you think about budget. Failure modes show you design for resilience. Scaling paths show you can evolve a system without rewriting it.
- Save framework-cheatsheet.md.
Now apply one template to each of three different systems so you have concrete examples to reference before interviews.
- Open url-shortener.md and add this cost reasoning section below your Step 5 trade-off:
### Cost Reasoning
'At 12K reads/sec, the dominant cost is storage at 91TB. If we needed to cut cost by half, the lever I would pull is aggressive TTL-based eviction on old URLs not accessed in 30 days.'
- Save url-shortener.md.
- Open chat-app.md and add this failure modes section below your Step 5 trade-off:
### Failure Modes
'If the WebSocket Gateway goes down, clients reconnect to another gateway instance and the message queue replays undelivered messages. If Kafka goes down, we buffer messages in-memory on the Chat Service with a circuit breaker to prevent OOM.'
- Save chat-app.md.
- Open news-feed.md and add this scaling path section below your Step 5 trade-off:
### Scaling Path
'At 10x traffic with more celebrity users, I would increase the follower threshold for the push/pull boundary, add more Redis shards for timeline storage, and introduce a separate media-processing pipeline to offload CDN population.'
Notice the pattern across all three
Cost reasoning connects your estimation math back to a business decision. Failure modes show you design for reality, not just the happy path. Scaling paths show you can evolve the system incrementally instead of redesigning from scratch.
- Save news-feed.md.
- Review your designs/ folder. Each of your six design files should now have a Step 5 section. Three of them should also have a 2026-specific subsection (Cost Reasoning, Failure Modes, or Scaling Path).
Not sure where each section goes?
Every file follows the same structure: Step 1 (Requirements) at the top, then Step 2, Step 3, Step 4 (Deep Dive, if applicable), Step 5 (Trade-offs). The 2026-specific subsections (Cost Reasoning, Failure Modes, Scaling Path) always go below the main trade-off statement inside Step 5.
Help me check that my design files are structured correctly.
✔️ Awesome, I've got everything!
Great. Double check you have saved all your files before moving on.
ⓧ I'd like to double check the full code
Here is the final state of url-shortener.md (your most complete design):
# URL Shortener Design
## Step 1: Requirements
### Functional
- Create short URL from long URL
- Redirect short URL to original
- Optionally track click analytics
### Non-Functional
- Low latency redirects (<100ms)
- High availability (99.99%)
- Handle 100M DAU
- URLs should not be guessable
## Step 2: Estimation
- 100M DAU x 1 write/day = ~1,200 writes/sec
- Read-heavy 10:1 ratio = ~12,000 reads/sec
- Each URL mapping ~500 bytes
- Storage: 100M/day x 500B x 365 x 5 years = ~91 TB
- Cache top 20% = ~18 TB cache needed
## Step 3: High-Level Design
Client -> Load Balancer -> Application Servers -> Cache (Redis) -> Database (PostgreSQL or DynamoDB)
### API Endpoints
- POST /shorten: {longUrl: '...'} -> {shortUrl: '...'}
- GET /{shortCode}: 301/302 redirect
### Read Path
Client -> LB -> App Server -> Cache (hit: return redirect) -> DB (miss: populate cache, return redirect)
### Write Path
Client -> LB -> App Server -> generate short code -> write to DB -> populate cache -> return short URL
## Step 4: Deep Dive - Key Generation
- Base62 of auto-increment ID: simple, sequential, reveals volume
- MD5/SHA256 truncated to 7 chars: collision risk, not sequential
- Pre-generated KGS: no collisions, decoupled, adds complexity
- CHOSEN: Base62 of distributed counter. 62^7 = 3.5 trillion possible URLs, fast generation, low collision risk
## Step 5: Trade-offs
'I chose 302 redirects over 301 because 302 lets us track analytics on every click. The trade-off is slightly higher latency since the browser cannot cache the redirect. If analytics were not required, I would switch to 301 for better client-side caching.'
### Cost Reasoning
'At 12K reads/sec, the dominant cost is storage at 91TB. If we needed to cut cost by half, the lever I would pull is aggressive TTL-based eviction on old URLs not accessed in 30 days.'
And here is the final state of framework-cheatsheet.md:
# System Design Interview Framework
## The 5 Steps
1. Requirements (5 min) - scope the problem
2. Estimation (5 min) - back-of-envelope math
3. High-Level Design (15 min) - end-to-end architecture
4. Deep Dive (15 min) - hardest parts in detail
5. Trade-offs (5 min) - what you chose and why
## Strong Hire Signals
- Clarifies requirements first
- Discusses trade-offs proactively
- Justifies decisions with numbers
- Treats interviewer as a partner
## Reject Signals
- Jumps to diagram immediately
- Presents one solution without alternatives
- Gives abstract answers without concrete technology choices
- Long silences
## Articulation Pattern
'I chose [X] because [Y]. The trade-off is [Z]. If the requirements changed to [W], I would switch to [V].'
## 2026-Specific Expectations
### Cost Reasoning
'At [scale], the dominant cost is [component]. If we needed to cut cost by half, the lever I would pull is [action].'
### Failure Modes
'If [component] goes down, [recovery mechanism]. If [second component] goes down, [mitigation].'
### Scaling Path
'At 10x traffic with [condition], I would [action 1], [action 2], and [action 3].'
You now have a complete interview toolkit: six designs with deep dives and trade-offs, plus a cheatsheet with reusable patterns. Next up, you will turn one of these designs into running code to prove your architecture decisions work in practice.
Build the Java URL Shortener
You now have six complete design documents covering the most common FAANG interview questions, plus a framework cheat sheet you can review before any interview.
In this step, you will turn your URL Shortener design into running code. The goal is to prove that the design decisions you made on paper (the cache-aside pattern, Base62 key generation, the read/write paths) actually work in a real implementation.
In this step, get ready to:
- Create a Java HTTP server with Base62 encoding and in-memory storage.
- Implement the POST and GET request handlers that mirror your design's read and write paths.
- Compile, run, and test the server with curl to verify your design in action.
Create the server file with Base62 encoding
Java's built-in com.sun.net.httpserver.HttpServer gives you a lightweight HTTP server without any external dependencies. This is the simplest way to get a working server that mirrors your high-level design.
- In your terminal, navigate to the url-shortener/ directory inside your system-design-prep folder by running:
cd ~/Desktop/system-design-prep/url-shortener
- Create the UrlShortenerServer.java file by running:
touch UrlShortenerServer.java
- Confirm the file exists by running:
ls
You should see UrlShortenerServer.java in the output.
- Open UrlShortenerServer.java in your Java IDE and paste the following code to set up the server skeleton, storage, and Base62 encoding:
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class UrlShortenerServer {
// Base62 alphabet: 0-9, a-z, A-Z
private static final String BASE62_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final int SHORT_CODE_LENGTH = 7;
private static final int CACHE_MAX_SIZE = 1000;
// In-memory URL store (mirrors what a database like DynamoDB would do in production)
private static final ConcurrentHashMap<String, String> urlStore = new ConcurrentHashMap<>();
// Simple cache with a max size (mirrors what Redis would do in production)
private static final ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
// Distributed counter for generating unique IDs
private static final AtomicLong counter = new AtomicLong(1000000);
public static void main(String[] args) throws IOException {
// Create server on port 8080
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
// Register the two API endpoints from your design
server.createContext("/shorten", new ShortenHandler());
server.createContext("/", new RedirectHandler());
server.setExecutor(null);
server.start();
System.out.println("URL Shortener running on http://localhost:8080");
}
// Encodes a number into a 7-character Base62 string
static String encodeBase62(long number) {
StringBuilder sb = new StringBuilder();
while (number > 0) {
sb.append(BASE62_CHARS.charAt((int) (number % 62)));
number /= 62;
}
// Pad to ensure consistent length
while (sb.length() < SHORT_CODE_LENGTH) {
sb.append('0');
}
return sb.reverse().toString();
}
What does this code do?
This sets up the exact architecture from your URL Shortener design document:
- urlStore is your database. In production this would be PostgreSQL or DynamoDB.
- cache is your Redis layer, limited to 1000 entries (matching the "cache top 20%" from your estimation).
- counter is the distributed counter you chose for key generation. Starting at 1000000 ensures 7-character codes.
- encodeBase62 converts the counter value to a 7-character string using 62 possible characters (3.5 trillion combinations).
You won't see any output yet. The file will not compile until you add the request handlers next.
Implement the request handlers
Your design specified two API endpoints: POST /shorten for creating short URLs and GET /{shortCode} for redirecting. Each handler implements one of the paths you drew in Excalidraw.
- In UrlShortenerServer.java, add the ShortenHandler inner class directly below the encodeBase62 method. This implements the write path from your design:
// Handles POST /shorten - the WRITE PATH from your design
static class ShortenHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
// Only allow POST requests
if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) {
String error = "{\"error\": \"Method not allowed\"}";
exchange.sendResponseHeaders(405, error.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(error.getBytes());
os.close();
return;
}
// Read the long URL from the request body
InputStream is = exchange.getRequestBody();
String longUrl = new String(is.readAllBytes()).trim();
is.close();
// Validate input
if (longUrl.isEmpty()) {
String error = "{\"error\": \"URL is required\"}";
exchange.sendResponseHeaders(400, error.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(error.getBytes());
os.close();
return;
}
// Generate short code using Base62 of the incremented counter
long id = counter.incrementAndGet();
String shortCode = encodeBase62(id);
// Write to the store (database in production)
urlStore.put(shortCode, longUrl);
// Populate cache (Redis in production)
if (cache.size() < CACHE_MAX_SIZE) {
cache.put(shortCode, longUrl);
}
// Return the short URL as JSON
String response = "{\"shortUrl\": \"http://localhost:8080/" + shortCode + "\", \"shortCode\": \"" + shortCode + "\"}";
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(200, response.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
System.out.println("Created: " + shortCode + " -> " + longUrl);
}
}
How does ShortenHandler map to your design?
This follows the exact write path from your URL Shortener design document:
- counter.incrementAndGet() is your distributed counter generating unique IDs.
- encodeBase62(id) converts the ID to a short code. This is the "Base62 of distributed counter" decision from your deep dive.
- urlStore.put() writes to the store, then cache.put() populates the cache. Exactly the write path: generate -> DB -> cache -> respond.
- Now add the RedirectHandler inner class directly below ShortenHandler. This implements the read path and cache-aside pattern. Make sure the final closing brace } closes the outer UrlShortenerServer class:
// Handles GET /{shortCode} - the READ PATH from your design
static class RedirectHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
String path = exchange.getRequestURI().getPath();
String shortCode = path.substring(1);
// Handle root path and favicon requests
if (shortCode.isEmpty() || shortCode.equals("favicon.ico")) {
String response = "{\"service\": \"URL Shortener\", \"usage\": \"POST /shorten with URL in body\"}";
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(200, response.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
return;
}
// Check cache first (cache-aside pattern)
String longUrl = cache.get(shortCode);
if (longUrl != null) {
System.out.println("Cache hit: " + shortCode);
} else {
// Cache miss: fall back to store (database in production)
longUrl = urlStore.get(shortCode);
if (longUrl != null) {
System.out.println("Cache miss, fetched from store: " + shortCode);
// Populate cache on miss
if (cache.size() < CACHE_MAX_SIZE) {
cache.put(shortCode, longUrl);
}
}
}
// Return 404 if short code does not exist
if (longUrl == null) {
String error = "{\"error\": \"Short URL not found\"}";
exchange.sendResponseHeaders(404, error.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(error.getBytes());
os.close();
return;
}
// 302 redirect (not 301, so we can track analytics)
exchange.getResponseHeaders().add("Location", longUrl);
exchange.sendResponseHeaders(302, -1);
exchange.getResponseBody().close();
}
}
}
How does RedirectHandler map to your design?
This is the read path from your architecture diagram:
- cache.get(shortCode) checks the cache first. On a hit, it skips the store entirely.
- On a cache miss, it queries urlStore (your database) and populates the cache for next time. This is the cache-aside pattern.
- The 302 redirect matches your trade-off decision: "I chose 302 over 301 because 302 lets us track analytics on every click."
- Save UrlShortenerServer.java.
Seeing red squiggles in your IDE?
- Make sure the closing brace } at the very end of the file closes the UrlShortenerServer class. You should have exactly three closing braces at the bottom: one for the handle method, one for RedirectHandler, and one for the outer class.
- Check that the class name UrlShortenerServer matches the filename exactly (Java requires this).
- help me debug a compilation error in my UrlShortenerServer.java
✔️ Awesome, I've got everything!
Great. Double check you have saved UrlShortenerServer.java and let's compile it next.
ⓧ I'd like to double check the full code
Here is the complete UrlShortenerServer.java file. Make sure yours matches exactly:
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class UrlShortenerServer {
private static final String BASE62_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final int SHORT_CODE_LENGTH = 7;
private static final int CACHE_MAX_SIZE = 1000;
private static final ConcurrentHashMap<String, String> urlStore = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
private static final AtomicLong counter = new AtomicLong(1000000);
public static void main(String[] args) throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
server.createContext("/shorten", new ShortenHandler());
server.createContext("/", new RedirectHandler());
server.setExecutor(null);
server.start();
System.out.println("URL Shortener running on http://localhost:8080");
}
static String encodeBase62(long number) {
StringBuilder sb = new StringBuilder();
while (number > 0) {
sb.append(BASE62_CHARS.charAt((int) (number % 62)));
number /= 62;
}
while (sb.length() < SHORT_CODE_LENGTH) {
sb.append('0');
}
return sb.reverse().toString();
}
static class ShortenHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) {
String error = "{\"error\": \"Method not allowed\"}";
exchange.sendResponseHeaders(405, error.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(error.getBytes());
os.close();
return;
}
InputStream is = exchange.getRequestBody();
String longUrl = new String(is.readAllBytes()).trim();
is.close();
if (longUrl.isEmpty()) {
String error = "{\"error\": \"URL is required\"}";
exchange.sendResponseHeaders(400, error.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(error.getBytes());
os.close();
return;
}
long id = counter.incrementAndGet();
String shortCode = encodeBase62(id);
urlStore.put(shortCode, longUrl);
if (cache.size() < CACHE_MAX_SIZE) {
cache.put(shortCode, longUrl);
}
String response = "{\"shortUrl\": \"http://localhost:8080/" + shortCode + "\", \"shortCode\": \"" + shortCode + "\"}";
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(200, response.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
System.out.println("Created: " + shortCode + " -> " + longUrl);
}
}
static class RedirectHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
String path = exchange.getRequestURI().getPath();
String shortCode = path.substring(1);
if (shortCode.isEmpty() || shortCode.equals("favicon.ico")) {
String response = "{\"service\": \"URL Shortener\", \"usage\": \"POST /shorten with URL in body\"}";
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(200, response.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
return;
}
String longUrl = cache.get(shortCode);
if (longUrl != null) {
System.out.println("Cache hit: " + shortCode);
} else {
longUrl = urlStore.get(shortCode);
if (longUrl != null) {
System.out.println("Cache miss, fetched from store: " + shortCode);
if (cache.size() < CACHE_MAX_SIZE) {
cache.put(shortCode, longUrl);
}
}
}
if (longUrl == null) {
String error = "{\"error\": \"Short URL not found\"}";
exchange.sendResponseHeaders(404, error.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(error.getBytes());
os.close();
return;
}
exchange.getResponseHeaders().add("Location", longUrl);
exchange.sendResponseHeaders(302, -1);
exchange.getResponseBody().close();
}
}
}
Compile and test your server
Time to prove your design works. You will compile the server, start it, and test both API endpoints with curl.
- Compile the server by running:
javac UrlShortenerServer.java
- If you see no output, the compilation succeeded.
Seeing compilation errors?
- Make sure you are running the command from inside the url-shortener/ directory where your .java file lives.
- Check that your JDK version is 21 or higher by running java --version. The readAllBytes() method requires JDK 9 or later.
- Compare your file against the full code in the "double check" tab above to spot any typos or missing braces.
- help me fix my javac compilation error
- Start the server by running:
java UrlShortenerServer
You should see URL Shortener running on http://localhost:8080 printed in your terminal.
- Open a second terminal window.
- Test the write path (POST /shorten) by running:
curl -X POST -d "https://www.example.com/very/long/url" http://localhost:8080/shorten
You should see a JSON response containing a shortUrl and shortCode field. Copy the short code value from the response.
- Test the read path (GET /{shortCode}) by running this command, replacing the short code with the value from your response:
curl -v http://localhost:8080/[[SHORTCODE="your short code from the response above"]]
You should see a 302 response with a Location header pointing to https://www.example.com/very/long/url.
- Run the same GET request again with the same short code to verify cache behavior.
Switch back to the terminal where your server is running. You should see Cache hit: followed by the short code on the second request. The first request showed Cache miss, fetched from store: because the initial write already populated the cache. Both requests resolved instantly without hitting the store.
How does this connect to your design?
The read path and cache-aside pattern you designed in Step 2 is now running as real code.
- The ConcurrentHashMap mirrors what Redis would do in production.
- The AtomicLong mirrors the distributed counter you chose for key generation.
- The 302 redirect confirms your trade-off decision about analytics tracking.
In an interview, being able to say "I have built this pattern" carries more weight than describing it abstractly.
Server not responding to curl?
- Make sure the server is still running in your first terminal. You should see the "URL Shortener running" message.
- Check that nothing else is using port 8080. If it is, stop the other process and restart your server.
- Verify you are using the second terminal for curl commands. The first terminal is occupied by the running server.
- help me troubleshoot why my Java URL Shortener server is not responding
Secret mission
Timed Design - YouTube/Netflix Video Streaming
You have internalized the framework across 6 systems. Now prove it transfers to completely new territory. Set a 20-minute timer and design a Video Streaming system (YouTube/Netflix) from scratch, covering patterns you have never practiced: adaptive bitrate streaming, transcoding pipelines, and CDN edge caching.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- The system-design-prep project directory (design documents and Java URL shortener).
- The URL Shortener server process (if still running on port 8080).
- Excalidraw diagrams saved in your browser's local storage.
Keep everything running
No action needed. Choose this if you are still actively reviewing your design documents or preparing for upcoming interviews.
- Your system-design-prep folder stays on your Desktop with all design documents ready for review.
- Your Excalidraw diagrams remain accessible in the browser at excalidraw.com (stored in local storage).
- If your URL Shortener server is still running, it continues using a small amount of memory.
Pause - I'll come back to this later
Shut down running processes to free up memory while keeping all your files intact.
- If your URL Shortener server is still running in a terminal, press Ctrl+C to stop it.
- Your design documents and Java source code remain in system-design-prep on your Desktop for next time.
- To export your Excalidraw diagrams for safekeeping, click the hamburger menu in Excalidraw and select Export to file. Save the .excalidraw file into your system-design-prep folder.
Delete - I don't want to use this again
Remove all project files and start fresh.
- If your URL Shortener server is still running, press Ctrl+C in the terminal to stop it.
- Delete the entire project directory by running this command from your Desktop:
macOS
cd ~/Desktop
rm -rf system-design-prep
Windows
cd ~\Desktop
rmdir /s /q system-design-prep
- To clear your Excalidraw diagrams from browser storage, open excalidraw.com, click the hamburger menu, and select Reset the canvas.
Nice Work!
Nice Work!
You just built a complete system design interview toolkit from scratch. You now have a repeatable framework, six worked designs, and running code that proves your architecture decisions work.
Here's what you accomplished:
- Internalized a 5-step system design framework and applied it to 6 real FAANG interview questions (URL Shortener, News Feed, Chat App, Rate Limiter, Search Autocomplete, Distributed Cache), identifying repeating patterns across all of them.
- Practiced the "strong hire" articulation pattern for trade-off discussions and documented 2026-specific interview expectations including cost reasoning, failure modes, and scaling paths.
- Built a working Java URL Shortener with Base62 encoding and a cache-aside pattern that connects your design decisions to running code.
- Secret Mission: Applied the full framework cold to a Video Streaming system under a 20-minute timer, proving the framework transfers to any question.
Ready to quiz yourself?