Chapter 15 Interview: Putting It All Together
1. Design a URL shortener (full walkthrough).
Classic interview question. Let me walk through my thought process the way I'd do it live.
First, requirements. Write path: user submits a long URL, gets back something like short.ly/abc123. Read path: user hits that short URL, gets 301 redirected to the original. Read-heavy — maybe 100:1 read-to-write ratio. Let's say 100M new URLs/month, so ~1B redirects/month. That's roughly 400 reads/sec and 40 writes/sec — not insane.
The key design decision is how to generate the short code. I'd use a base62 encoding (a-z, A-Z, 0-9) of a unique ID. A 7-character code gives 62^7 = 3.5 trillion combinations — plenty. For ID generation, either a distributed ID generator (Snowflake-style) or a pre-generated pool of IDs. Avoid hash collisions by using IDs rather than hashing the URL.
Storage: a simple key-value store works — short code → long URL. I'd use something like DynamoDB or Cassandra for horizontal scaling. Add a cache layer (Redis) in front since reads are repetitive — popular links get hit millions of times.
For the redirect service, it's stateless — look up the code in cache (or DB on miss), return a 301. Horizontally scalable behind a load balancer. Analytics? Log redirect events to Kafka asynchronously, process them in a pipeline for click tracking.
- Encoding: base62 of unique ID, 7 chars = 3.5T possibilities
- Storage: KV store (DynamoDB/Cassandra) + Redis cache layer
- Read path: cache lookup → DB fallback → 301 redirect
- Scale: stateless redirect service, horizontally scaled
2. Design a real-time chat system.
Real-time chat is a fun one because it touches so many system design concepts. Let me structure this.
The real-time delivery layer uses WebSockets. Each user maintains a persistent connection to a chat server. When Alice sends a message to Bob, it hits Alice's connected server, gets persisted, then needs to reach Bob's server. If they're on different servers — which they likely are at scale — you need a pub/sub layer between servers. Redis Pub/Sub or Kafka can fan out messages to the right server.
Message storage: I'd use Cassandra or a similar wide-column store. Partition by conversation ID, sort by timestamp. This gives you efficient "load last 50 messages for this chat" queries. For group chats, same model — one partition per group, all messages appended.
Presence (online/offline status) is its own challenge. Heartbeats from WebSocket connections update a presence service. When a connection drops, mark offline after a grace period. Store in Redis with TTL-based expiry.
Offline delivery: if Bob isn't connected, queue his messages. When he reconnects, pull undelivered messages. A simple approach: store a "last seen message ID" per user per conversation and fetch everything after that on reconnect.
At scale — millions of concurrent connections — you shard WebSocket servers by user ID, use consistent hashing to route, and the pub/sub layer handles cross-server message delivery.
- Transport: WebSockets for bidirectional real-time delivery
- Cross-server routing: pub/sub (Redis/Kafka) between chat servers
- Storage: Cassandra partitioned by conversation, sorted by time
- Offline: queue messages, deliver on reconnect via last-seen pointer
3. Design a social media news feed.
The news feed problem is essentially: when a user opens the app, show them a personalized, ranked list of recent posts from people they follow. The challenge is doing this at scale with low latency.
Two fundamental approaches. Fan-out on write: when a user posts, immediately push that post into all their followers' feed caches. Pre-computed feeds — reads are instant, just fetch from cache. Fan-out on read: when a user opens their feed, pull posts from everyone they follow and merge/rank in real-time. Saves write amplification but reads are expensive.
The hybrid approach is what works at scale — it's what Twitter described. For normal users (say, under 10K followers), fan-out on write. Their posts get pushed to follower feeds instantly. For celebrities with millions of followers, fan-out on read — you can't write to 50 million feed caches every time they post. When rendering a feed, merge the pre-computed timeline with fresh pulls from celebrity accounts.
Storage-wise: each user's feed is a sorted list (by timestamp or rank score) in Redis. Posts themselves live in a separate store (Cassandra or similar). The feed cache just stores post IDs — you hydrate with full content on read.
Ranking adds another layer. Instead of pure chronological, a ranking service scores posts by engagement signals, recency, relationship strength. This can run as a lightweight ML model at read time over the candidate posts.
- Fan-out on write: push to follower caches — fast reads, expensive writes
- Fan-out on read: pull and merge at read time — cheaper writes, slower reads
- Hybrid: write-fanout for normal users, read-fanout for celebrities
- Storage: Redis for feed caches (post IDs), Cassandra for post content
4. How do you structure your thinking in a system design interview?
I have a framework I follow that keeps me organized and shows the interviewer I'm methodical. Takes about 35-40 minutes total.
First 5 minutes: clarify requirements. Functional (what does it do?) and non-functional (scale, latency, availability, consistency). Ask about the expected scale — number of users, requests per second, data volume. This scopes the problem and shows you don't jump to solutions.
Next, back-of-envelope estimation. Quick math: if 10M daily active users each make 20 requests, that's ~2,300 QPS average, maybe 10K peak. Storage: 10M users × 1KB profile = 10GB. This tells you whether you need distributed systems or a single box handles it.
Then high-level design — boxes and arrows. Major components: clients, API gateway, core services, databases, caches. Draw the data flow for the primary use cases. Get alignment from the interviewer here before diving deep.
Deep dive into 2-3 critical components. The interviewer usually steers you, or you pick the hardest parts. This is where you discuss trade-offs: SQL vs NoSQL, caching strategies, consistency models. Show you understand WHY you're choosing something, not just WHAT.
End with operational concerns: monitoring, failure modes, scaling bottlenecks. This shows senior-level thinking.
- Step 1: Clarify requirements — functional + non-functional + scale
- Step 2: Back-of-envelope math — QPS, storage, bandwidth
- Step 3: High-level architecture — main components and data flow
- Step 4: Deep dive — trade-offs on 2-3 critical decisions
- Step 5: Operational concerns — monitoring, failures, scaling
5. Walk me through approaching an unfamiliar design problem.
This happens all the time in interviews — you get asked to design something you've never built. The key is not to panic. The fundamentals apply everywhere; you just need to figure out which ones.
First, I'd decompose the problem into things I DO understand. "Design a ride-sharing system" — okay, I know how to do location-based queries, real-time matching is a variant of the assignment problem, payments are a separate service. Break the unfamiliar into familiar sub-problems.
Second, identify the core challenge. Every design problem has 1-2 things that make it hard. For ride-sharing, it's real-time geospatial matching under time pressure. For a search engine, it's building and querying an inverted index at scale. Name that hard thing explicitly — "I think the crux here is X" — and spend most of your time on it.
Third, reason from first principles about data flow. What data comes in? What transformations happen? What goes out? Even for an unfamiliar system, if you can trace data from input to output, you'll discover the natural component boundaries.
And honestly? Be transparent. Say "I haven't built one of these before, but here's how I'd reason about it." Interviewers respect that more than someone bullshitting. They're evaluating your thinking process, not your memorization of architectures.
- Decompose: break unfamiliar into familiar sub-problems
- Find the crux: identify the 1-2 genuinely hard parts and focus there
- Trace data flow: input → transform → output reveals component boundaries
- Be transparent: show your reasoning process, not memorized solutions