Module 1: How to Approach a System Design Problem

5. The 4-step framework

Description

Up to here you've learned the loose pieces: distinguishing requirements, asking questions, scoping. This lesson assembles them into a repeatable method you can apply to any design problem without freezing again in front of the blank page. It's called the 4-step framework, and it's the backbone of the rest of the guide: (1) requirements → (2) estimation → (3) high-level design → (4) deep dive. Always in that order, always those four. It's not a straitjacket —you go back and forth between steps when you discover something—, but it's a script that tells you, at every moment, what to do now and what's not up yet. The reason people get stuck on an open problem isn't that they lack knowledge; it's that they don't have an order. The framework is that order.

Each step produces something concrete and rests on the previous one. Step 1 (requirements) produces the list of functionals, non-functionals, and scope —everything you learned in lessons 2 to 4—. Step 2 (estimation) turns those requirements into capacity numbers: QPS, storage, memory —what you'll see in depth in module 2, and which here we run end to end for Enlace—. Step 3 (high-level design) draws for the first time the boxes and arrows that satisfy those requirements with those numbers —Enlace's single box, which is lesson 6—. And step 4 (deep dive) goes down to the components the numbers flag as critical —ID generation, caching, sharding— which are, precisely, modules 3 to 7 of the guide.

Connection to the module: this lesson is the heart of the module, the one that gives method-shape to everything else. Lessons 2, 3, and 4 were, in reality, step 1 broken down. Lesson 6 will be step 3 applied to Enlace. Step 2 we present here (running the numbers you already know) and we develop it in module 2. Step 4 is the map of the whole guide —each later module is a "deep dive" into a component—. If you take a single thing from module 1, let it be this framework: it's the tool that turns "I don't know where to start" into "I know exactly what my next move is".

The pilot's checklist

Think of it this way. An airplane pilot, before takeoff, doesn't improvise. They pull out a checklist and run through it in order: check fuel, check flaps, test the controls, confirm with the tower. Not because the pilot doesn't know how to fly —they have thousands of hours—, but precisely because they know how to fly: they know that in a moment of pressure memory fails, steps get skipped, and a skipped step in a plane is paid for dearly. The checklist doesn't replace the knowledge; it orders it, so that not even the best pilot on the worst day forgets to lower the landing gear. And notice the order: you don't check the flaps before the fuel by whim; there's a sequence because each step gives information the next one needs.

The magic of the checklist is what it does to anxiety. Faced with a cockpit full of instruments, a beginner is overwhelmed —"where do I start?"— and even an expert pilot would feel the weight if they had no method. The checklist dissolves that paralysis: you don't have to decide what to do, you have to do the next thing on the list. The blank page —or the cockpit full of dials— stops being scary when there's a script that runs through it step by step.

A system design problem is that cockpit full of instruments. "Design a URL shortener" confronts you with a thousand possible decisions —which database?, cache?, how many servers?, how do I generate the codes?— and without method it overwhelms you. The 4-step framework is the designer's checklist: it doesn't tell you what to design (that depends on the problem), it tells you in what order to think, so you never stay staring at the blank page not knowing what your next move is. Requirements first (what and how well?), estimation next (what numbers?), high-level design then (what boxes?), and deep dive at the end (what does each critical box look like inside?). In that order, because each step feeds the next: you can't estimate without requirements, or draw without numbers, or deep-dive without knowing what boxes there are.

It's worth spelling it out in full:

The 4-step framework —requirements, estimation, high-level design, deep dive— is the designer's checklist: it doesn't replace your judgment, it orders it, so you never freeze in front of an open problem or jump to drawing before understanding.

The four steps, one by one

Step 1 — Requirements

What you produce: the list of functional requirements (what it does), non-functional ones (how well, with numbers), and the scope table (what's in, what's deferred). It's everything you learned in lessons 2, 3, and 4, together. This is where you ask or, if there's no one to, state assumptions. For Enlace, step 1 delivers:

FUNCTIONAL:  shorten(long_url)->short_code ; resolve(short_code)->redirect ; 404
NON-FUNCTIONAL: ~40 writes/s, ~4000 reads/s (100:1, read-heavy);
                latency < 100 ms; availability 99.9%; high durability;
                eventual consistency OK
SCOPE v1:   in = shorten, resolve, 404 ; out = analytics, custom
            URLs, expiration (deferred)

Step 1 rule: don't draw anything yet. If you catch yourself thinking about databases or servers here, you got ahead of yourself.

Step 2 — Estimation

What you produce: the capacity numbers that derive from the requirements —read and write QPS, storage over N years, working-set memory, bandwidth—. It's the back-of-the-envelope math, and it's so important that it has a whole module (module 2). Here we run it end to end for Enlace, because these numbers are the ones that will justify each decision in the design. Without step 2, step 3 would be drawing boxes at random; with it, each box has a number behind it that demands it.

Step 2 rule: round without fear. Estimation seeks the order of magnitude (tens, thousands, millions?), not the exact digit. "~4000 reads/s" is the useful answer; "3858.02 reads/s" is false precision.

Step 3 — High-level design

What you produce: the first diagram —boxes and arrows— that satisfies the requirements (step 1) at the estimated scale (step 2). This is where you finally draw: the client, the server, the database, the cache, the load balancer —whichever are needed according to the numbers, no more and no less—. For Enlace, the high-level design starts deliberately simple: a single box (a server + a DB), which is all of lesson 6. The write path and the read path are shown.

Step 3 rule: the simplest design that meets the requirements wins. Don't add cache, replicas, or microservices until a number from step 2 forces it. Starting simple and complicating under numerical pressure is the central discipline of the craft.

Step 4 — Deep dive

What you produce: the internal design of the critical components —the ones estimation flagged as the bottleneck—. Here you go down from "boxes and arrows" to "how this box works inside": how the short_codes are generated without colliding (module 3), how to cache the read path (module 4), how to scale the DB with replicas and sharding (module 5), how to balance (module 6), how to make it reliable (module 7). You don't deep-dive into everything —that would be infinite—; you deep-dive into what the numbers marked as important.

Step 4 rule: deep-dive where the number squeezes, not where you feel comfortable. The temptation is to deep-dive into what you already know; the craft is to deep-dive into the real bottleneck. In Enlace, the ~4000 reads/s send you to deep-dive into the read path (cache), not the write one.

Put in sequence, with what each one feeds the next:

graph LR
    R[1. REQUIREMENTS<br/>functional, non-functional,<br/>scope] --> E[2. ESTIMATION<br/>QPS, storage,<br/>memory]
    E --> D[3. HIGH-LEVEL DESIGN<br/>boxes and arrows<br/>the simplest that works]
    D --> P[4. DEEP DIVE<br/>critical components<br/>the number flags]
    P -.you discover something.-> R

Notice the dotted arrow going back: the framework is iterative. If while deep-diving (step 4) you discover that a component can't handle it, you go back to requirements or estimation and adjust. It's not a rigid single-pass waterfall; it's a cycle you run through, and in which each loop refines the design. But the first pass always goes in order, 1→2→3→4, because each step needs what the previous one produced.

Worked example: Enlace's step 2, end to end

Let's run the complete step 2 for Enlace, taking the step-1 requirements as input. This is a preview of module 2 —there each calculation is explained in detail—; here what matters is seeing step 2 as a step of the method: it turns requirements into the numbers that will guide the design.

# STEP 2 of Enlace: from requirements to capacity numbers
# Inputs (from step 1): 100M URLs/month, 100:1 ratio, ~1 KB/record, 5 years

writes_per_month = 100_000_000
read_write_ratio = 100
bytes_per_record = 1024
retention_years = 5

# --- QPS (operations per second) ---
seconds_per_month = 30 * 24 * 3600            # ~2.6 million
qps_write = writes_per_month / seconds_per_month
qps_read = qps_write * read_write_ratio

# --- Storage over N years ---
records_total = writes_per_month * 12 * retention_years
storage_bytes = records_total * bytes_per_record

# --- Code space (base62, 7 chars) ---
code_space = 62 ** 7
fraction_used = records_total / code_space * 100

print("=== STEP 2: ENLACE ESTIMATION ===")
print(f"writes/s          = {qps_write:8.1f}   -> round ~40/s")
print(f"reads/s           = {qps_read:8.1f}   -> round ~4000/s")
print(f"records at 5 years= {records_total:>14,}")
print(f"storage           = {storage_bytes/1e12:8.2f} TB -> round ~6 TB")
print(f"code space        = {code_space:>14,}  (62^7)")
print(f"fraction used     = {fraction_used:8.3f} %  -> plenty to spare")

What to expect. Running this with Python 3.14.0:

=== STEP 2: ENLACE ESTIMATION ===
writes/s          =     38.6   -> round ~40/s
reads/s           =   3858.0   -> round ~4000/s
records at 5 years=  6,000,000,000
storage           =     6.14 TB -> round ~6 TB
code space        =  3,521,614,606,208  (62^7)
fraction used     =    0.170 %  -> plenty to spare

There you have the whole step 2, and notice how each number speaks to steps 3 and 4:

  • ~40 writes/s → the write path is calm; in step 3, a single DB absorbs it. No need to deep-dive (step 4) here.
  • ~4000 reads/s → the read path is the bottleneck; in step 4 you have to deep-dive into caching (module 4) and balancing (module 6). This number, by itself, marks where the effort goes.
  • ~6 TB at 5 years → too much to keep comfortably on one machine forever; in step 4 you have to deep-dive into replicas and sharding (module 5).
  • 62⁷, 0.17% used → 7 characters suffice with plenty to spare; in step 4, ID generation (module 3) has free space to choose a strategy without fear of running out.

That's the power of the framework: step 2 doesn't produce numbers for the sake of it; it produces signals of where to design and where to deep-dive. Each figure points to a later step. Designing without step 2 is drawing boxes blindly; with it, each box of step 3 and each deep dive of step 4 has a number that justifies it. That's why the order matters: estimation goes before the design, not after, because it's the one that tells the design what shape to take.

The framework isn't a rigid recipe

A warning, because the framework can be misunderstood as a formula applied the same way every time. It isn't. It's an order checklist, not a recipe of fixed ingredients. Three nuances:

You iterate, you don't go in a straight line. As the dotted arrow shows, when you deep-dive (step 4) you discover things that send you back to the requirements (step 1). Example: you deep-dive into the cache and realize you need to know how many links are "hot", data you didn't estimate —you go back to step 2—. That's not failing; it's the framework working. The first pass goes 1→2→3→4; the following ones go back and forth.

The weight of each step depends on the problem. In a read-heavy system like Enlace, step 4 concentrates on the read side (cache, replicas). In a write-heavy system —say, log ingestion— step 4 would concentrate on the write side (queues, write partitioning). The framework is the same; where you deep-dive changes with the numbers.

Not every problem needs all four steps in depth. For the intranet Enlace (0.6 reads/s), step 2 gives numbers so small that step 4 is almost empty —one box is enough, there's no bottleneck to deep-dive into—. The framework scales with the problem: in a small system, steps 3 and 4 are short; in a big one, they're most of the work. Recognizing how much to deep-dive is, itself, design judgment —don't deep-dive into a bottleneck the numbers say doesn't exist—.

Common mistakes

Jumping to step 3 (drawing) without doing 1 and 2. What happens: it's the mistake from lesson 1, now with a step's name. Someone starts with the boxes (step 3) without requirements (step 1) or numbers (step 2), and draws an architecture they don't know is too much or too little for the real problem. Why it happens: drawing is the fun and visible part; requirements and estimation feel like paperwork. How to detect it: if your diagram has a cache but you can't say what number demands it, you skipped step 2. How to fix it: order discipline. You don't draw until you have requirements and estimation. The framework exists precisely to resist this temptation.

Estimating with false precision. What happens: someone, in step 2, computes "3858.0246 reads per second" and treats it as an exact figure, when the input ("100M a month", "100:1 ratio") was already a rough approximation. The precision of the output can't exceed that of the input. Why it happens: the calculator gives many digits and they're confused with exactness. How to detect it: if you drag decimals in a back-of-the-envelope estimate, you have excess precision. How to fix it: round to friendly orders of magnitude (~40, ~4000, ~6 TB). Step 2 seeks the scale, not the digit. Module 2 turns this into a method.

Treating the framework as a single-pass waterfall. What happens: someone runs through 1→2→3→4 once, in a straight line, and calls the design done without going back, even though while deep-diving (step 4) things appeared that invalidate an assumption from step 1. The design ends up with a crack the process itself would have closed if it had iterated. Why it happens: "first-pass order" is confused with "going back is forbidden". How to detect it: if you never went back to touch the requirements after drawing, you probably didn't iterate. How to fix it: remember the dotted arrow. Discovering in step 4 something that changes step 1 is the framework working, not failing. Go back and adjust.

Exercises

Exercise 1 — Place each activity in its step. You're designing Enlace and you do these things, out of order. Assign each one to the framework step (1: requirements, 2: estimation, 3: high-level design, 4: deep dive): (a) You compute that there are ~4000 reads/s. (b) You decide that analytics stays out of v1. (c) You draw a server connected to a database. (d) You design how to generate short_code with base62 without collisions. (e) You state the assumption "100M URLs/month". (f) You choose to put a cache in front of the DB because the 4000 reads/s justify it.

See solution
  • (a) Step 2 (estimation). Computing QPS is the heart of estimation.
  • (b) Step 1 (requirements). Deciding the scope —what's in, what's deferred— is part of setting requirements.
  • (c) Step 3 (high-level design). Drawing boxes and arrows for the first time is high-level design.
  • (d) Step 4 (deep dive). Designing ID generation internally is deep-diving into a critical component.
  • (e) Step 1 (requirements). Stating a scale assumption is setting a non-functional requirement.
  • (f) Step 4 (deep dive) —justified by step 2—. Adding a cache is a deep-dive decision, but notice the key: it's justified by a step-2 number (the 4000 reads/s). Without that number, the cache would be decoration; with it, it's an answer.

The lesson: real design jumps between steps, but each activity belongs to a step, and recognizing which one tells you whether you're respecting the order. Note how (f) links step 2 (number) with step 4 (decision): that's how the framework chains together. No deep-dive decision (step 4) should exist without a step-2 number that demands it.

Exercise 2 — Run step 2 of another system. Apply only step 2 (estimation) to this prompt: "Design a profile-picture service: 10 million users upload a photo (200 KB average) on signup, and each photo is viewed about 50 times a day." Estimate, with back-of-the-envelope arithmetic: writes/s (uploads), reads/s (views), and the total storage of the photos. Round to orders of magnitude.

See solution

Let's assume the 10 million users sign up over, say, a year (for the uploads), and that the views are over the 10 million existing photos.

  • Uploads (writes). 10M photos / year ÷ (365 × 86,400 s) ≈ 10,000,000 / 31,536,000 ≈ ~0.3 uploads/s. Very little: the write path is trivial.
  • Views (reads). 10M photos × 50 views/day = 500M views/day. ÷ 86,400 s ≈ ~5,800 views/s, rounded ~6,000 reads/s. Read-heavy, just like Enlace.
  • Storage. 10M photos × 200 KB = 10,000,000 × 200,000 bytes = 2 × 10¹² bytes = ~2 TB.

Interpretation (what step 2 tells steps 3/4): the writes are negligible (~0.3/s), the reads are the bottleneck (~6,000/s → cache and CDN to serve images), and 2 TB of photos fit in object storage without drama. Notice the parallel with Enlace: another read-heavy system where step 4 goes entirely to the read path. The framework is the same; only the numbers change.

Exercise 3 — Diagnose a design done backwards. A colleague shows you their Enlace design: they started by drawing three microservices, a load balancer, two cache tiers, and a database sharded across eight nodes. When you ask "how many operations per second does Enlace handle?", they answer "I didn't compute it, but surely it's a lot". Which framework steps did they skip, in what order should they have done them, and what concrete risk does their design run for having skipped them?

See solution

Your colleague went straight to step 3 (high-level design) and even to step 4 (deep dive: two-tier cache, sharding across eight nodes), skipping step 1 (requirements) and step 2 (estimation). The correct order was 1→2→3→4: first set requirements (what scale?), then estimate (how many ops/s, how much storage?), and only then draw and deep-dive.

The concrete risk: over-engineering. Without step 2, they don't know whether Enlace does 40 or 40,000 operations per second. They may have designed eight shards and two cache tiers for a system that, with the real numbers (~40 writes/s, ~4000 reads/s, 6 TB), maybe needs a single DB with replicas and one cache —much less—. Or, the reverse, they could have underestimated and fallen short. In any case, their architecture isn't justified by numbers: it's a collection of impressive components without a calculation that demands them. The fix: stop, do steps 1 and 2, and let the estimation say what components are really needed —probably far fewer than eight shards—. The simplest design that meets the requirements wins; they built the most complex one they could think of.

Summary and next step

In this lesson you assembled the whole module into a repeatable method: the 4-step framework —requirements → estimation → high-level design → deep dive—, the designer's checklist that dissolves the paralysis of the blank page. Each step produces something concrete and feeds the next: the requirements give the goals, the estimation turns them into numbers, the high-level design draws the boxes those numbers demand, and the deep dive goes down to the detail of the components the bottleneck flags.

You ran Enlace's step 2 end to end and saw its true power: each number isn't an isolated fact, it's a signal of where to design —the ~40 writes/s say "calm write", the ~4000 reads/s say "deep-dive here into caching", the 6 TB say "replicas and sharding", the 0.17% of 62⁷ says "the IDs are plentiful"—. And you saw that the framework isn't a rigid recipe: it's iterated (the return arrow), its weight changes with the problem, and it scales with the size of the system.

Before moving on you should be able to: recite the four steps in order and what each produces; place any design activity in its step; run step 2 of a new system with back-of-the-envelope arithmetic; and diagnose a design done backwards (that jumped to drawing without estimating).

What comes next is step 3 in action. With clear requirements (lessons 2–4) and the estimation run (this lesson), we finally draw: lesson 6 presents Enlace in a single box —a server and a database—, with its write and read paths, and the discipline of why you start simple and not with your colleague's eight shards. It's the first real design of the guide.

Resources