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
- Thinking the transcripts are about URL shorteners — they’re about behaviour. The moves transfer to
every question.
- Assuming the weak candidate failed on knowledge — they failed on process. Knowing more facts wouldn’t
have saved an answer with no requirements, no estimation, and no trade-offs.
- Only reading transcript A — the negative examples are where you catch your own habits.
🎙️ Soundbites
(These are the strong candidate’s lines — internalise the shape, not the words.)
- “Before I design, let me clarify the requirements.”
- “These numbers tell me the write path is easy and the read path needs caching.”
- “I’d lean toward X — the trade-off is Y, which is acceptable given our NFRs.”
- “That aligns with favouring availability, which we established up front.”
- “To summarise: [design] with the main trade-offs being [A] and [B]; with more time I’d detail [C].”
🛠️ 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