A system-design interview is not a memory test. In the first few minutes, the strongest signal is whether you can turn an ambiguous prompt into explicit constraints before proposing a database. This 20-minute drill uses a small Node.js script to make that habit concrete: estimate load, declare the hard limits, choose a first architecture, then name the failure you would address next.
I used to treat system-design practice as diagram practice. That produced tidy boxes and weak conversations: I would reach for Redis or Kafka before I could say what was slow, expensive, or unsafe. Interviewers can usually spot that immediately. A diagram is an answer; constraints are the reasoning that earns it.
This is a deliberately small exercise. It will not replace reading production postmortems or building systems. It does give you a repeatable way to rehearse the part that is hardest to bluff: making a decision and defending its downside.
What should you clarify before drawing anything?
Start every prompt with a short contract. For a hypothetical URL-shortening service, I would write these questions down before naming a component:
| Area | Question to ask | Why it changes the design |
|---|---|---|
| Users | Are links public, private, or both? | Authorization and cacheability change. |
| Traffic | What are average and peak reads and writes? | Peak load determines capacity, not the daily average. |
| Latency | Is 200 ms at the edge acceptable? | It determines where caching and regional routing matter. |
| Durability | Can a newly created link disappear after a regional failure? | This decides the replication and consistency budget. |
| Abuse | What must happen to a bursty anonymous client? | Rate limits and identity keys become first-class requirements. |
| Cost | Is the workload read-heavy enough to justify a cache? | It keeps “add Redis” from becoming a reflex. |
You do not need perfect numbers. You do need assumptions that can be revised when the interviewer says, “Actually, this is for 50 million daily active users,” or “Links must be revocable in under a minute.”
A useful sentence starter is: “I will optimize for X; I am accepting Y; if Z changes, I would revisit this choice.” That is more persuasive than presenting every component as universally necessary.
A 20-minute constraint-first drill
Create a file named design-drill.js and run it with Node 18 or later. It has no packages and intentionally prints the numbers you should be comfortable narrating.
const scenario = {
name: "short links",
dailyActiveUsers: 1_000_000,
requestsPerUserPerDay: 8,
peakMultiplier: 10,
averagePayloadKB: 2.5,
readToWriteRatio: 20,
targetP95Ms: 200,
maxNewLinkLossSeconds: 0,
};
const perDay = 86_400;
const bytesPerGB = 1024 * 1024 * 1024;
function round(value, digits = 1) {
return Number(value.toFixed(digits));
}
function estimateLoad(input) {
const dailyRequests =
input.dailyActiveUsers * input.requestsPerUserPerDay;
const averageRps = dailyRequests / perDay;
const peakRps = averageRps * input.peakMultiplier;
const dailyTransferGB =
(dailyRequests * input.averagePayloadKB * 1024) / bytesPerGB;
const writesPerSecond = peakRps / (input.readToWriteRatio + 1);
return {
dailyRequests,
averageRps: round(averageRps),
peakRps: round(peakRps),
peakWritesPerSecond: round(writesPerSecond),
dailyTransferGB: round(dailyTransferGB),
};
}
function chooseFirstDesign(input, load) {
const choices = [
"Stateless API service behind a load balancer",
"Primary key-value store for canonical links",
"Read-through cache for hot public links",
];
if (load.peakRps > 5_000) {
choices.push("Partition key space before a single writer becomes the bottleneck");
}
if (input.maxNewLinkLossSeconds === 0) {
choices.push("Synchronous durable write before returning a new short link");
} else {
choices.push("Asynchronous replication with an explicit recovery-point objective");
}
return choices;
}
function nextFailureToDiscuss(input) {
if (input.readToWriteRatio >= 10) {
return "Cache invalidation after a link is revoked or expires.";
}
return "Idempotent writes when a client retries after a timeout.";
}
const load = estimateLoad(scenario);
console.log(`\nScenario: ${scenario.name}`);
console.table(load);
console.log("\nFirst architecture:");
for (const choice of chooseFirstDesign(scenario, load)) {
console.log(`- ${choice}`);
}
console.log(`\nNext failure mode: ${nextFailureToDiscuss(scenario)}`);
console.log("\nFollow-up prompts:");
console.log("- What breaks first if peak traffic doubles?");
console.log("- Which data must be strongly consistent, and which can be stale?");
console.log("- How would you prove the cache is helping rather than hiding an outage?");
For the default input, the script estimates about 92.6 average requests per second, 925.9 peak requests per second, and 44.1 peak writes per second. The exact result matters less than the chain of reasoning:
- The service is read-heavy, so cache reads that are safe to serve stale.
- Link creation is comparatively rare, so make the write path durable and simple first.
- Revocation is the important edge case: a cache turns deletion into an invalidation problem.
- If the traffic estimate changes by an order of magnitude, revisit partitioning before adding complexity prematurely.
Notice what is missing: a claim that one storage engine is “best.” In a real interview, naming a product too early invites a product quiz. Framing the data access pattern first gives you room to compare options based on latency, durability, operational cost, and team familiarity.
How do you narrate the diagram without sounding rehearsed?
Use the script as a timer, not a script to recite. A useful 20-minute run looks like this:
- Minutes 0–3: Repeat the problem in your own words and ask three clarifying questions.
- Minutes 3–6: State one traffic assumption and one reliability assumption. Do the rough arithmetic out loud.
- Minutes 6–11: Draw the happy path: client, edge or load balancer, stateless service, storage.
- Minutes 11–15: Add only the component justified by your assumptions, such as a cache for hot reads.
- Minutes 15–18: Pick one failure mode: cache invalidation, a retry storm, hot keys, or a regional dependency.
- Minutes 18–20: Name a trade-off you would reverse if the requirement changed.
That final step is where many answers become memorable. For example: “I am starting with one writable region because the prompt values durable writes more than global write latency. If creators in multiple regions need low-latency writes, I would introduce region-aware ownership or a different consistency model, and I would measure the conflict rate before doing it.”
What follow-ups should you practice?
After each run, change only one input and explain what moves. This is more useful than redrawing the same architecture five times.
| Change | What a good answer explores |
|---|---|
| Peak multiplier rises from 10 to 100 | Hot-key protection, admission control, autoscaling limits, and overload behavior. |
| Revocations must take effect in 10 seconds | Cache TTLs, invalidation fan-out, and a source-of-truth check for sensitive reads. |
| Links can be private | Authorization at the service boundary and avoiding shared public-cache keys. |
| Writes must work during a regional outage | Replication topology, conflict handling, and the cost of stronger guarantees. |
Keep a “because” attached to every box. “A queue because traffic spikes can be absorbed and processed asynchronously” is a design decision. “A queue because architectures have queues” is decoration.
For retry behavior, the AWS Builders’ Library essay Timeouts, retries, and backoff with jitter is a useful primary reference: retries are not harmless when the dependency is already overloaded. For data consistency vocabulary, Martin Kleppmann’s Designing Data-Intensive Applications remains a solid reference. Read sources like these after the drill, then use the drill to see whether you can turn a concept into a spoken choice.
Where can AI fit without replacing the reasoning?
An AI practice partner is most useful after you have a first answer. Give it your assumptions and ask it to play a skeptical interviewer: “Why not use a relational database?” “What is your recovery objective?” “What metric would tell you this cache is harmful?” That turns passive reading into a conversation.
aceround.app — an AI interview assistant can be used in that narrow role: run a timed mock, capture the follow-ups that make you hesitate, and retry the same problem with one changed constraint. The value is not a prettier diagram; it is getting faster at explaining why a choice fits the prompt.
A final checklist
Before ending a practice session, make sure you can answer these in one sentence each:
- What is the primary user action and its latency target?
- What number represents peak load?
- Which write must be durable before acknowledging success?
- Which read may be stale, and for how long?
- What fails first under overload?
- Which component would you remove if the workload stayed small?
If you can make those answers explicit, the diagram becomes a consequence of your reasoning rather than a collection of familiar logos.
Disclosure: AI assistance was used to edit and organize this article. The code and technical claims were reviewed and tested before publication.

Top comments (0)