System Design Road

Part 8 · Lesson 8.4

Two Walkthroughs and Ten Prompts

Practice prompts8 steps · 12 min

Where we are: the last lesson of the course. Lesson 8.1 gave you a plan for 45 minutes, lesson 8.2 the arithmetic, and lesson 8.3 the follow-up questions. This lesson puts them together: two complete walkthroughs, as a strong candidate might do them, and ten more prompts to practise on your own.

What you’ll learn

  • A full worked walkthrough of a URL shortener, step by step
  • A full worked walkthrough of a notification system
  • Ten more prompts to practise, with what each is really testing
  • How to review your own practice runs
Lesson trace

Lesson 8.4 Two Walkthroughs and Ten Prompts

Step 1 of 8 · 1 min · The story

The walkthroughs below follow lesson 8.1's six steps, with rough timings. They're one good answer each, not the only correct ones. A different design, well reasoned, with honest trade-offs, is just as good. Read them after you've tried the prompt yourself, against a clock.

Step 2 of 8 · 4 min

Walkthrough 1: a URL shortener

Prompt: "Design a service like bit.ly that turns long URLs into short links."

Clarify (4 minutes)

Candidate: "A few questions first. Core features: create a short link from a long URL, and redirect anyone who visits the short link. Should users be able to choose a custom alias, like /cricket-final? Do links expire? Do we need click analytics, like counts by country and time?"

Interviewer: "Custom aliases yes, expiry optional, basic click counts yes."

Candidate: "Scale: how many links created, and how many redirects?"

Interviewer: "Say 100 million new links a month, and a hundred times as many redirects."

Candidate: "And quality: redirects should be fast, under 50 ms or so from most places; a link that's been created must never point somewhere else; and the redirect path should be very highly available, since broken links are visible everywhere they've been shared. Analytics can lag by minutes. I'll leave out user accounts, link editing and malware scanning unless we have time."

Estimate (3 minutes)

Napkin math
Writes:  100M / month ÷ 2.5M s ≈ 40 links/s
Reads:   10B / month ÷ 2.5M s ≈ 4,000 redirects/s average, ~10,000 at peak
Storage: 100M × 500 B ≈ 50 GB / month ≈ 6 TB over 10 years
Codes:   12B links over 10 years; 62⁶ ≈ 57B, so 7 characters gives lots of room

Candidate: "So this is very read-heavy, 100 reads per write, and the data is small. The design should be about serving redirects fast and reliably, mostly from caches. Writes are easy."

Interface and data (4 minutes)

HTTP
POST /links        { long_url, custom_alias?, expires_at? } → { short_code, short_url }
GET  /{short_code} → 301 or 302 redirect to long_url
GET  /links/{code}/stats → click counts
Text
links:  short_code (PK), long_url, created_at, expires_at, owner
clicks: aggregated counts per (short_code, day, country)

Candidate: "The main access pattern is 'get the long URL by short code,' which is a simple key lookup. That suits a key-value or wide-column store, partitioned by short code, but at 6 TB over ten years and 40 writes a second, a sharded relational database would work fine too. I'll use a key-value store for the links, since we never need joins."

High-level design (10 minutes)

Candidate, drawing: "Two paths.

Creating a link: the client calls the API through the load balancer. The link service generates a short code, writes the link with a uniqueness check, and returns the short URL.

Redirecting: a request for /aB3xK9q hits the CDN edge first. If the edge has a cached redirect for it, it answers immediately. Otherwise, the request goes to the redirect service, which checks a cache (Redis), then the database, and returns the redirect. Each redirect also emits a click event into a log, Kafka, which an analytics consumer aggregates into the click counts."

Deep dive (16 minutes)

Candidate: "The interesting parts are generating short codes, making redirects fast and available, and analytics without slowing redirects down. Let me start with codes."

Generating short codes. Options:

  • Hash the long URL (say, the first 7 characters of a hash, in base 62). Simple, and the same URL gets the same code. But collisions happen, and must be detected and handled by trying again with a salt, which needs a uniqueness check on every write.
  • A counter, encoded in base 62. Each new link gets the next number, written in base 62 (0–9, a–z, A–Z). No collisions by design. But a single counter is a bottleneck, and sequential codes are guessable (lesson 4.3: someone could walk every link).
  • Ranges of counter values. Each link service instance takes a block of 10,000 numbers from a small coordination store (a ticket server, lesson 4.3) and uses them locally. No coordination per link, still no collisions. To make codes less guessable, run each number through a reversible scrambling step before encoding, so consecutive numbers give unrelated-looking codes.

Candidate: "I'd use ranges plus scrambling. Custom aliases go through the same table with a uniqueness constraint; if the alias is taken, the request fails clearly."

Fast, available redirects.

Candidate: "Redirects are the whole product, so:

  • 301 or 302? A 301 is a permanent redirect that browsers and CDNs can cache, which is fastest, but then we don't see repeat clicks, and we can't change or expire the link for people who've cached it. A 302 isn't cached by default, so every click reaches us. I'd use 302 with a short CDN cache time (say a minute), which still absorbs viral spikes at the edge while keeping counts accurate enough and expiry possible.
  • Cache: the hot links are a small fraction. A Redis cluster with a few gigabytes holds the popular ones (lesson 2.4); with a 99% hit ratio, the database sees about 100 reads a second at peak.
  • A viral link is a hot key (lesson 4.5): the CDN and a short local cache on redirect servers absorb it.
  • Availability: redirect servers are stateless, across zones and ideally regions (lesson 5.5), with the link data replicated to every region, since it's small and rarely changes. Even if the link-creation path is down, redirects keep working."

Analytics without slowing redirects.

Candidate: "The redirect never waits for analytics. It emits an event into Kafka and returns (lesson 4.1). A consumer aggregates counts per link per minute and writes them in batches, so a viral link doesn't become a hot row (lesson 4.5). If analytics falls behind, redirects are unaffected."

Wrap up (4 minutes)

Candidate: "At ten times the load, redirects scale by adding stateless servers and cache capacity, and the CDN absorbs most spikes. Failure modes: if the cache is cold after a restart, the database sees all reads, so I'd keep the cache replicated and warm it. Abuse: rate limits on link creation per account and IP block, and scanning destinations for malware asynchronously (lesson 6.3). I'd measure redirect success rate and p99 latency per region as SLIs (lesson 6.4). With more time, I'd add link editing and malware scanning."

Step 3 of 8 · 3 min

Walkthrough 2: a notification system

Prompt: "Design a system that sends push, email and SMS notifications for a large app with 100 million users."

This is the prompt Sana ran out of time on in lesson 8.1. Here's how it goes with the plan.

Clarify (5 minutes)

Agreed scope: other services trigger notifications through an API; three channels (push, email, SMS); users have preferences (channels on or off, quiet hours); two kinds of traffic: transactional ("someone replied to you," "your OTP code") and campaigns (a message to millions). Out of scope: building the push, email and SMS delivery networks themselves (we use providers), and the content editor.

Quality: transactional notifications should arrive within seconds; OTPs are urgent; nobody should get the same notification twice; campaigns must never delay transactional traffic; notifications can occasionally be dropped, except security ones.

Estimate (3 minutes)

Napkin math
Transactional: 100M users × 5/day = 500M/day ≈ 5,000/s avg, ~15,000/s peak
Campaign:      one campaign to 50M users over 30 minutes ≈ 28,000/s
Mix by channel: mostly push; email a fraction; SMS small (it costs money each)
Storage:       500M/day × 500 B ≈ 250 GB/day of notification records; keep 30 days

Candidate: "Campaigns more than double peak load, so they need their own lane and pacing. Providers' rate limits will be our real ceiling, not our servers. SMS volume must be limited carefully, because each one costs money."

Interface and data (4 minutes)

HTTP
POST /notify { user_id, type, data, idempotency_key, priority }
POST /campaigns { segment, template, schedule }
PUT  /users/{id}/notification-preferences

Data: preferences per user (read on every send, so cached); a notification record per send (status per channel, for de-duplication and support), stored partitioned by user, with a 30-day TTL (lesson 3.2); device tokens per user for push.

High-level design (10 minutes)

Candidate, drawing: "Callers hit the notification API. It checks the idempotency key, loads preferences from cache, applies quiet hours and per-user rate limits, records the notification, and puts one job per channel onto that channel's queue, in a priority lane: urgent (OTP, security), normal transactional, or bulk campaign. Channel workers pull jobs, render the template, and call the provider. Provider responses update the record. Delivery receipts and bounces come back via webhooks.

Campaigns go through a separate campaign service that expands the segment into users in chunks (lesson 4.4's splitter) and feeds them into the bulk lane at a controlled rate."

Deep dive (16 minutes)

No duplicates.

Candidate: "The calling service sends an idempotency key, say reply:9001:user4821 (lesson 1.5). The API stores it with a unique constraint, so a retried call returns the existing notification. Channel workers are at-least-once (lesson 4.1), so each send records (notification_id, channel) as sent, in the same transaction as the status update, and checks it before calling the provider. For providers that accept idempotency keys, we pass one along. A tiny window remains (a crash between the provider call and recording it), which is acceptable for most notifications."

Campaigns can't delay transactional traffic.

Candidate: "Separate queues and worker pools per lane (bulkheads, lesson 6.1), with urgent workers reserved. Campaigns are paced to stay within provider limits, leaving headroom for transactional traffic. If a provider slows down, the bulk lane backs off first (load shedding)."

Provider failures.

Candidate: "Timeouts and circuit breakers per provider (lesson 6.1). Retries with backoff and jitter, within a budget. For SMS and email, a secondary provider we can fail over to when the breaker opens. OTPs fail over immediately, campaigns can wait. Dead-letter queues for jobs that keep failing, with alerts."

Preferences and rate limits.

Candidate: "Preferences are read on every send, so they're cached with invalidation on change (lesson 2.4). Per-user rate limits (say, no more than 20 pushes an hour, 3 SMS a day) use a token bucket in Redis (lesson 6.3), failing open for push and closed for SMS, because SMS costs money. Quiet hours delay non-urgent notifications into a scheduled queue rather than dropping them."

Wrap up (4 minutes)

Candidate: "SLIs: time from API call to provider acceptance, per lane, and delivery success rate per channel (lesson 6.4). Bottlenecks at 10×: provider throughput and cost, especially SMS, more than our servers. Multi-region: the API and workers run in each region; campaigns run from one region at a time. Next steps: notification collapsing (combining '12 people liked your post' into one), and per-user delivery-time optimization."

Step 4 of 8 · 2 min

Ten prompts to practise

Try each against a 45-minute timer. The notes say what each is mostly testing and which lessons it draws on. Don't read them until you've finished.

1. Design a news feed (like Instagram or Twitter).

Fan-out on write vs read, and the hybrid for celebrities; feed storage and caps; ranking as candidate generation; deletes and blocks at read time; hot keys during viral posts. Lessons 4.4, 4.5, 2.4, 2.3.

2. Design a chat app (like WhatsApp).

WebSocket gateways and a connection registry; ordering by per-conversation sequence numbers; de-duplication with client IDs; offline sync; receipts; presence; groups. Lesson 7.1, plus 4.1 and 5.1.

3. Design a ride-hailing service (like Uber or Ola).

Location updates in memory; geospatial indexing with cells; atomic driver claims; partitioning by city; hot spots; ETAs on a road graph; durable trip state. Lesson 7.2, plus 5.3 and 4.5.

4. Design a video platform (like YouTube).

Resumable uploads; transcoding ladders in parallel chunks; HLS/DASH and adaptive bitrate; CDN design for popular and long-tail videos; storage tiers; view counting. Lesson 7.4, plus 2.3 and 4.5.

5. Design a payment system or digital wallet.

Integer money; double-entry ledger; idempotency keys end to end; unknown outcomes and status checks; state machines; sagas; reconciliation; hot accounts. Lesson 7.3, plus 1.5 and 4.2.

6. Design a rate limiter as a service.

Algorithms (token bucket, sliding window); keys; distributed state in Redis with atomic updates; local approximations at high volume; failing open or closed; headers. Lesson 6.3.

7. Design a distributed cache (like Redis Cluster or Memcached at scale).

Partitioning with consistent hashing or hash slots; replication; eviction; hot keys; cold starts; client-side routing and redirects. Lessons 2.4, 5.2, 4.5.

8. Design a search-as-you-type (autocomplete) service.

Prefix indexes (tries or edge n-grams); ranking by popularity; precomputed top results per prefix; caching very short prefixes; updating popularity from logs in batches; latency budget per keystroke. Lessons 3.4, 2.4, 4.1.

9. Design a ticket-booking system for a hugely popular concert.

Exact inventory under extreme contention; holds with expiry; queues to admit buyers fairly; payment flow with idempotency; preventing double booking; bots and rate limits. Lessons 4.5, 5.3, 7.3, 6.3.

10. Design a metrics and monitoring system (like a small Prometheus or Datadog).

High write volume of time series; storage designed for appends by time (LSM, compression); cardinality limits; downsampling old data; querying percentiles from histograms; alerting on burn rates. Lessons 3.2, 6.2, 6.4.

Step 5 of 8 · 1 min

Reviewing your own run

After each practice run, spend ten minutes answering these, honestly:

  1. Where did the time go? Compare against lesson 8.1's plan. Which step ran over?
  2. Was the design complete by about minute 25? Could you walk a request through every main flow?
  3. Which numbers did you use, and did each lead to a design decision?
  4. Which trade-offs did you say out loud? List them. Aim for at least five.
  5. Which failure modes did you cover before being asked?
  6. What would you do differently next time? Write one concrete thing.

Then do the same prompt again a week later, and compare.

Step 6 of 8 · 1 min

One more way to practise: design against a postmortem

The prompts above start from a product. A different, very effective kind of practice starts from a failure: take a real postmortem, and design a system that would have survived it. It forces every "what if" to have a concrete answer, and it's how many reliability engineers actually think.

Here's a short example using the S3 outage of February 2017 (lesson 2.3). The question: what happens to Chirp's photos if object storage in its main region is unavailable for four hours?

Work through it in the same steps as any design:

  1. What breaks? Every photo read that misses the CDN cache, and every upload.
  2. What can degrade instead of break? Reads: popular photos are already cached at the edge. Long cache lifetimes, plus stale-if-error, let the CDN keep serving them even when the origin errors. Uploads: the app can keep a photo on the phone and retry later, showing "will post when ready" instead of an error.
  3. What would remove the dependency? Cross-region replication of the bucket, with the CDN configured to fall back to the second region on errors, and the upload API able to write to either region.
  4. What does it cost? Double storage for photos, replication traffic between regions, and some complexity in the upload path. Is four hours of degraded photos worth that? For Chirp, maybe not in year one, and yes once it's the main way people share.
  5. How would you know it works? Test it: block the main region's bucket in a staging environment, or during a planned exercise, and watch what users would see.

Other postmortems in this course that make good practice: Slack's autoscaling feedback loop (lesson 2.1), Discord's hot partitions (lesson 3.2), Kinesis's thread limit (lesson 4.1), the AWS control-plane outage (lesson 5.5) and Roblox's 73 hours (lesson 6.4). For each, ask the same five questions about a system you know.

Step 7 of 8 · 1 min

The end of the course

You started this course with Priya waiting 1.2 seconds for her feed in Mumbai, and a single server answering in 20 milliseconds. From there, every lesson was one more thing that broke, and what it took to fix it: round trips, indexes, caches that held the database up, replicas that lost writes, celebrities who broke the feed, and a login service that only lived in Virginia.

Most of what makes someone good at system design isn't memorizing architectures. It's having seen enough things break to ask, about any design, what happens when this is slow, or full, or duplicated, or gone?, and knowing a few good answers to each. That's what these lessons have been building.

Good luck in your interviews. And if Chirp is ever down, it's probably DNS.

Step 8 of 8 · 1 min

Further reading

  • Alex Xu, System Design Interview: An Insider's Guide, volumes 1 and 2, for more worked prompts.
  • Martin Kleppmann, Designing Data-Intensive Applications, which you'll now find much easier to read.
  • The engineering blogs of companies whose products match these prompts: they're the best source of real-world details to mention.