system-design

Mock Interview Scripts

Two full annotated transcripts of system design interviews — one that goes well, one that goes badly — so you can see the framework in motion and recognise the good and bad moves as they happen.

Prerequisites: The Framework, Common Mistakes Time to read: ~22 minutes


Why transcripts help

You’ve read the framework, the rubric, and the common mistakes. But reading about an interview is different from seeing one unfold minute by minute. 🚨 This chapter is two annotated transcripts — the same question (“Design a URL shortener”), one answered well and one answered poorly — with commentary calling out each move. Read them as a highlight reel: you’re training pattern-recognition for the good moves and the bad ones.

The question is deliberately simple so the behaviour stands out, not the technical content. Substitute any question — the moves are the same.


Transcript A — the strong candidate

Interviewer: Design a URL shortener like bit.ly.

Candidate: Great. Before I design, let me clarify the requirements.

🟢 Starts with clarification, not solutioning. (requirements)

Candidate: For functional: users give a long URL and get a short one; visiting the short URL redirects to the long one. Do we need custom aliases (user-chosen short codes)? Analytics on clicks? Link expiration?

Interviewer: Custom aliases yes. Analytics — assume a separate system handles it, out of scope. Expiration, let’s say optional.

Candidate: For non-functional: this is extremely read-heavy — redirects vastly outnumber creates. It must be highly available (a dead link is a bad experience) and redirects must be low-latency. Slight delay before a new link works is tolerable, so we can favour availability over strong consistency. Does that match your expectations?

🟢 States NFRs and interprets them into design directions (read-heavy, AP over CP). (requirements)

Interviewer: Yes. Let’s say 100 million new URLs per month.

Candidate: Let me estimate. 100M/month ≈ 40 writes/sec average. If reads are 100:1, that’s ~4,000 reads/sec, with peaks maybe 5× — call it 20K reads/sec. Storage: 100M/month × 12 × 5 years ≈ 6 billion URLs; at ~500 bytes each that’s ~3 TB. So: modest write rate, high read rate, terabytes of storage over years.

🟢 Estimation with a purpose — each number leads to a design implication. (estimation)

Candidate: These numbers tell me: the write path is easy, the read path needs caching, and storage fits comfortably in a sharded database or even a single large key-value store. Let me sketch the high-level design.

🟢 Explicitly connects numbers to decisions before drawing.

Candidate: [draws] Client → load balancer → stateless app servers. On create, the app generates a short code and stores code → long URL in the database. On read, it looks up the code and returns a 301 redirect. I’ll put a cache in front of the database for reads, since it’s so read-heavy. A CDN or the 301 itself helps too.

🟢 Clean high-level flow, both paths, cache justified by the read-heavy NFR. (diagrams)

Interviewer: How do you generate the short code?

Candidate: A few options. One: hash the long URL (e.g. MD5) and take the first few characters — simple but collisions need handling. Two: a counter that increments and base-62 encodes the number — no collisions, but the counter is a bottleneck and codes are guessable/sequential. Three: a pre-generated pool or a distributed ID generator like a range-based counter per server. I’d lean toward the base-62 counter approach with ranges handed to each server, so there’s no single bottleneck and no collisions. Trade-off: sequential-ish codes are somewhat guessable, which we could mitigate if that’s a concern.

🟢 Multiple options, trade-offs, a justified choice, and acknowledges the downside. (deep dives, tradeoff vocabulary)

Interviewer: With 20K reads/sec, how does the database hold up?

Candidate: The cache absorbs most of it — popular links are read constantly, so a high cache hit rate means the database sees far fewer reads. For the ones that miss, a key-value store keyed by short code is an O(1) lookup and scales horizontally by sharding on the code. So the DB load is manageable. If a single link goes viral — a hot key — the cache handles it, and I’d make sure that link is well-cached, possibly replicated across cache nodes.

🟢 Reasons quantitatively, names the hot-key edge case unprompted. (deep dives)

Interviewer: What happens if the database goes down?

Candidate: For reads, the cache continues serving popular links, so most redirects keep working — that aligns with favouring availability. Writes would fail, but new-link creation failing briefly is more tolerable than redirects failing. I’d use a replicated database with failover to a replica to minimise downtime. There’s a consistency trade-off with async replication — a just-created link might not be on the replica yet — but given our NFRs that’s acceptable.

🟢 Connects failure handling back to the stated NFRs; names the trade-off. (tradeoff vocabulary)

Interviewer: We’re running low on time. Can you summarise?

Candidate: Sure. Stateless app tier behind a load balancer; base-62 range-based code generation to avoid a bottleneck and collisions; a key-value store sharded on the short code for storage; and a cache in front to absorb the read-heavy load, which also gives us availability if the DB has issues. The main trade-offs were choosing availability over strong consistency for redirects, and accepting somewhat guessable codes for a simpler, bottleneck-free generator. If I had more time I’d detail the analytics pipeline and custom-alias collision handling.

🟢 Crisp summary tying back to requirements and trade-offs; flags what’s left. (common mistakes)

Verdict: Strong hire signal. Clarified first, estimated with purpose, both paths, justified choices with trade-offs, handled failure and edge cases, connected everything back to the NFRs, drove the process, and summarised. 🟢 This maps directly onto the rubric.


Transcript B — the weak candidate

Interviewer: Design a URL shortener like bit.ly.

Candidate: OK, so I’ll use a microservices architecture. There’ll be a URL service, a user service, an analytics service, and I’ll use Kafka between them, and a NoSQL database because it scales.

🔴 Jumps straight to solutioning, no requirements. Buzzwords with no justification (Kafka? user service — was auth even in scope?). (common mistakes)

Interviewer: Before that — what are the requirements?

Candidate: Um, shorten URLs and redirect. And it should scale to billions of users and be super fast.

🔴 Vague, doesn’t distinguish functional vs non-functional, invents scale (“billions of users”) without basis. (requirements)

Interviewer: Roughly how much traffic?

Candidate: A lot. Millions? I’ll just make everything horizontally scalable so it doesn’t matter.

🔴 Refuses to estimate; “scale everything” is not a design. No numbers means no justified decisions. (estimation)

Interviewer: How do you generate the short code?

Candidate: I’ll hash the URL with MD5.

Interviewer: What about collisions?

Candidate: Um… they won’t happen. MD5 is unique.

🔴 Wrong (hashes collide, especially truncated), and states it with false confidence rather than reasoning. (handling uncertainty)

Interviewer: With truncation they can. How would you handle one?

Candidate: I guess I’d… rehash? Or add a number. I’m not sure.

🟡 The recovery is OK-ish (append/retry is a real approach), but it arrives flustered, not reasoned. Better would be to reason from principles calmly. (handling uncertainty)

Interviewer: How does the read path perform under load?

Candidate: The NoSQL database is fast, so it’s fine.

🔴 No caching, no quantitative reasoning, hand-waves “it’s fast.” Misses the single most important insight — this is read-heavy and wants a cache. (deep dives)

Interviewer: What if a single link goes viral?

Candidate: The database handles it.

🔴 Doesn’t recognise the hot-key problem; no cache to lean on because none was proposed.

Interviewer: Why did you choose NoSQL over a relational database?

Candidate: Because it scales better.

🔴 Cargo-cult reasoning. Can’t articulate the actual trade-off; the data here is a simple key-value lookup, which is a reason, but the candidate doesn’t give it. (tradeoff vocabulary)

Interviewer: We’re low on time — summarise?

Candidate: Yeah so, microservices, Kafka, NoSQL, and it scales. Should be good.

🔴 Summary is a buzzword list with no trade-offs, no connection to requirements (which were never established). (common mistakes)

Verdict: No-hire signal. Never clarified, refused to estimate, over-engineered with unjustified buzzwords, missed the core caching insight, false confidence on collisions, and couldn’t articulate a single trade-off. Note the technical facts weren’t all wrong — NoSQL and hashing can work — but the judgment and process were absent, and that’s what’s graded.


The moves, side by side

Moment 🟢 Strong candidate 🔴 Weak candidate
Opening Clarifies requirements Jumps to a solution
Requirements Functional + NFRs, interpreted Vague, invented scale
Estimation Numbers → implications Refuses; “just scale it”
Design Both paths, cache justified Buzzword architecture
Code generation Options + trade-off + choice One option, false confidence
Read load Cache absorbs it, quantified “The DB is fast”
Hot key Named unprompted Not recognised
Failure Tied back to NFRs Not addressed
Uncertainty (would reason calmly) Flustered, bluffed
Summary Ties to requirements + trade-offs Buzzword list

🚨 Same question, same clock. The difference is entirely process and judgment — which is exactly what the rubric grades. The strong candidate didn’t know more distributed-systems facts; they used what they knew with structure and justification.


🚨 Interview traps


🎙️ Soundbites

(These are the strong candidate’s lines — internalise the shape, not the words.)


🛠️ Try it

1. Annotate your own mock. Record a practice interview (audio is enough) and mark every move 🟢 or 🔴 against the table above. You’ll hear your own weak-candidate moments — the buzzword you couldn’t justify, the estimation you skipped.

2. Rewrite transcript B. Take each 🔴 line and rewrite it as the strong candidate would have said it. Turning bad moves into good ones cements the difference more than reading transcript A ever will.

3. Run both roles. Interview a partner and be interviewed. Playing the interviewer — watching someone skip requirements or hand-wave load — makes the good moves obvious from the other side of the table.

4. Time-box it. Do a full 35-minute mock end to end and check you hit every stage: clarify, estimate, high-level, deep-dive, failure, summarise. Missing a stage under time pressure is the most common real-interview failure. (the framework)


Check yourself

1. The weak candidate wasn't technically ignorant — so why was it a no-hire? Because system design interviews grade *judgment and process*, not a checklist of facts, and the weak candidate failed on exactly those dimensions despite knowing real technologies. Notice that the weak candidate's technical statements weren't all false — NoSQL databases *can* scale well and *are* a reasonable choice for a key-value lookup, hashing *is* a valid code-generation approach, and microservices and Kafka *are* real, useful tools. The problem was that every choice was made without justification, without connection to requirements that were never established, and without the ability to articulate a single trade-off. The candidate proposed microservices and Kafka before knowing what the system even needed to do (over-engineering with no basis), refused to estimate (so no decision could be grounded in actual load), missed the single most important insight of the problem (it's read-heavy and wants a cache), recognised no edge cases (the hot key), stated a falsehood with confidence (MD5 doesn't collide), and justified choices with cargo-cult reasoning ("NoSQL scales better") rather than the actual reason (the data is a simple key-value lookup). An interviewer extrapolates from this that on a real team the candidate would reach for complex tools without justification, wouldn't ground decisions in data, would miss important considerations, and couldn't reason about trade-offs — which is a no-hire regardless of how many technology names they can recite. The rubric rewards the candidate who *uses* knowledge with structure and justification, and knowing more facts would not have rescued an answer that had no requirements, no estimation, and no articulated trade-offs. This is the central lesson: the interview is not a quiz, and you can know all the right words and still fail by not demonstrating the reasoning that connects them.
2. The strong candidate named the hot-key problem before being asked. Why does that matter so much? Because volunteering a relevant edge case unprompted is one of the strongest positive signals a candidate can send — it demonstrates senior-level judgment, the kind of thinking-ahead that distinguishes engineers who've operated real systems from those who've only studied them. When the candidate, while discussing the read path, proactively said "if a single link goes viral — a hot key — the cache handles it," they demonstrated several things at once: they understand that traffic isn't uniform and that popularity concentrates on a few items (a real-world operational insight), they anticipate failure modes before they bite rather than only reacting when prompted, they connect the edge case to their existing design (the cache absorbs it) rather than treating it as a separate problem, and they exhibit the habit of stress- testing their own design that separates strong engineers from weak ones. Contrast the weak candidate, who didn't recognise the hot-key problem even when *directly asked* "what if a link goes viral" — revealing both a gap in operational understanding and the absence of a cache to lean on because none was proposed. The reason this matters so much is that interviewers are trying to predict on-the-job behaviour, and an engineer who spots the hot key, the thundering herd, the cache stampede, or the failure cascade *before* it happens in production is enormously more valuable than one who only addresses problems after being pointed at them — the former prevents incidents, the latter cleans them up. Naming edge cases unprompted also drives the interview forward, showing you're leading the design rather than waiting to be interrogated, which is itself a positive signal. It's a compounding advantage: each unprompted, relevant consideration raises the interviewer's confidence that you'd catch the ones they didn't even ask about.
3. Both candidates had the same 35 minutes. What did the strong candidate's structure buy them? Structure bought the strong candidate *coverage and depth without wasting a second*, letting them hit every dimension the rubric grades within the same fixed clock that the weak candidate squandered. Because the strong candidate followed a framework — clarify, estimate, high-level design, deep-dive, failure, summarise — they moved deliberately from one stage to the next, never stalling on what to do or circling back to redo skipped work, and every minute produced signal: the clarification established the NFRs that every later decision referenced, the estimation produced numbers that justified the caching decision, the high-level design covered both read and write paths, the deep-dives went into code generation and load with real trade-offs, and the summary tied it all back together. The weak candidate had the identical 35 minutes but no structure, so time leaked away: they solutioned prematurely (wasting minutes on a microservices architecture that requirements might have ruled out), got redirected back to requirements they should have started with, refused the estimation that would have grounded their decisions, and never reached failure handling or a coherent summary — the same clock produced a fraction of the coverage. Structure also *reduces cognitive load under pressure*: knowing "I clarify first, then estimate, then draw" means you're not spending scarce mental energy deciding what to do next, freeing that energy for the actual technical thinking; the weak candidate, improvising, spent energy floundering. And structure ensures you don't forget a whole graded dimension — the most common real-interview failure is running out of time with failure handling or trade-offs never addressed, precisely because the candidate didn't budget the stages. The framework isn't bureaucracy; it's what lets a candidate convert a fixed, pressured 35 minutes into complete, deep, well-justified coverage instead of a partial, shallow, disorganised one.
4. The strong candidate repeatedly said things like "that aligns with favouring availability, which we established up front." Why is this callback technique effective? Because it demonstrates that the candidate's design decisions are *derived from requirements* rather than pulled from memory, which is the single most important thing an interviewer wants to see — that you can reason from a system's specific needs to its specific design, the actual skill the job requires. Every time the strong candidate connected a choice back to a previously-established requirement ("we favour availability, so the cache keeps serving redirects if the DB fails," "redirects failing is worse than creates failing, which is why..."), they showed that the NFRs weren't decorative preamble but the *load- bearing foundation* of the whole design — each decision traceable to a need. This does several things. First, it justifies the decision: instead of "I'll add a cache" (which could be cargo-culting), it's "I'll add a cache *because* we established this is read-heavy and latency-sensitive," which is a reasoned choice. Second, it demonstrates coherence — the design hangs together as a consistent response to one set of requirements rather than a grab-bag of independently-chosen components, showing the interviewer you're holding the whole problem in mind. Third, it shows the requirements-gathering at the start wasn't a ritual you performed and forgot; you're *using* it, which retroactively makes that early clarification look purposeful. Fourth, it makes trade-offs legible: "I'll accept eventual consistency here because we chose availability over strong consistency up front" frames a downside as a deliberate, requirement-driven decision rather than an oversight. The weak candidate, by contrast, had no requirements to call back to (they never established any) and so every decision floated free, justified only by buzzwords. The callback technique is the visible thread that proves you're designing *this* system for *its* needs — and an interviewer who sees that thread has strong evidence you'd do the same on a real problem where the requirements, not your memory of a reference architecture, must drive the design.
5. When the weak candidate was unsure about collisions, they bluffed ("MD5 is unique"). What should they have done, and why is that better? They should have reasoned calmly from first principles instead of asserting a falsehood with false confidence — something like: "Any hash truncated to a few characters can collide, since we're mapping a huge input space to a small output space — pigeonhole principle. So I need a collision-handling strategy: on insert, check if the code already exists, and if it does, append a discriminator or rehash and retry. For a system at this scale I'd want to think about how often collisions occur and keep the retry cheap." This is better for several compounding reasons. First and most obviously, it's *correct* — truncated hashes absolutely do collide, and stating otherwise is a factual error that an interviewer will catch and that damages credibility on everything else you say. Second, false confidence is actively dangerous as a signal: an engineer who asserts wrong things confidently is a liability on a real team, because colleagues can't trust their claims and wrong assertions ship to production; an interviewer who catches a confident falsehood downgrades not just that answer but their trust in all your other unverified claims. Third, reasoning from principles demonstrates exactly the skill being tested — you don't need to have *memorised* "MD5 collides when truncated" if you can *derive* it from the pigeonhole principle in the moment, and showing that derivation is more impressive than reciting a fact, because it proves you can handle novel situations the interviewer hasn't pre-loaded you for. Fourth, the calm framing ("let me reason about this") turns a moment of uncertainty into a *display of competence* rather than a stumble — the strong move under uncertainty is to acknowledge it, reason openly, and make progress, which reads as maturity and honesty. Notice the weak candidate's actual recovery ("rehash? or add a number? I'm not sure") contained a *correct* idea (append/retry is a real approach) but arrived flustered and unconfident, undermining even the right instinct — the problem wasn't only the initial bluff but the absence of a calm reasoning process to fall back on. The lesson: never trade a confident falsehood for the discomfort of admitting uncertainty; reasoning openly from principles is both more honest and, in an interview that grades thinking over recall, more impressive.

Further reading