Defend every box you draw.

System design gets taught as "memorize these diagrams," which is why people can reproduce an architecture but can't defend one decision in it. This path is about tradeoffs under constraints — so a system you've never designed before still has an obvious first question.

Duration
7–8 weeks
Format
Tradeoff-driven
Prerequisite
Built a real app before
You'll build
A design playbook

Each stage is one real constraint

Each stage introduces one force that reshapes a design — scale, state, storage, staleness, coupling, partition, blindness — and what it actually costs you to handle it.

A live playground, right here

No setup, no account. This is Stage 0's back-of-envelope estimate — the calculation that decides whether you need one server or fifty. Change the inputs and watch it move.

playground.js
Console
Run the code to see output here.

This is what's waiting at the end

Certificate of Completion
Your Name
has completed Defend Every Box You Draw — 9 stages, 9 passed quizzes
9 / 9 stagesDesign playbookVerified ID
🔒

Complete all 9 stages and their quizzes to unlock this.

Nine stages, in this order

Each stage leans on the one before it — you can't reason about caching until you understand the database load it's relieving, or about queues until you've felt synchronous coupling hurt.

STAGE 00

Thinking in constraints, not diagrams

Week 1

System design gets taught as “draw these boxes,” which is why people can reproduce a diagram but can't defend a single choice in it. Every real design decision is a tradeoff under constraints — start there.

What you'll learn
  • Latency vs. throughput vs. availability — what each actually means
  • Back-of-envelope estimation: requests/sec, storage, bandwidth
  • Why “it depends” is the correct answer, and what it depends ON
Code
// Back-of-envelope: can one server handle this?
const dailyActiveUsers = 100_000;
const requestsPerUserPerDay = 20;
const totalDaily = dailyActiveUsers * requestsPerUserPerDay; // 2,000,000

const avgPerSecond = totalDaily / 86_400;      // ~23 req/s — sounds easy
const peakPerSecond = avgPerSecond * 10;       // ~230 req/s — traffic isn't flat

// Peak, not average, is what your system must survive.
// This 10x rule of thumb catches most people out on the first estimate.
BuildEstimate the infrastructure for a URL shortener at 10M links/month — storage, read/write ratio, peak QPS. Write down every assumption.
Self-checkYour API handles 100 requests/second fine but falls over at 500. Where do you look first, and why there?
STAGE 01

Scaling — vertical, horizontal, and statelessness

Week 1–2

“Just add more servers” only works if your app is stateless — and most apps aren't, by accident. Understanding why is what makes horizontal scaling possible instead of theoretical.

What you'll learn
  • Vertical vs. horizontal scaling, and when each hits its ceiling
  • Why statelessness is the precondition for horizontal scaling
  • Load balancers, and what happens to in-flight requests during deploys
Code
// Stateful — breaks the moment you add a second server
const sessions = {}; // lives in THIS server's memory only
app.post("/login", (req, res) => {
  sessions[req.sessionId] = { userId };  // server B knows nothing about this
});

// Stateless — any server can handle any request
app.post("/login", (req, res) => {
  const token = jwt.sign({ userId }, SECRET); // state travels with the request
  res.json({ token });
});
BuildTake a stateful in-memory-session app and convert it to stateless — then reason through what breaks if you run two instances of each version.
Self-checkWhy does adding a second server sometimes make a stateful app behave worse rather than better?
STAGE 02

Databases — indexing, replication, sharding

Week 2–3

The database is where most systems actually break under load. Knowing what an index costs, not just what it speeds up, is the difference between fixing and guessing.

What you'll learn
  • How indexes work, and their real write cost
  • Read replicas, and the replication lag they introduce
  • Sharding — and why it's a last resort, not a starting point
Code
// Without an index: full table scan, O(n)
SELECT * FROM orders WHERE user_id = 42;

// With an index on user_id: tree lookup, ~O(log n)
CREATE INDEX idx_orders_user ON orders(user_id);

// The cost nobody mentions: every write now maintains this index too.
// 10 indexes on a hot write table = 10 extra structures updated per INSERT.

// Replication lag is real — a read right after a write may not see it
await db.write("INSERT INTO orders ...");
const rows = await replica.read("SELECT ... "); // may be milliseconds stale
BuildLoad 100k rows into a local table, time a query without an index, add one, and time it again — then measure the insert slowdown.
Self-checkWhen does adding a database index make things slower rather than faster?
STAGE 03

Caching — the biggest win and the sneakiest bugs

Week 3

Caching is the highest-leverage performance tool and the easiest way to serve confidently wrong data. Both halves matter equally.

What you'll learn
  • Cache layers: browser, CDN, application, database
  • Invalidation strategies — TTL, write-through, explicit busting
  • Cache stampede, and why a popular key expiring can take down a database
Code
async function getUser(id) {
  const cached = await cache.get(`user:${id}`);
  if (cached) return JSON.parse(cached);

  const user = await db.query("SELECT * FROM users WHERE id = ?", [id]);
  await cache.set(`user:${id}`, JSON.stringify(user), { ttl: 300 });
  return user;
}

// The bug: update the DB, forget the cache, serve stale data for 5 minutes
async function updateUser(id, data) {
  await db.query("UPDATE users SET ... WHERE id = ?", [id]);
  await cache.del(`user:${id}`); // this line is the whole ballgame
}
BuildAdd caching to a slow endpoint, then deliberately introduce the stale-data bug by removing the invalidation — and watch it serve wrong data.
Self-checkWhat's the hardest part of caching, and why specifically is it hard?
STAGE 04

Async work — queues and background jobs

Week 4

The moment you make a user wait for something they don't need to wait for, you've built a fragile system. Queues are how you stop doing that.

What you'll learn
  • Sync vs. async work, and deciding which is which
  • Message queues, workers, and retry semantics
  • Idempotency — why a job must survive being run twice
Code
// Bad: user waits for email delivery to finish signing up
app.post("/signup", async (req, res) => {
  const user = await createUser(req.body);
  await sendWelcomeEmail(user);  // 2s — and fails signup if email is down
  res.json({ user });
});

// Good: enqueue and return immediately
app.post("/signup", async (req, res) => {
  const user = await createUser(req.body);
  await queue.add("send-welcome-email", { userId: user.id });
  res.json({ user });  // instant; email happens out of band
});

// Idempotent worker — safe if the queue delivers twice
if (await alreadySent(userId)) return;
BuildMove a slow side-effect (email, image processing, report generation) out of a request handler into a queued worker with retries.
Self-checkWhy does sending a welcome email inside the signup request handler hurt the user, not just the architecture?
STAGE 05

Consistency, CAP, and honest tradeoffs

Week 5

CAP gets recited as trivia. What matters is recognizing which side of the tradeoff a given feature actually needs — a bank balance and a like count have genuinely different requirements.

What you'll learn
  • CAP theorem in practical terms, not just as a triangle
  • Strong vs. eventual consistency, feature by feature
  • Where eventual consistency is fine and where it's unacceptable
Code
// Eventual consistency is fine here — a slightly stale count harms nobody
likeCount: readFromReplica()

// Strong consistency required — stale data means real money moves wrongly
accountBalance: readFromPrimary()

// The design question is never "which is better" —
// it's "what does THIS feature actually require, and what does that cost?"
BuildTake one product (a social feed, say) and classify every feature as needing strong or eventual consistency — and defend each call.
Self-checkIn CAP terms, what are you actually choosing between during a network partition?
STAGE 06

Observability — logs, metrics, traces

Week 5–6

You cannot fix what you cannot see. Observability is what turns “the site feels slow” into “this query on this endpoint regressed at 14:32.”

What you'll learn
  • The three pillars: logs, metrics, traces — and what each answers
  • What to actually alert on (symptoms users feel, not every anomaly)
  • Structured logging, and correlation IDs across services
Code
// Unstructured: unsearchable at 3am during an incident
console.log("User did something bad");

// Structured: filterable, aggregatable, correlatable
logger.info({
  event: "payment_failed",
  userId: user.id,
  amount,
  reason: err.code,
  requestId: req.id,   // ties every log line of one request together
});

// Alert on what users feel — error rate, p99 latency —
// not on every CPU spike that nobody noticed.
BuildAdd structured logging with request IDs to an existing app, then trace one request end to end through the logs.
Self-checkWhy is “we'll add monitoring later” a bigger problem than it sounds?
STAGE 07

Designing a real system, end to end

Week 6–7

This is where the pieces combine. A real design isn't a list of technologies — it's a sequence of decisions, each justified by a requirement you established first.

What you'll learn
  • A repeatable framework: requirements → estimates → API → data model → scale → tradeoffs
  • Drawing the diagram last, after the reasoning, not first
  • Naming your design's weaknesses before someone else does
Code
// The order that actually works, every time:
1. Functional requirements    // what must it do?
2. Non-functional            // scale, latency, availability targets
3. Back-of-envelope          // how big is this really?
4. API design                // the contract
5. Data model                // what's stored, how it's accessed
6. High-level architecture   // NOW you draw boxes
7. Scale the bottleneck      // cache/shard/queue where it hurts
8. Tradeoffs & weaknesses    // what you gave up, and why
BuildDesign a URL shortener and a rate limiter end to end, following all 8 steps in order — written out, not just in your head.
Self-checkA junior asks why your design uses a queue instead of just calling the service directly. What's the strongest single reason?
STAGE 08

Communicating design under pressure

Week 7–8

System design interviews test how you think out loud, not whether you memorized an architecture. The best design communicated badly reads as a worse design.

What you'll learn
  • Structuring 45 minutes: clarify, estimate, design, scale, critique
  • Asking clarifying questions that actually narrow the problem
  • Handling “what if traffic 100x'd?” without abandoning your design
Code
// Weak opening — technology-first, requirements never established
"I'll use Kafka, Redis, and Cassandra."

// Strong opening — constraints first, technologies as consequences
"Before I design: is this read-heavy or write-heavy?
 Roughly how many daily active users?
 Is eventual consistency acceptable for the feed?"

// Every technology you name later should answer one of those.
BuildDo 3 timed 45-minute mock designs out loud (record yourself) — then re-listen and mark where you jumped to a technology before establishing the requirement.
Self-checkIn a system design interview, why is starting by naming technologies a weak opening?

The capstone: two designs, fully defended

Two systems designed end to end following all 8 steps — written out, not just imagined — plus three recorded 45-minute mock designs. The written designs are what you reread before an interview; the recordings are how you find out you jump to technologies too early.

Two full designs, written out in all 8 steps
Back-of-envelope numbers with stated assumptions
An explicit bottleneck, and how you'd scale it
Tradeoffs section — what you gave up, and why
3 recorded 45-minute mock designs
Notes on where you named tech before requirements

Common pitfalls

  • Technology-first answers. Naming Kafka before establishing requirements signals pattern-matching, not reasoning.
  • Designing for imaginary scale. Sharding a system with 200 users isn't foresight, it's a red flag.
  • Memorizing diagrams. You'll be asked "why?" about every box — a remembered picture has no answer.
  • Hiding the weaknesses. Every design has them. Naming yours first reads as senior; being caught out doesn't.

Resources worth your time

  • Designing Data-Intensive Applicationsthe book
  • System Design Primer (GitHub)free
  • High Scalabilitycase studies
  • AWS Architecture Centerpatterns
  • Google SRE Bookfree, ops