Blog 3
The C.R.E.D Framework
"Design a notification system."
Five words. And most engineers — talented ones, engineers who genuinely know their caches and queues and databases — start drawing boxes within thirty seconds.
Ten minutes later, they realize they designed the wrong system.
Part 1 gave you the why. Part 2 gave you the mindset — think in scale, hunt for bottlenecks, expect failure, reason in trade-offs. But a mindset, on its own, can still leave you frozen in front of a blank whiteboard or an open design doc, knowing you should think carefully and having no idea where to start.
This article fixes that. It gives you a method — a calm, repeatable sequence you can run on any system design problem, so you never again face an ambiguous prompt without a first move.
It's called C.R.E.D, and by the end of this article it will be the closest thing you have to a system design operating system.
Most Engineers Fail Before the First Box
Let's go back to those five words: "Design a notification system." Watch what a lot of engineers do — in interviews, yes, but far more importantly, in real project kickoffs and design meetings.
They start drawing immediately:
API → Database → Queue → Workers → Provider
It looks productive. Boxes are appearing, arrows are connecting, the whiteboard is filling up. There's a satisfying feeling of momentum.
But notice everything they haven't asked:
- Email, SMS, push — or all three? (Each has completely different constraints.)
- Transactional or promotional? (One must never be lost; the other can be dropped.)
- Real-time, or is a delay acceptable? (This decides the entire delivery path.)
- Can messages be lost? Do we need retries?
- What scale are we talking about — thousands, or hundreds of millions?
- Do users have notification preferences we must respect?
- Do we need to track delivery status?
Every one of those questions reshapes the architecture. And because none of them were asked, the beautiful diagram on the whiteboard is answering a question nobody actually posed. Ten minutes in, a clarification finally surfaces — "oh, these are all transactional and we can never lose one" — and the whole design has to be reconsidered.
Here's the thing worth sitting with: the failure didn't happen during the design. It happened before it. The engineer didn't fail because they don't understand queues. They failed because they solved the wrong problem — confidently, skillfully, and completely.
This is the single most common way system design goes wrong, and it has almost nothing to do with technical knowledge. Which means the fix isn't more technology. It's a process — one that forces understanding to come before design. That process is what the rest of this article builds.
Why Great Engineers Ask Before They Answer
There's a pressure, especially early in a career, to answer fast. Someone poses a design problem, and silence feels like weakness, so you start talking, start drawing, start solving — because solving looks like competence.
Experienced architects have unlearned this instinct almost completely. They know something that takes years to fully trust: asking the right questions is part of the answer — not a delay before it.
Before selecting a single technology, before drawing a single box, they ask:
- What are we actually building?
- For whom?
- What's in scope — and just as importantly, what's out of scope?
- What matters most here? What's the thing that must be true?
- What can be simplified or ignored?
- What constraints are we operating under?
This isn't stalling. It's the most leveraged part of the entire process, because every answer narrows an infinite design space into a concrete one. An engineer who asks these questions first isn't slower — they're the one who avoids building the wrong thing.
And there's a deeper principle underneath it, one that connects straight back to Part 2's lesson that constraints drive architecture: no constraints, no architecture.
Without constraints, "design a notification system" has a million valid answers and no way to choose between them. The constraints are what collapse that million down to one defensible design. So the architect's first job isn't to design — it's to find the constraints. And that's exactly where C.R.E.D begins.
Introducing C.R.E.D
Here's the whole framework:
C — Clarify What are we actually building, and for whom?
R — Requirements What must it do, and how well must it do it?
E — Estimate How big is this, in real numbers?
D — Design Only now do we draw the architecture.
Four steps, in order. That order is the entire point.
C.R.E.D is not a rigid template you fill in mechanically. It's a thinking sequence — a path that takes you from an ambiguous prompt to a defensible design, without falling into the trap that catches everyone: designing before understanding.
Each step feeds the next. Clarify defines the real problem. Requirements turn that understanding into concrete must-haves. Estimate reveals how serious the scale actually is. And only then — armed with a clear problem, real requirements, and honest numbers — do you Design, with every choice grounded in something you already established.
What makes C.R.E.D worth internalizing is that it isn't just an interview trick. The exact same sequence works for architecture reviews, system design interviews, technical planning, capacity discussions, presales solutioning, design documents, and real project discovery.
Anywhere you face a technical decision under ambiguity, C.R.E.D gives you a calm place to start. Let's walk through each step, using a notification system as our running example — the same prompt that tripped up our hypothetical engineer at the start.
Step 1: Clarify
Clarification is where you define the actual problem — as opposed to the one you assumed. It's the step that would have saved our engineer their ten wasted minutes.
For "design a notification system," good clarifying questions sound like this:
- What notification channels — email, SMS, push, in-app, or all of them?
- Is this transactional, promotional, or both?
- Is delivery immediate, or can some notifications be delayed?
- Can duplicates happen? Can messages be lost?
- Do users have preferences we must respect (channels, quiet hours, opt-outs)?
- Do we need delivery tracking and status?
- What scale are we designing for?
- Is provider integration in scope? Is scheduling in scope?
Notice what these have in common: every single one changes the architecture depending on the answer. That's the test of a good clarifying question — it identifies a design-shaping variable.
Now, clarification can also go wrong, and it's worth knowing what bad clarification looks like: asking a long list of random questions to seem thorough, diving into implementation details far too early ("which message broker?"), asking things that don't actually shape the design, or spending so long clarifying that you never get to design.
The goal isn't to interrogate. It's to surface the handful of variables that genuinely determine the shape of the system, and then move on. Clarification should identify design-shaping variables, not become a questionnaire.
And the mindset to carry into it: clarification is not a delay tactic. It is how architects avoid solving the wrong problem.
Get this step right, and everything downstream gets easier — because you're finally designing the system someone actually asked for.
Step 2: Requirements
Once you know what you're building, you pin down what it must do and how well it must do it. These are two genuinely different things, and separating them is one of the most important habits in system design.
Functional Requirements — what the system must do
These are the features, the verbs, the capabilities. For our notification system: accept a notification request, store the notification, send it through the selected channel, retry failed deliveries, track delivery status, respect user preferences.
Functional requirements are usually the easy part — they fall almost directly out of good clarification. They tell you what the system does.
Non-Functional Requirements — how well it must do it
These are the qualities, the adverbs — and they are where the real architecture is decided. For the same system: throughput (how many notifications per second?), latency (how fast must delivery feel?), availability (how much downtime is tolerable?), reliability (can we drop messages, or never?), durability (must status survive a crash?), cost (what's the budget per notification at volume?), security (who can send, who can receive?), observability (how do we know it's working?).
Here's the crucial insight, and it's the causal engine of the whole design: non-functional requirements drive the architecture. The functional requirement "send a notification" is satisfied by a single line of code calling a provider. It's the non-functional requirements that force everything interesting into existence:
- high throughput → you need a queue and a pool of workers, not one synchronous call
- low latency → you need a fast delivery path and maybe prioritization for urgent messages
- high reliability → you need retries, a dead-letter queue, durable storage
- low cost → you need batching, smart provider selection, throttling for promotional traffic
- auditability → you need persistent status history, not fire-and-forget
- observability → you need metrics, logs, and traces built in from the start
See the pattern? Each quality requirement summons a piece of architecture. The features barely move the design; the non-functional requirements shape almost all of it. This is exactly what Part 2 meant when it said constraints drive architecture more than features — here it is, made concrete.
Functional requirements tell us what to build. Non-functional requirements tell us how serious the architecture needs to be.
When you separate these two cleanly, your design stops being a guess and starts being a consequence — every component you draw can be traced back to a requirement that demanded it.
Step 3: Estimate
Now we put rough numbers on the problem. And I want to be clear about what this step is and isn't, because it intimidates people unnecessarily.
Estimation is not about perfect math. Nobody expects you to know the exact QPS of a system that doesn't exist yet. The goal is completely different: estimation is about discovering scale drivers — figuring out which part of the system becomes expensive or hard first.
Let's run it for the notification system with simple, illustrative assumptions:
Daily active users: 10 million
Notifications/user/day: 5
Total notifications/day: 50 million
Seconds/day: ~86,400
Average notifications/sec: ~580
Peak factor: 10×
Peak notifications/sec: ~5,800
None of these numbers are precise, and that's fine. What matters is what they reveal. The moment you see ~5,800 notifications per second at peak, the architecture starts making decisions for you: one worker is obviously not enough — you need many, scaling horizontally; provider rate limits suddenly matter a great deal; queue depth becomes something you have to watch; retry amplification (retries piling on during a provider slowdown) becomes a real risk; storage for status history becomes a genuine capacity question; observability stops being optional.
That's the magic of estimation: a handful of rough numbers turned a vague "notification system" into a system with specific pressures you now have to design for. You didn't need precision — you needed the shape of the load.
A few other quantities are worth estimating, depending on the system.
Storage
50 million notifications/day
× ~1 KB metadata each
≈ 50 GB/day (before indexes and replication)
Which immediately raises: how long do we retain status? What does that cost over a year? Storage estimates turn "we'll save the records" into a real number with real consequences.
Bandwidth
For media-heavy systems — think video or image delivery — bandwidth often dominates everything else. Moving large files to many users is expensive and shapes the whole design toward CDNs and edge delivery. (Our notification system is light here; a streaming system would not be.)
Cache Size
For read-heavy systems, you estimate how much of your hot data you'd want to keep in memory. If a small fraction of your data serves most of your reads, a modest cache absorbs enormous load — and that estimate tells you whether caching is a nice-to-have or a structural necessity.
Read/Write Ratio
This one quietly determines a system's entire personality. A URL shortener is overwhelmingly read-heavy — one write, then millions of reads — so it's a caching problem. A chat application is write-heavy — every message is a write that must be stored and delivered. A social feed is read-heavy but with fan-out complexity — one post can trigger millions of downstream reads. Knowing the ratio tells you where to spend your design effort before you've drawn a thing.
The goal of estimation is not a perfect number. The goal is to discover which part of the system becomes expensive first.
We'll keep estimation light here — there's a dedicated bonus article later in the series that goes deep on the techniques. For now, the method is what matters: rough numbers, honestly reasoned, to find the pressure points.
Step 4: Design
Now — and only now — do we draw architecture. With a clear problem, real requirements, and honest estimates behind us, the design becomes almost a formality: each piece follows from something we already established.
Start with the high-level flow for the notification system:
Client / Internal Service
↓
Notification API
↓
Notification Store
↓
Queue
↓
Workers
↓
Email / SMS / Push Providers
↓
Delivery Status Updates
Then design in layers, deepening only as needed. This layered approach is what keeps you from drowning in detail — you get the whole picture stable before you refine any one part.
Layer 1: High-Level Flow
The basic request path, above. A request arrives, gets stored, gets queued, gets picked up by a worker, gets sent to a provider, and produces a status update. Get this skeleton agreed before going deeper.
Layer 2: Data Model
What do we actually store? A notification record: recipient, channel, content reference, status, retry count, timestamps. The data model makes the abstract flow concrete.
Layer 3: Scaling
Here the estimates pay off. Queues to absorb the ~5,800/sec peak. A pool of workers that scales horizontally. Partitioning so throughput isn't bottlenecked. Respect for provider rate limits so we don't get throttled.
Layer 4: Reliability
The non-functional reliability requirement made concrete: retries for transient failures, a dead-letter queue for messages that keep failing, idempotency so a retry doesn't send twice, and provider failover — conceptually — so one provider's outage isn't ours.
Layer 5: Observability and Operations
Metrics (throughput, delivery rate, queue depth), logs, delivery dashboards, and alerts — so humans can actually see and operate the system in production.
Notice we're not going deep into any one layer. That's deliberate — Blog 14 designs a notification system fully, and this is just our vehicle for demonstrating the method. The point here isn't the notification system. It's that design, done well, is the calm final step of a sequence — not the panicked first one.
The Hidden Fifth Move: Defend
Officially, C.R.E.D ends at Design. But there's an unofficial fifth move that separates engineers who drew an architecture from engineers who understand one: they can defend every choice.
Watch the difference. Someone asks, "Why use a queue?"
A weak answer: "Because queues are scalable." That's a buzzword wearing a lab coat. It names a property without connecting it to anything.
A strong answer: "We expect bursty notification traffic, and the external providers have rate limits. A queue decouples request acceptance from delivery — so we can accept a spike instantly and drain it at a sustainable rate — and it lets us retry failures without blocking the caller. The trade-off is that delivery becomes eventual rather than immediate, and we take on the operational complexity of running and monitoring the queue."
Feel the difference. The second answer ties the choice to a requirement (bursty traffic, provider limits), explains what it buys (decoupling, spike absorption, non-blocking retries), and — crucially — names the trade-off (eventual delivery, operational cost). That's not describing a component. That's justifying a decision.
The same move applies everywhere:
- "Why cache?" → "Reads dominate writes here by a wide margin, and the same data is requested repeatedly; a cache absorbs that read load off the database. The trade-off is potential staleness, which is acceptable for this data."
- "Why a relational database?" → "The data is highly relational with real transactional needs, and we need strong consistency for these records. The trade-off is that scaling writes is harder than with some alternatives."
- "Why object storage?" → "We're storing large binary files, not queryable structured data; object storage is built for exactly that at a fraction of the cost. The trade-off is that it's not a database — no rich queries."
- "Why not microservices yet?" → "The team is small and the domain is still shifting; a well-structured monolith ships faster and is far easier to operate. We can extract services later when scale or team size genuinely demands it. The trade-off is that some scaling has to wait."
The lesson underneath all of them: architecture is not naming components. Architecture is justifying choices under constraints.
If you can run C.R.E.D and defend each choice this way, you're no longer performing system design. You're doing it.
What Interviewers and Reviewers Are Actually Evaluating
It's worth understanding what people are really looking for when they watch you work through a design — because it's the same thing in a system design interview and in a real design review at work, and it's almost never "did you arrive at the one correct architecture."
They're evaluating:
- how you handle ambiguity — do you panic, or do you calmly clarify?
- whether you clarify before designing — or leap straight to boxes
- whether you identify the constraints that matter
- whether you separate functional and non-functional requirements
- whether you estimate scale rather than hand-waving it
- whether your architecture follows from your requirements — or floats free of them
- whether you can explain your trade-offs honestly
- whether you consider failure before it's raised
- whether you communicate clearly throughout
Look at that list. Every item is a C.R.E.D behavior. The framework isn't a way to pass the evaluation — it is the thing being evaluated. An engineer who runs C.R.E.D naturally is demonstrating exactly the judgment that interviewers and design reviewers are trying to find.
And that's why this matters far beyond interviews: the senior engineer whose design docs get approved quickly, whose architecture reviews go smoothly, whose planning meetings don't spiral — they're running this same sequence, whether or not they call it C.R.E.D. The evaluation in the interview room is just a compressed version of the evaluation that happens every day in real engineering work.
Applying C.R.E.D Quickly: Design Instagram Stories
Let's run the whole framework fast on a different problem, to show how portable it is. Prompt: "Design Instagram Stories."
Clarify: Upload and view stories — both in scope? Do they expire after 24 hours? Global users, or one region? What media types — images, video, both? Any privacy controls (close friends, blocked users)?
Requirements: Functional — upload a story, view stories, auto-expire after 24 hours, enforce access control. Non-functional — fast media delivery globally, high availability, sensible cost at scale.
Estimate: Daily active users, uploads per day, storage generated per day, view traffic (far higher than uploads), CDN bandwidth for global delivery. Just those rough numbers already tell us this is a media-heavy, read-heavy, global system — which means object storage and a CDN are going to be central, and the write path is comparatively light.
Design:
User
↓
Upload API
↓
Object Storage → CDN (media delivery, global, fast)
Metadata
↓
Database (who posted what, when, privacy)
Expiration
↓
Background Job (removes stories after 24 hours)
The point here isn't to fully design Instagram — that would take an article of its own. The point is how calm and structured the discussion stays when you run C.R.E.D. No panic, no random boxes, no technology name-dropping. Just: understand it, pin it down, size it, then draw it. The same four steps, on a completely different problem, in a fraction of the time.
C.R.E.D Beyond Interviews
I keep returning to this because it's the part most system design content misses. C.R.E.D is not an interview hack you discard afterward. It's a genuinely useful thinking tool across your whole career:
- architecture reviews — clarify the problem before critiquing the solution
- project discovery — the sequence is good discovery
- technical planning — requirements and estimates before commitments
- capacity planning — estimation is the whole game
- incident prevention — "what breaks first?" asked early
- presales solutioning — clarify the client's real need before proposing
- engineering leadership discussions — structure a chaotic conversation
- design documents — a great design doc is literally C.R.E.D on paper
The underlying truth is simple: any serious technical decision benefits from the same discipline. Clarify the problem. Define the requirements. Estimate the load. Design intentionally. Defend the trade-offs. Whether you're in a forty-five-minute interview or a six-month project, that sequence keeps you honest and keeps you from moving fast in the wrong direction.
From My Journey: An Architectural Lesson
Early in an engineering career, it's easy to believe that architecture discussions are a contest to have the best answer, fastest. The person who starts drawing the confident final design first looks like the strongest engineer in the room, and so that's what you try to be.
Real design conversations, over enough years, teach something almost opposite.
The strongest engineers in those rooms are very often not the ones who start drawing immediately. They're the ones who slow the room down — just enough — to establish what's actually being solved before anyone commits to a solution. They ask what problem we're really addressing. Who the users are. What must be true. What can be safely ignored. What scale actually matters here. What trade-offs everyone is quietly assuming but nobody has said out loud.
At first, this can look like hesitation. It isn't. It's the discipline of refusing to solve a problem nobody has clearly stated yet — because they've learned, usually the hard way, how this goes wrong. So many poor designs begin with smart people confidently solving an unclear problem. The engineering is excellent. The architecture is elegant. And it solves the wrong thing, which means all of that skill produced something that has to be rebuilt.
That's the lesson that reframes what a framework like C.R.E.D is even for. It isn't bureaucracy, and it isn't there to slow good engineers down. It's there to make sure their considerable speed is pointed in the right direction.
A structured process does not slow good engineers down. It prevents them from moving fast in the wrong direction.
That's the whole reason to internalize C.R.E.D. Not to look methodical — to avoid the specific, expensive, deeply common mistake of designing the wrong system beautifully.
Common Mistakes Engineers Make With System Design Problems
A quick field guide to the traps C.R.E.D is built to prevent:
- Jumping straight to boxes — designing before understanding the problem
- Starting with technology names — "let's use Kafka" before knowing if you need a queue at all
- Skipping clarification — solving the assumed problem instead of the real one
- Treating all requirements as equal — missing that a few non-functional requirements dominate the design
- No non-functional requirements — designing features with no sense of scale, reliability, or cost
- No estimation — hand-waving the load and missing the real bottleneck
- Drawing too many components too early — complexity before clarity
- Using Kafka / Redis / microservices as buzzwords — naming tools instead of justifying them
- Not explaining trade-offs — presenting choices as if they were free
- Ignoring failure — designing only the happy path
- Not defending choices — unable to say why, only what
- Trying to design everything at once — instead of in calm layers
Every one of these is a symptom of the same root cause: designing before understanding. And every one is exactly what running C.R.E.D, in order, quietly prevents.
Key Takeaways
- Most system design mistakes happen before the first box is drawn — you fail by solving the wrong problem, not by misusing technology.
- Clarify before designing — asking the right questions is part of the answer, not a delay before it.
- C.R.E.D gives you structure under ambiguity: Clarify, Requirements, Estimate, Design — a thinking sequence, not a rigid template.
- Functional requirements define what the system does; they're usually the easy part.
- Non-functional requirements drive the architecture — throughput, latency, reliability, cost, and observability are what force the interesting design into existence.
- Estimation reveals scale drivers — rough numbers, honestly reasoned, to discover which part of the system becomes expensive first.
- Design should evolve in layers — high-level flow, then data, then scaling, then reliability, then observability — never everything at once.
- Architecture choices must be defended — naming a component isn't design; justifying it under constraints is.
- The framework works far beyond interviews — design reviews, planning, discovery, capacity, presales, and real projects all run on the same sequence.
- A structured process doesn't slow good engineers down; it keeps their speed pointed in the right direction.
What's Next
You now have both halves of the foundation: the mindset (Part 2) and the method (this article). You can walk up to an ambiguous problem and calmly turn it into a defensible design.
But there's a layer beneath all of this that most engineers have never really looked at — what actually happens when a user types a URL and hits enter. Before we design large systems, we need to understand the journey a single request takes across the internet to reach them.
Part 4 — DNS to CDN: How the Internet Actually Delivers a Request. We'll follow one request, all the way through:
https://example.com
↓
browser
↓
DNS
↓
CDN
↓
load balancer
↓
reverse proxy
↓
application server
↓
cache
↓
database
Every large system you'll ever design sits on top of this path. Understanding it — really understanding what each hop does and why it exists — is what turns architecture diagrams from abstract boxes into a system you can actually reason about.
Question for Readers
Now that you've seen the full sequence, be honest with yourself:
Which C.R.E.D step do you usually skip? Do you jump straight to design before clarifying? And what was the last design discussion where unclear requirements led to rework you could have avoided?
Most of us have a step we skip under pressure. Naming it is the first step to fixing it.