Skip to main content

Full Mock Walkthrough

Editorial

One problem, start to finish

Seven lessons of method. Now watch it run.

This is a complete forty-five minutes on one problem, minute by minute — what gets said, what goes on the board, where the forks are, and what a strong answer sounds like at each one. It is deliberately not a summary of the method; it's the method happening.

The problem is "design a URL shortener" — and we're using it on purpose, for two reasons. It's the classic deceptively-simple prompt, so if the method produces real depth here it will produce it anywhere. And you've already done its requirements phase: this is the same problem you scoped in Requirements & NFRs That Matter, so we pick up with the commitments you already made rather than starting cold.

Read it once end to end. Then run it yourself in the exercise partway down — because there's a fork in the middle, and the choice you make there decides which hard question you spend your deep dive on.

The whole run at a glance

Before the detail, the shape of the forty-five minutes — so you can see where each phase went and what it produced.

A timeline of one complete forty-five minute system design interview for a URL shortener, drawn as a horizontal clock across the top with the five canonical phases beneath it as bars placed at their actual minutes, forming a staircase, and what each phase produced written beside it. Requirements runs to about minute seven and holds the committed scope: create a link and redirect it, one hundred to one reads, sub-100 millisecond redirects, four nines availability, eventual consistency acceptable, with analytics named as out of scope. Estimation runs to about minute eleven and holds the two rates that decided the architecture — roughly thirty-eight writes per second, which is low enough that the correct move is NOT to shard writes, and about ten thousand peak reads per second, which is what earns the cache. High-level design runs to about minute twenty-four and holds one entity, two endpoints, the choice of a 302 rather than a 301, and the read-write split. A prominent marker above the axis and a dashed line dropping onto it show the end-to-end design going up on the board exactly at minute twenty, the load-bearing checkpoint. Deep dive is visibly the widest bar, running to about minute forty, and holds the two components that carry the risk: short-code generation and the read path under a hot link. Wrap-up closes the last five minutes with the trade-off taken and the honest weak point. The closing lines state what the drawing makes unarguable: the widest block is the one that shows depth, and nobody had to watch a clock for that to happen — following the phase budget puts it there.

Two things to notice before we walk it.

The board went up at minute twenty, exactly on the checkpoint — not because anyone was watching a clock, but because the budget from The Delivery Framework was followed and it lands there naturally.

And the deep dive is the widest block, which is the whole argument of that lesson made concrete: sixteen minutes on two components, versus thirteen on the entire high-level sketch.

Minutes 0–7 · Requirements

"Design a URL shortener."

Four words that sound finished. The clock is running.

What gets asked — not everything, just the few that carry a decision:

  • "Is the core just create-and-redirect, or also analytics, expiry, custom aliases?"
  • "What's the read-to-write ratio?"
  • "What latency should the redirect feel like, and how available does it need to be?"
  • "When a link is created, must it resolve everywhere instantly?"

What comes back: core is create and redirect; analytics is a bonus. One hundred reads for every write. Redirects under 100 ms at p99. 99.99% availability — a dead link is a broken product. Eventual consistency for a brand-new link is fine.

Then the commit — one sentence, and it's the most important sentence of the phase:

"So: create and redirect are the core, a hundred to one reads, sub-100-millisecond redirects, four nines, and eventual consistency is acceptable. Analytics is out of scope. I'll design to that — sound right?"

Seven minutes, and everything after this points back at it.

Minutes 7–11 · Estimation

"Roughly what scale are we talking about?"

Four minutes, and only the numbers that change a decision — watch what each one buys:

Writes. A hundred million new links a month is about 38 writes a second. That is nothing. A single primary store handles it without breaking a sweat.

⭐ That number's entire value was giving permission not to build write sharding. An estimate that stops you building something is worth more than one that justifies building something.

Reads. About 10,000 a second at peak. Two orders of magnitude above the writes — this is the number that earns a cache tier, and now the cache has a reason on the board next to it.

Key space. We need maybe ten billion codes over a few years. Codes are base62 — the 62 characters you can safely put in a URL, a–z, A–Z, 0–9 — so six of them give 62⁶ ≈ 56 billion combinations. Plenty. One line of arithmetic that fixes a real design parameter.

And that's it. No bandwidth calculation, no server count, no storage-growth curve — because none of them would have changed anything. (Storage, if asked: ~500 bytes a row, so roughly 600 GB a year. Comfortably one machine. Also not a reason to shard.)

Minutes 11–24 · High-level design

Now the board, and the chain from The Design Template — nouns, then verbs, then boxes.

The nouns. Almost embarrassingly few: one entity, Link, holding the code, the long URL, and when it was created. That's the domain.

The verbs.

POST  /links      {long_url}  →  {short_url}
GET   /{code}                 →  302 Location: <long_url>

⭐ 302, not 301 — and this is a genuinely senior detail that costs one sentence. A 301 says permanent, so browsers cache it aggressively: you could never revoke or repoint that link, and you'd never see the click again. A 302 keeps the redirect coming through you. Notice it also quietly preserves the analytics feature you scoped out — which is what "reversible decision" looks like in practice.

The boxes, built one endpoint at a time: client → edge → gateway → then a deliberate split into a read service and a write service → cache → primary store.

The split is the one structural choice here, and it's justified by the number from four minutes ago: at a hundred to one, the two paths scale on completely different axes. Splitting them lets you scale reads hard and leave writes alone.

Minute twenty. End-to-end design on the board — the checkpoint from The Delivery Framework, hit without watching a clock.

Then the last call of this phase, and it's the one that shapes everything after it.

The fork: how do you make a short code?

This is the decision the whole problem turns on, and it's worth slowing down for — because the three answers don't just differ in quality. They hand you different interviews.

A figure showing the single decision that most shapes a URL shortener interview, drawn as a fork with three branches converging onto two different follow-up questions. At the top sits the question: how do you make the short code. The left branch, in green and marked as the strongest choice, is a counter encoded in base62 — you increment and encode, uniqueness comes for free because the next number has never been used, so there is no collision check and no retry on the write path at all. The middle branch, marked defensible, hashes the URL and takes the first six characters, which is deterministic but means collisions happen and so every write has to check first. The right branch, marked weakest, is a random string: generate then check, the same cost as hashing without the deterministic one-to-one mapping. Arrows then show where each branch lands. The counter branch leads to a coordination question — where does the counter live when there are many write servers — answered by leasing blocks of a thousand values at a time and accepting that gaps do not matter, because what is required is uniqueness rather than continuity. The hash and random branches both land on a correctness question instead — two URLs produce the same code, then what — answered with a uniqueness constraint and a salted retry rather than a racy read-before-write check. The closing note states the insight the figure exists for: the choice did not make the interview easier or harder, it decided which hard question the deep dive would be spent on.

Hashing the URL is deterministic and it works. But hashes collide, so before every insert you have to check whether that code is already taken — which puts a database round-trip on the write path and needs retry logic. Random strings have the same cost and don't even give you determinism.

A counter, base62-encoded, makes uniqueness structural: the next number has never been used, so there is no check, no retry, and nothing on the write path. You've traded a correctness problem for a coordination problem — and coordination is the easier one to solve cleanly.

Whichever you pick, the interviewer's next question is already determined:

  • Counter → "where does that counter live? You have many write servers."
  • Hash or random → "two URLs produce the same code. What happens?"

Neither branch is a trap. But notice what just happened: a decision at minute sixteen chose the hard problem you'll spend minutes twenty to thirty on. That's the thing you can't feel from reading a finished solution, and it's what the exercise below lets you feel.

Minutes 24–40 · Deep dive

The biggest block, on the two components that carry the risk. Everything else stays named and justified — this is the pick two and go further from The Questions Everyone Asks.

The counter, and where it lives

"You've got a dozen write servers. Where does the counter live?"

The naive answer is one Redis instance with INCR. It's actually safe — Redis is single-threaded, so the increment is atomic and there's no race. But it's a network round trip on every single write, and one box on the critical path for all creation.

The answer that reads as experience:

"Each write server takes a block of a thousand counter values with one atomic INCRBY, then hands them out locally until it runs out. Redis sees one call per thousand writes instead of one per write."

And then the sentence that pre-empts the obvious follow-up before it's asked:

"If a server dies holding a block, we lose those codes — and that's fine. We need uniqueness, not continuity."

That one clause is the difference between someone who read about this and someone who has run it. It also opens the multi-region answer for free: give each region a disjoint range and cross-region coordination disappears entirely.

One more, unprompted, because naming your own weakness is a senior move: sequential codes are enumerable — anyone can walk your entire link space. The fix is to scramble the counter before encoding it, not to abandon the counter.

The read path, and the link that goes viral

"One link gets a billion clicks in an hour. What breaks?"

The cache is doing the work already — memory is roughly a thousand times faster than SSD, which is how a sub-100 ms p99 is met at all. And because a small minority of links carry most of the traffic, the working set is tiny and the hit rate is very high.

But a billion clicks on one link is a different problem: every request hashes to the same cache key, so one node takes all of it. Adding nodes does nothing.

The answer uses a property of the data: a code-to-URL mapping is immutable. So you can replicate that key across every cache node without any consistency worry — and better, push it to the CDN edge, where the redirect is served near the user and never reaches your infrastructure at all.

Cache invalidation, notably, is not a problem here — and saying so is worth ten seconds. It's only interesting when links expire or change, and we scoped both out.

Minutes 40–45 · Wrap-up

Five minutes to step back. Not to add anything — to make the last forty minutes countable.

"To summarise: we optimised for read latency first, and we paid for it with eventual consistency on newly-created links — which the requirements said was acceptable. The piece I'd harden next is the counter service: today a Redis failure stalls creation, though redirects keep serving. And if analytics came back into scope, it rides an async click stream off the redirect path — which is why the 302 mattered."

Four sentences. Priorities, the price paid, the honest weak point, and a decision from minute twelve paying off at minute forty-four.

Notice what wasn't done: no new boxes, no rate limiter bolted on at the end, no listing of things there wasn't time for. The wrap-up compresses; it doesn't grow.

Now run it yourself

Reading a good run is useful. Making the calls yourself is where it sticks.

Same prompt, same forty-five minutes. At each phase you choose what to say, and the model answer appears after you commit — so it teaches rather than tests. The clock is real, and the minute-twenty checkpoint is marked.

Then take the fork the other way. Choose hashing instead of the counter and the deep dive changes underneath you — you'll be defending collision handling instead of counter coordination. Same problem, same clock, a different hard question. That's the thing worth feeling twice.

Drive the whole forty-five minutes and compare each call to the model answer — then take the other fork and get a different deep dive.

What the board looked like at the end

Here's the whole thing as it would actually stand at minute forty-five.

The finished whiteboard from the worked URL shortener interview, drawn as the design would actually stand at minute forty-five. Across the top sit two pinned strips that never came down: the requirements — create and redirect, one hundred to one reads, sub-100 millisecond redirects, four nines availability — and the numbers, thirty-eight writes per second, ten thousand reads per second at peak, and sixty-two to the sixth being about fifty-six billion so six characters suffices. Beneath them the architecture flows left to right: a client, a CDN edge, an API gateway, then a deliberate split into a read service on the upper track and a write service on the lower one, with the read path continuing through a cache and the write path through a counter service, both landing on the same links store. Every edge is a synchronous call drawn as a solid arrow pointing from caller to callee; there is no asynchronous edge anywhere on this board, which is itself a consequence of the scope that was committed. Two components are ringed and numbered as the deep dives that were actually opened — the cache, whose hot key is replicated and then served at the edge, and the counter, which leases blocks of a thousand values at a time so that gaps do not matter. Along the bottom sit the two lines the last five minutes produced: the trade-off taken, read latency first paid for with eventual consistency, and what was parked — analytics, expiry and custom aliases, all scoped out. The count beneath the board is the point: one entity, two endpoints, seven boxes, and every one of them traces back to something agreed at minute seven, which is why a small board beats a crowded one.

It's small. One entity. Two endpoints. Seven boxes. Two things opened up.

Every arrow on it points from caller to callee and every one is solid — there isn't a single async edge, which is the conventions from Whiteboard & Diagram Craft being applied honestly rather than decoratively. Nothing here is queued because nothing here needed to be.

That's worth sitting with, because the instinct under pressure is that more boxes means a better answer. This board would beat a diagram with three times as much on it, for one reason: every element on it traces back to something that was agreed at minute seven. The cache is there because of 100:1. The read/write split is there because of 100:1. Six characters is there because of 62⁶. The 302 is there because analytics might come back.

Nothing on this board is decoration.

What actually made that run work

Read back over it and none of the individual facts were exotic. There was no clever trick and nothing you'd need to have memorised. What carried it was the method, doing exactly what the last seven lessons said it would:

  • The scope was committed out loud at minute seven, so everything afterwards had something to point back at.
  • Nothing was computed that didn't change a decision — and the very first number justified building nothing at all.
  • The design went up end-to-end by minute twenty, so there was something to deepen.
  • Two components got sixteen minutes; everything else stayed named and justified.
  • Every box carried a because, so no challenge required improvisation.
  • Nothing happened silently — every choice was narrated as it was made, which is the habit from Communicating While Designing doing its job under pressure.
  • The weak point was volunteered rather than discovered.

That last one is worth naming on its own. At no point did the candidate claim the design was finished or perfect. They said what it traded away, where it would break, and what they'd do next — and that reads as far stronger than a design presented as flawless.

You now have the method and you've watched it run. The last thing left in this part of the course is to check that it's actually yours — a fresh prompt, and your own clock. That's the Checkpoint, next.