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 1System 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–3The 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 staleBuildLoad 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 3Caching 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 4The 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 5CAP 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–6You 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–7This 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–8System 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?