What It Does vs How Well It Has to Do It
Functional vs Non-Functional Requirements
Functional requirements say what a system does; non-functional requirements say how well — and the non-functional ones are what actually shape the design.
What you'll learn
- Separate functional from non-functional requirements
- Name the core non-functional axes — scale, latency, availability, consistency, durability
- See how the same feature yields different designs under different NFRs
Every design starts with two questions:
- Functional requirements — what does it do? The features. “Users can post a tweet.” “Users can follow other users.” “A timeline shows tweets from people you follow.”
- Non-functional requirements (NFRs) — how well must it do it? The qualities. “Timelines load in under 200ms.” “The service is available 99.99% of the time.” “We never lose a posted tweet.”
Beginners obsess over functional requirements because they’re the visible product. But the functional list for Twitter and for a toy clone is nearly identical — post, follow, view timeline. What makes Twitter hard is entirely non-functional: a million reads per second, sub-second timelines, near-perfect uptime. NFRs are what shape the architecture.
The core non-functional axes
| Axis | The question | Example targets |
|---|---|---|
| Scale | How much traffic and data? | 100M DAU, 1M reads/sec, 50TB/yr |
| Latency | How fast per request? | p99 < 200ms |
| Availability | How much uptime? | 99.99% (“four nines”) |
| Consistency | How fresh/correct must reads be? | Strong vs eventual |
| Durability | Can we ever lose data? | 11 nines for stored objects |
| Reliability | Does it behave correctly under failure? | No double-charges |
These axes trade off against each other — you cannot max them all. The CAP theorem (later in this track) makes one such tradeoff formal: under a network partition, you choose consistency or availability, not both.
The same feature, different NFRs, different design
This is the whole point. Take one functional requirement — “send a message” — and change only the NFRs:
| If the priority is… | The design leans toward… |
|---|---|
| Lowest latency, OK to lose a message | In-memory, fire-and-forget, no durable write |
| Never lose a message | Durable queue + acknowledgements + retries |
| Strict ordering per chat | Single partition per conversation |
| Global, billions of users | Sharding, regional routing, eventual consistency |
Same feature on the surface; four completely different systems underneath. The NFRs decided everything.
Where JavaScript engineers feel this
NFRs aren’t abstract — they show up directly in Node code:
// Functional requirement: "record that a user viewed a post."
// The NFR you pick changes the code completely.
// Priority = lowest latency, approximate counts OK:
// fire-and-forget, don't await — the user's request returns instantly.
function recordView(postId) {
queue.add('view', { postId }); // no await: ~0ms added latency
}
// Priority = exact counts, never lose a view:
// durably write and confirm before responding.
async function recordViewExact(postId) {
await db.increment('views', postId); // adds latency, but durable
} The functional behavior is “count a view” in both cases. The non-functional
requirement — fast-but-approximate vs slow-but-exact — is what dictates
whether you await. That single choice ripples out into your whole
architecture.
A quick checklist
For any prompt, before designing anything, fill in:
- Functional: the 3–5 core operations (verbs the user performs).
- Scale: DAU, read/write QPS, data volume per year.
- Latency: the p99 target for the critical path.
- Availability: how many nines.
- Consistency: strong, or is eventual acceptable?
- Durability: what must never be lost?
Nail these six and the architecture almost designs itself. The next lesson turns the “scale” line into actual numbers.